claude-dev-env 1.36.0 → 1.36.1

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.
@@ -2,9 +2,9 @@
2
2
  name: pr-converge
3
3
  description: >-
4
4
  Drives the current PR to convergence by alternating Cursor Bugbot and the
5
- second audit (**bugteam** always — `Skill({skill: "bugteam", ...})` when the host
6
- exposes `Skill`; bugteam `SKILL.md` **Path routing** picks Path A vs Path B from
7
- `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`; per-path harness in
5
+ second audit (**bugteam** always — `Skill({skill: "bugteam", ...})` when the
6
+ host exposes `Skill`; bugteam `SKILL.md` **Path routing** picks Path A vs Path
7
+ B from `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`; per-path harness in
8
8
  `bugteam/reference/workflow-path-a-orchestrated-teams.md` and
9
9
  `bugteam/reference/workflow-path-b-task-harness.md`). Each invocation runs one
10
10
  tick of work in the main session: fetches the latest reviewer state, applies
@@ -13,33 +13,38 @@ description: >-
13
13
  Default behavior loops until back-to-back clean: pace the next tick with
14
14
  ScheduleWakeup when the harness exposes it, otherwise use the AHK
15
15
  auto-continue driver (see workflows/ahk-auto-continue-loop.md). Pacing details
16
- live in workflows next to SKILL.md — load exactly one per Step 4.
17
- Convergence requires four gates on the same HEAD: (1) a back-to-back clean
18
- cycle (bugbot CLEAN immediately followed by second-audit CLEAN with no
19
- intervening fixes), (2) no outstanding Copilot reviewer findings, (3)
20
- `mergeStateStatus == CLEAN` with `mergeable == MERGEABLE` (a `DIRTY`
21
- state triggers the `rebase` skill; non-CLEAN non-DIRTY states are hard
22
- blockers), and (4) the post-convergence Copilot review request returns
23
- clean or, if it surfaces findings, the PR is still flipped ready and a
24
- follow-up draft PR is opened off the converged HEAD with those findings as
25
- a checklist. After all gates pass the PR is flipped to ready for review and
26
- the loop terminates.
27
- Multi-PR runs persist traffic in `<TMPDIR>/pr-converge-<session_id>/state.json`
28
- per §Multi-PR orchestration model; single-PR-only runs may use the conversation
29
- state line instead. Triggers: '/pr-converge', 'drive PR to
30
- convergence', 'loop bugbot and bugteam',
31
- 'babysit bugbot and bugteam', 'until both are clean', 'converge this PR'.
16
+ live in workflows next to SKILL.md — load exactly one per Step 4. Convergence
17
+ requires four gates on the same HEAD: (1) a back-to-back clean cycle (bugbot
18
+ CLEAN immediately followed by second-audit CLEAN with no intervening fixes),
19
+ (2) no outstanding Copilot reviewer findings, (3) `mergeStateStatus == CLEAN`
20
+ with `mergeable == MERGEABLE` (a `DIRTY` state triggers the `rebase` skill;
21
+ non-CLEAN non-DIRTY states are hard blockers), and (4) the post-convergence
22
+ Copilot review request returns clean — or, if it surfaces findings, the PR is
23
+ still flipped ready and a follow-up draft PR is opened off the converged HEAD
24
+ with those findings as a checklist. After all gates pass the PR is flipped to
25
+ ready for review and the loop terminates. Multi-PR runs persist traffic in
26
+ `<TMPDIR>/pr-converge-<session_id>/state.json>` per §Multi-PR orchestration
27
+ model; single-PR-only runs may use the conversation state line instead.
28
+ Triggers: '/pr-converge', 'drive PR to convergence', 'loop bugbot and
29
+ bugteam', 'babysit bugbot and bugteam', 'until both are clean', 'converge this
30
+ PR'.
32
31
  ---
33
32
 
34
33
  # PR Converge
35
34
 
36
- Each **invocation** runs **one tick** of the bugbot ↔ second-audit loop in the **parent session** (fetch state, address findings under the Fix
37
- protocol when needed, at most one fix commit per tick, inline replies or teammate handoffs, Bugbot re-trigger rules in Step 2 / Step 3).
38
- **By default** the skill **keeps going** until back-to-back clean on the same `HEAD`: after each tick, **Step 4** schedules the next tick with
39
- `ScheduleWakeup` when the tool exists, otherwise uses the **AHK auto-continue** driver. On convergence, mark the PR ready (`gh pr ready` or
40
- `mark_pr_ready.py` per §Step 2), then **stop all pacing** (omit further `ScheduleWakeup`; stop the AHK auto-typer when that fallback was in use).
41
- Default entry is **`/pr-converge`** (loops per Step 4). When the host exposes `ScheduleWakeup`, wakeups use `prompt: "/pr-converge"` unless the
42
- harness requires the `/loop` wrapper for wakeup execution (`workflows/schedule-wakeup-loop.md`).
35
+ Each **invocation** runs **one tick** of the bugbot ↔ second-audit loop in the
36
+ **parent session** (fetch state, address findings under the Fix protocol when
37
+ needed, at most one fix commit per tick, inline replies or teammate handoffs,
38
+ Bugbot re-trigger rules in Step 2 / Step 3). **By default** the skill **keeps
39
+ going** until back-to-back clean on the same `HEAD`: after each tick, **Step 4**
40
+ schedules the next tick with `ScheduleWakeup` when the tool exists, otherwise
41
+ uses the **AHK auto-continue** driver. On convergence, mark the PR ready (`gh pr
42
+ ready` or `mark_pr_ready.py` per §Step 2), then **stop all pacing** (omit
43
+ further `ScheduleWakeup`; stop the AHK auto-typer when that fallback was in
44
+ use). Default entry is **`/pr-converge`** (loops per Step 4). When the host
45
+ exposes `ScheduleWakeup`, wakeups use `prompt: "/pr-converge"` unless the
46
+ harness requires the `/loop` wrapper for wakeup execution
47
+ (`workflows/schedule-wakeup-loop.md`).
43
48
 
44
49
  ## Table of contents
45
50
 
@@ -47,7 +52,7 @@ Each **invocation** runs **one tick** of the bugbot ↔ second-audit loop in the
47
52
  2. [Pacing workflows (load exactly one)](#pacing-workflows-load-exactly-one)
48
53
  3. [State across ticks](#state-across-ticks)
49
54
  4. [Per-tick work](#per-tick-work)
50
- - [Step 1: Resolve current HEAD and PR context](#step-1-resolve-current-head-and-pr-context)
55
+ - [Step 1 HEAD and PR context](#step-1-resolve-current-head-and-pr-context)
51
56
  - [Step 2: Branch on `phase`](#step-2-branch-on-phase)
52
57
  - [Convergence gates](#convergence-gates)
53
58
  - [Step 3: Re-trigger bugbot](#step-3-re-trigger-bugbot)
@@ -59,200 +64,395 @@ Each **invocation** runs **one tick** of the bugbot ↔ second-audit loop in the
59
64
 
60
65
  ## Parent session
61
66
 
62
- Use this skill on a **draft PR** where **Cursor Bugbot** and the **`/bugteam`** audit should **re-run after each push**, with
63
- **findings fixed between rounds**, until **back-to-back clean** on the same `HEAD`; then **mark the PR ready for review**.
64
-
65
- Run **every converge tick** in the **parent harness session** (the conversation where the user invoked `/pr-converge`).
66
- **Loop pacing** (how the next tick is scheduled) is split into two workflow files — load **exactly one** per **Step 4**; see
67
- [Pacing workflows](#pacing-workflows-load-exactly-one).
68
-
69
- This skill **complements** **bugteam** (same skill, **team** vs **background-agent** workflow per §Second-audit execution): it sequences Bugbot
70
- re-reviews, second-audit runs, the Fix protocol, and inline replies or teammate handoffs between pushes until back-to-back clean. On every
71
- BUGTEAM tick, run **bugteam** — never a hand-rolled substitute audit. **Fix protocol** production
72
- edits in the **main Cursor session** use **`Task`** with **`subagent_type: "generalPurpose"`** plus the clean-coder **Read** preamble in the **`prompt`**
73
- (see [Fix protocol](#fix-protocol)) - Cursor does not accept `subagent_type: "clean-coder"`. When **`state.json`** drives multi-PR orchestration,
74
- the **`clean-coder` teammate** path in that model is unchanged. **Loop pacing** stays
75
- in the **main** session when this host exposes `ScheduleWakeup`; otherwise use the AHK workflow file row below.
67
+ Use this skill on a **draft PR** where **Cursor Bugbot** and the **`/bugteam`**
68
+ audit should **re-run after each push**, with **findings fixed between rounds**,
69
+ until **back-to-back clean** on the same `HEAD`; then **mark the PR ready for
70
+ review**.
71
+
72
+ Run **every converge tick** in the **parent harness session** (the conversation
73
+ where the user invoked `/pr-converge`). **Loop pacing** (how the next tick is
74
+ scheduled) is split into two workflow files load **exactly one** per **Step
75
+ 4**; see [Pacing workflows](#pacing-workflows-load-exactly-one).
76
+
77
+ This skill **complements** **bugteam** (same skill, **team** vs
78
+ **background-agent** workflow per §Second-audit execution): it sequences Bugbot
79
+ re-reviews, second-audit runs, the Fix protocol, and inline replies or teammate
80
+ handoffs between pushes until back-to-back clean. On every BUGTEAM tick, run
81
+ **bugteam** — never a hand-rolled substitute audit. **Fix protocol** production
82
+ edits in the **main Cursor session** use **`Task`** with **`subagent_type:
83
+ "generalPurpose"`** plus the clean-coder **Read** preamble in the **`prompt`**
84
+ (see [Fix protocol](#fix-protocol)) - Cursor does not accept `subagent_type:
85
+ "clean-coder"`. When **`state.json`** drives multi-PR orchestration, the
86
+ **`clean-coder` teammate** path in that model is unchanged. **Loop pacing**
87
+ stays in the **main** session when this host exposes `ScheduleWakeup`; otherwise
88
+ use the AHK workflow file row below.
76
89
 
77
90
  ## Pacing workflows (load exactly one)
78
91
 
79
- Before **Step 4** on each tick, **only the parent session** that is executing this skill's Step 4 (not a `Task` / `Explore` child) picks **one**
80
- workflow row using the steps below, then **Use the Read tool** on that row's file path next to this skill's `SKILL.md` (installed copies usually
81
- live under `$HOME/.claude/skills/pr-converge/`):
82
-
83
- 1. **Open the tool inventory for this turn** — every function or tool **name** the harness allows **this** assistant to invoke **in this message**
84
- (the same catalog that lists companions such as `Read`, `Task`, and your harness's shell or terminal tool).
85
- 2. **`ScheduleWakeup` is invokable** if that catalog contains a **top-level** callable entry whose name is exactly **`ScheduleWakeup`**. If the
86
- catalog lists only indirect gateways (for example **`call_mcp_tool`** with server-qualified MCP tools inside descriptors), **`ScheduleWakeup` is
87
- not invokable** here unless **`ScheduleWakeup`** itself still appears as its own invocable name in that same catalog — when in doubt, **not**
88
- invokable.
89
- 3. **Pick the table row** — invokable → **`ScheduleWakeup` available**; otherwise **`ScheduleWakeup` not available** (includes missing, empty, or
90
- unreadable catalogs: **fail closed** to the AHK workflow; do **not** attempt `ScheduleWakeup`).
92
+ Before **Step 4** on each tick, **only the parent session** that is executing
93
+ this skill's Step 4 (not a `Task` / `Explore` child) picks **one** workflow row
94
+ using the steps below, then **Use the Read tool** on that row's file path next
95
+ to this skill's `SKILL.md` (installed copies usually live under
96
+ `$HOME/.claude/skills/pr-converge/`):
97
+
98
+ 1. **Open the tool inventory for this turn** every function or tool **name**
99
+ the harness allows **this** assistant to invoke **in this message** (the same
100
+ catalog that lists companions such as `Read`, `Task`, and your harness's
101
+ shell or terminal tool).
102
+ 2. **`ScheduleWakeup` is invokable** if that catalog contains a **top-level**
103
+ callable entry whose name is exactly **`ScheduleWakeup`**. If the catalog
104
+ lists only indirect gateways (for example **`call_mcp_tool`** with
105
+ server-qualified MCP tools inside descriptors), **`ScheduleWakeup` is not
106
+ invokable** here unless **`ScheduleWakeup`** itself still appears as its own
107
+ invocable name in that same catalog — when in doubt, **not** invokable.
108
+ 3. **Pick the table row** — invokable → **`ScheduleWakeup` available**;
109
+ otherwise → **`ScheduleWakeup` not available** (includes missing, empty, or
110
+ unreadable catalogs: **fail closed** to the AHK workflow; do **not** attempt
111
+ `ScheduleWakeup`).
91
112
 
92
113
  | Route | Read this file |
93
114
  | --- | --- |
94
115
  | `ScheduleWakeup` available | `workflows/schedule-wakeup-loop.md` |
95
116
  | `ScheduleWakeup` not available | `workflows/ahk-auto-continue-loop.md` |
96
117
 
97
- All pacing-specific instructions for that route — delays, prompts, AHK setup, `continue` handling, convergence cleanup for the auto-typer,
98
- inline-lag pacing split, and route-only gotchas live **only** in that workflow file. This `SKILL.md` keeps shared bugbot / second-audit / Fix
99
- protocol / stop rules.
118
+ All pacing-specific instructions for that route — delays, prompts, AHK setup,
119
+ `continue` handling, convergence cleanup for the auto-typer, inline-lag pacing
120
+ split, and route-only gotchas — live **only** in that workflow file. This
121
+ `SKILL.md` keeps shared bugbot / second-audit / Fix protocol / stop rules.
100
122
 
101
- - **`/pr-converge`** (default): loops until convergence. After each tick (unless converged or stopped), run **Step 4**, which starts by loading
102
- the correct workflow row from the table above.
123
+ - **`/pr-converge`** (default): loops until convergence. After each tick (unless
124
+ converged or stopped), run **Step 4**, which starts by loading the correct
125
+ workflow row from the table above.
103
126
 
104
127
  ## Progressive disclosure (skill folder)
105
128
 
106
- This skill is a **folder** (`SKILL.md` plus `scripts/` plus `workflows/`): wrappers centralize gh pagination and body-file rules so the model composes orchestration instead of re-deriving CLI footguns. Read in this order ([Anthropic — internal patterns for Claude Code skills](https://x.com/trq212/status/2033949937936085378)):
129
+ This skill is a **folder** (`SKILL.md` plus `scripts/` plus `workflows/`):
130
+ wrappers centralize gh pagination and body-file rules so the model composes
131
+ orchestration instead of re-deriving CLI footguns. Read in this order
132
+ ([Anthropic — internal patterns for Claude Code
133
+ skills](https://x.com/trq212/status/2033949937936085378 )):
107
134
 
108
135
  1. This `SKILL.md` — phase graph, teammate contracts, stop conditions.
109
- 2. [`scripts/README.md`](scripts/README.md) — argv, stdout JSON shapes, pointers to `../../rules/gh-paginate.md` and `../../rules/gh-body-file.md`.
110
- 3. [`../bugteam/reference/workflow-path-b-task-harness.md`](../bugteam/reference/workflow-path-b-task-harness.md) **on demand** — bugteam **Path B** harness only (read after bugteam `SKILL.md` **Path routing** selects Path B). Path A harness: [`../bugteam/reference/workflow-path-a-orchestrated-teams.md`](../bugteam/reference/workflow-path-a-orchestrated-teams.md).
111
- 4. Individual script source or `--help` only when a call fails or `${CLAUDE_SKILL_DIR}` resolves unexpectedly.
112
-
113
- Taxonomy: **CI/CD & Deployment** in the [`babysit-pr` archetype](https://x.com/trq212/status/2033949937936085378) — monitors a PR, applies fixes between reviewer ticks, and flips it ready-for-review on convergence. If the doc feels broad, use **§Multi-PR orchestration model** as the workflow spine and **§Per-tick work** as the single-PR linearization.
136
+ 2. [`scripts/README.md`](scripts/README.md) — argv, stdout JSON shapes, pointers
137
+ to `../../rules/gh-paginate.md` and `../../rules/gh-body-file.md`.
138
+ 3. Bugteam **Path B** harness (**on demand**): after `SKILL.md` **Path routing**
139
+ selects Path B, read [`workflow-path-b-task-harness.md`][path-b]. Path A
140
+ harness: [`workflow-path-a-orchestrated-teams.md`][path-a].
141
+ 4. Individual script source or `--help` — only when a call fails or
142
+ `${CLAUDE_SKILL_DIR}` resolves unexpectedly.
143
+
144
+ [path-b]: ../bugteam/reference/workflow-path-b-task-harness.md
145
+ [path-a]: ../bugteam/reference/workflow-path-a-orchestrated-teams.md
146
+
147
+ Taxonomy: **CI/CD & Deployment** in the [`babysit-pr`
148
+ archetype](https://x.com/trq212/status/2033949937936085378) — monitors a PR,
149
+ applies fixes between reviewer ticks, and flips it ready-for-review on
150
+ convergence. If the doc feels broad, use **§Multi-PR orchestration model** as
151
+ the workflow spine and **§Per-tick work** as the single-PR linearization.
114
152
 
115
153
  ## Gotchas
116
154
 
117
- Non-default behaviors worth burning in; add a bullet here when a real run fails in a new way ([same source](https://x.com/trq212/status/2033949937936085378)):
118
-
119
- - **`ScheduleWakeup` is not in subagent tool registries** — a background `general-purpose` tick cannot schedule the next re-entry; only the parent session where this skill runs with `ScheduleWakeup` in the tool registry can call it.
120
- - **Bugbot only recognizes the literal re-trigger phrase `bugbot run`** other comment text no-ops; prefer `trigger_bugbot.py` (temp body file) or
121
- the bundled `packages/claude-dev-env/skills/pr-converge/scripts/post-bugbot-run.ps1` so backticks in prose never corrupt the PR comment.
122
- - **Review body and inline comments can desync for the same `commit_id`** — “dirty body, zero inline rows at `current_head`” is **`inline_lag`**, not **`dirty`**; bump `inline_lag_streak`, wait 60s, retry fetch (Step 2 BUGBOT fourth branch; §Fix result → general-purpose steps 4c–4e).
123
- - **`state.json` without the §Concurrency lock loses merges** when several teammates finish in one wall-clock window.
124
- - **`tick_count` must not double-increment** — conversation line (Step 1) only when **no** `state.json`; with `state.json`, only the orchestrator bump in §Orchestrator `state.json` writes increments `tick_count`.
125
- - **Back-to-back clean is necessary but not sufficient — `mergeStateStatus` gates the ready flip** — a PR can be back-to-back clean (bugbot CLEAN ∧ bugteam CLEAN at the same HEAD) yet still have merge conflicts with the base branch. Before flipping ready, run `check_pr_mergeability.py` and confirm `mergeStateStatus == "CLEAN"` AND `mergeable == "MERGEABLE"`. When `mergeStateStatus == "DIRTY"` (or `mergeable == "CONFLICTING"`), invoke the **`rebase`** skill ([`../rebase/SKILL.md`](../rebase/SKILL.md), Phase 1–4); after a successful rebase + force-with-lease push, the new HEAD invalidates prior clean state — reset `bugbot_clean_at = null`, `copilot_clean_at = null`, transition `phase = BUGBOT`, retrigger bugbot, schedule next tick. Non-`CLEAN` non-`DIRTY` states (`BLOCKED`, `BEHIND`, `UNKNOWN`) are hard blockers per §Stop conditions.
126
- - **Copilot findings on `current_head` block convergence** — Copilot (`copilot-pull-request-reviewer[bot]`) findings are evaluated *after* bugbot CLEAN ∧ bugteam CLEAN at the same HEAD. When `fetch_copilot_reviews.py` returns a review at `current_head` whose `state == "CHANGES_REQUESTED"` (or `state == "COMMENTED"` with a non-empty body) and there are unaddressed inline findings from `fetch_copilot_inline_comments.py`, treat the result as a Fix protocol input (same shape as bugbot dirty): TDD fix → push → reply inline → reset `bugbot_clean_at = null` AND `copilot_clean_at = null` → transition `phase = BUGBOT` → retrigger bugbot → schedule. The full back-to-back clean cycle must be met again. If no Copilot review exists on `current_head` yet, this gotcha does **not** apply — the proactive request happens in §Convergence gates step (c).
127
- - **Post-convergence Copilot request runs once, regardless of outcome** — after every other gate passes (bugbot CLEAN ∧ bugteam CLEAN ∧ no outstanding Copilot findings on HEAD ∧ `mergeStateStatus == "CLEAN"`), call `request_copilot_review.py` and wait one tick. A clean Copilot review marks the PR ready and terminates. A Copilot review with findings on `current_head` still marks the PR ready (this is the "we still allow it to be 'clean'" rule), but before terminating runs `open_followup_copilot_pr.py` to capture the findings as a draft PR off `current_head`. The follow-up PR runs its own `/pr-converge` cycle (queued for the user — never inline-spawn another converge loop in the same session). The reviewer ID literal is `copilot-pull-request-reviewer[bot]` with the `[bot]` suffix — `Copilot`, `copilot`, and `github-copilot` all silently no-op per [`../copilot-review/SKILL.md`](../copilot-review/SKILL.md).
155
+ Non-default behaviors worth burning in; add a bullet here when a real run fails
156
+ in a new way ([same source](https://x.com/trq212/status/2033949937936085378)):
157
+
158
+ - **`ScheduleWakeup` is not in subagent tool registries**a background
159
+ `general-purpose` tick cannot schedule the next re-entry; only the parent
160
+ session where this skill runs with `ScheduleWakeup` in the tool registry can
161
+ call it.
162
+ - **Bugbot only recognizes the literal re-trigger phrase `bugbot run`** other
163
+ comment text no-ops; prefer `trigger_bugbot.py` (temp body file) or the
164
+ bundled `packages/claude-dev-env/skills/pr-converge/scripts/post-bugbot-ru
165
+ n.ps1` so backticks in prose never corrupt the PR comment.
166
+ - **Review body and inline comments can desync for the same `commit_id`** —
167
+ “dirty body, zero inline rows at `current_head`” is **`inline_lag`**, not
168
+ **`dirty`**; bump `inline_lag_streak`, wait 60s, retry fetch (Step 2 BUGBOT
169
+ fourth branch; §Fix result → general-purpose steps 4c–4e).
170
+ - **`state.json` without the §Concurrency lock loses merges** when several
171
+ teammates finish in one wall-clock window.
172
+ - **`tick_count` must not double-increment** — conversation line (Step 1) only
173
+ when **no** `state.json`; with `state.json`, only the orchestrator bump in
174
+ §Orchestrator `state.json` writes increments `tick_count`.
175
+ - **Back-to-back clean is necessary but not sufficient — `mergeStateStatus`
176
+ gates the ready flip** — a PR can be back-to-back clean (bugbot CLEAN ∧
177
+ bugteam CLEAN at the same HEAD) yet still have merge conflicts with the base
178
+ branch. Before flipping ready, run `check_pr_mergeability.py` and confirm
179
+ `mergeStateStatus == "CLEAN"` AND `mergeable == "MERGEABLE"`. When
180
+ `mergeStateStatus == "DIRTY"` (or `mergeable == "CONFLICTING"`), invoke the
181
+ **`rebase`** skill ([`../rebase/SKILL.md`](../rebase/SKILL.md), Phase 1–4);
182
+ after a successful rebase + force-with-lease push, the new HEAD invalidates
183
+ prior clean state — reset `bugbot_clean_at = null`, `copilot_clean_at = null`,
184
+ transition `phase = BUGBOT`, retrigger bugbot, schedule next tick. Non-`CLEAN`
185
+ non-`DIRTY` states (`BLOCKED`, `BEHIND`, `UNKNOWN`) are hard blockers per
186
+ §Stop conditions.
187
+ - **Copilot findings on `current_head` block convergence** — Copilot
188
+ (`copilot-pull-request-reviewer[bot]`) findings are evaluated *after* bugbot
189
+ CLEAN ∧ bugteam CLEAN at the same HEAD. When `fetch_copilot_reviews.py`
190
+ returns a review at `current_head` whose `state == "CHANGES_REQUESTED"` (or
191
+ `state == "COMMENTED"` with a non-empty body) and there are unaddressed inline
192
+ findings from `fetch_copilot_inline_comments.py`, treat the result as a Fix
193
+ protocol input (same shape as bugbot dirty): TDD fix → push → reply inline →
194
+ reset `bugbot_clean_at = null` AND `copilot_clean_at = null` → transition
195
+ `phase = BUGBOT` → retrigger bugbot → schedule. The full back-to-back clean
196
+ cycle must be met again. If no Copilot review exists on `current_head` yet,
197
+ this gotcha does **not** apply — the proactive request happens in §Convergence
198
+ gates step (c).
199
+ - **Post-convergence Copilot request runs once, regardless of outcome** — after
200
+ every other gate passes (bugbot CLEAN ∧ bugteam CLEAN ∧ no outstanding Copilot
201
+ findings on HEAD ∧ `mergeStateStatus == "CLEAN"`), call
202
+ `request_copilot_review.py` and wait one tick. A clean Copilot review marks
203
+ the PR ready and terminates. A Copilot review with findings on `current_head`
204
+ still marks the PR ready (this is the "we still allow it to be 'clean'" rule),
205
+ but before terminating runs `open_followup_copilot_pr.py` to capture the
206
+ findings as a draft PR off `current_head`. The follow-up PR runs its own
207
+ `/pr-converge` cycle (queued for the user — never inline-spawn another
208
+ converge loop in the same session). The reviewer ID literal is
209
+ `copilot-pull-request-reviewer[bot]` with the `[bot]` suffix — `Copilot`,
210
+ `copilot`, and `github-copilot` all silently no-op per
211
+ [`../copilot-review/SKILL.md`](../copilot-review/SKILL.md).
128
212
 
129
213
  ## Second-audit execution (bugteam — Path A vs Path B)
130
214
 
131
- The **second audit** (BUGTEAM phase) is **always** the **bugteam** skill: preflight, CODE_RULES gate, **`code-quality-agent`** / **`clean-coder`** loop,
132
- audit rubric, outcome shape, and Step 2 BUGTEAM §(b)–(d) contract all live in [`../bugteam/SKILL.md`](../bugteam/SKILL.md) plus `PROMPTS.md` /
133
- `EXAMPLES.md` / `CONSTRAINTS.md` do not re-spec them here.
134
-
135
- **Path routing is bugteam-internal:** [bugteam `SKILL.md` — Path routing](../bugteam/SKILL.md#path-routing-mandatory-first-branch) (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` equals **`1`** → Path A orchestrated teams; otherwise → Path B Task harness). **Harness-only** execution: Path A — [`../bugteam/reference/workflow-path-a-orchestrated-teams.md`](../bugteam/reference/workflow-path-a-orchestrated-teams.md); Path B — [`../bugteam/reference/workflow-path-b-task-harness.md`](../bugteam/reference/workflow-path-b-task-harness.md).
136
-
137
- **pr-converge rule:** Prefer **`Skill({skill: "bugteam", args: "<PR URL or args>"})`** wherever the tool registry exposes `Skill` — bugteam executes the correct path. When **`Skill` is not invokable** (typical delegated teammate), that worker still runs **bugteam** by loading **`../bugteam/SKILL.md`** from the same checkout and following **Path routing** plus [`../bugteam/reference/workflow-path-b-task-harness.md`](../bugteam/reference/workflow-path-b-task-harness.md) when Path B applies; never replace bugteam with a hand-rolled audit.
138
-
139
- ### Team infrastructure detection (for pr-converge pacing and docs cross-links only)
140
-
141
- This mirrors bugteam **Path routing** so pr-converge prose stays aligned with host capability checks elsewhere in this file:
142
-
143
- - **`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` == `1`** (trimmed) → bugteam **Path A** when `/bugteam` runs inside Claude Code with teams.
144
- - **Otherwise** → bugteam **Path B** Task harness inside the same bugteam `SKILL.md` contract.
215
+ The **second audit** (BUGTEAM phase) is **always** the **bugteam** skill:
216
+ preflight, CODE_RULES gate, **`code-quality-agent`** / **`clean-coder`** loop,
217
+ audit rubric, outcome shape, and Step 2 BUGTEAM §(b)–(d) contract all live in
218
+ [`../bugteam/SKILL.md`](../bugteam/SKILL.md) plus `PROMPTS.md` / `EXAMPLES.md` /
219
+ `CONSTRAINTS.md` — do not re-spec them here.
220
+
221
+ **Path routing is bugteam-internal:** [bugteam `SKILL.md` Path
222
+ routing](../bugteam/SKILL.md#path-routing-mandatory-first-branch)
223
+ (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` equals **`1`** Path A orchestrated
224
+ teams; otherwise → Path B Task harness). **Harness-only** execution: Path A —
225
+ [`../bugteam/reference/workflow-path-a-orchestrated-teams.md`](../bugteam/refere
226
+ nce/workflow-path-a-orchestrated-teams.md); Path B —
227
+ [`../bugteam/reference/workflow-path-b-task-harness.md`](../bugteam/ref
228
+ erence/workflow-path-b-task-harness.md).
229
+
230
+ **pr-converge rule:** Prefer **`Skill({skill: "bugteam", args: "<PR URL or
231
+ args>"})`** wherever the tool registry exposes `Skill` — bugteam executes the
232
+ correct path. When **`Skill` is not invokable** (typical delegated teammate),
233
+ that worker still runs **bugteam** by loading **`../bugteam/SKILL.md`** from the
234
+ same checkout and following **Path routing** plus
235
+ [`../bugteam/reference/workflow-path-b-task-harness.md`](../bugteam/reference/wo
236
+ rkflow-path-b-task-harness.md) when Path B applies; never replace bugteam with a
237
+ hand-rolled audit.
238
+
239
+ ### Team infrastructure detection (for pr-converge pacing and docs cross-links
240
+ ### only)
241
+
242
+ This mirrors bugteam **Path routing** so pr-converge prose stays aligned with
243
+ host capability checks elsewhere in this file:
244
+
245
+ - **`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` == `1`** (trimmed) → bugteam **Path
246
+ A** when `/bugteam` runs inside Claude Code with teams.
247
+ - **Otherwise** → bugteam **Path B** Task harness inside the same bugteam
248
+ `SKILL.md` contract.
145
249
 
146
250
  ## Multi-PR orchestration model
147
251
 
148
252
  ### Core rule: orchestrator is a traffic controller only
149
253
 
150
- The orchestrator (main session) **never** reads **repository source files**, writes code, audits findings, or does any per-PR **codebase** work inline. It **always** reads `state.json` for traffic state and may write only the narrow fields in §Orchestrator `state.json` writes; it receives teammate handoffs and spawns the next worker. Every unit of audit/fix work runs inside a dedicated teammate.
254
+ The orchestrator (main session) **never** reads **repository source files**,
255
+ writes code, audits findings, or does any per-PR **codebase** work inline. It
256
+ **always** reads `state.json` for traffic state and may write only the narrow
257
+ fields in §Orchestrator `state.json` writes; it receives teammate handoffs and
258
+ spawns the next worker. Every unit of audit/fix work runs inside a dedicated
259
+ teammate.
151
260
 
152
- This is a [workflow-style skill](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices#use-workflows-for-complex-tasks): the orchestrator decomposes the multi-PR problem into parallel per-PR subworkflows, each owned by a short-lived teammate. The orchestrator's only job is to keep the state file consistent and spawn the next agent in each chain.
261
+ This is a [workflow-style skill](https://platform.claude.com/docs/en/agents-and-
262
+ tools/agent-skills/best-practices#use-workflo ws-for-complex-tasks): the
263
+ orchestrator decomposes the multi-PR problem into parallel per-PR subworkflows,
264
+ each owned by a short-lived teammate. The orchestrator's only job is to keep the
265
+ state file consistent and spawn the next agent in each chain.
153
266
 
154
267
  ### Per-PR state file
155
268
 
156
- Create once at session start; each teammate writes its result back before going idle:
269
+ Create once at session start; each teammate writes its result back before going
270
+ idle:
157
271
 
158
272
  **Path:** `<TMPDIR>/pr-converge-<session_id>/state.json`
159
273
 
160
274
  **Session ID:** `YYYYMMDDHHMMSS` captured once when the loop starts.
161
275
 
162
- **Directory lifecycle:** Keep `<TMPDIR>/pr-converge-<session_id>/` for the **whole converge run** (every tick) until **each** `prs[...]` is
163
- **`converged`** or **`blocked`**, or the user stops. **Then** delete that folder if you want reclaim — **`mark_pr_ready.py` / `gh pr ready`** on
164
- GitHub is the canonical record of ready state. See [Memory](#memory) for the optional append-only log in the same directory.
276
+ **Directory lifecycle:** Keep `<TMPDIR>/pr-converge-<session_id>/` for the
277
+ **whole converge run** (every tick) until **each** `prs[...]` is **`converged`**
278
+ or **`blocked`**, or the user stops. **Then** delete that folder if you want
279
+ reclaim — **`mark_pr_ready.py` / `gh pr ready`** on GitHub is the canonical
280
+ record of ready state. See [Memory](#memory) for the optional append-only log in
281
+ the same directory.
165
282
 
166
283
  **Barebones schema:**
167
284
 
168
285
  ```json
169
286
  {
170
- "session_id": "20260502050000",
171
- "team_name": "bugteam-20260502050000",
172
- "prs": {
173
- "289": {
174
- "owner": "jl-cmd",
175
- "repo": "claude-code-config",
176
- "branch": "feat/shared-pr-loop-extraction",
177
- "phase": "BUGBOT",
178
- "current_head": "f9a7d49e",
179
- "bugbot_clean_at": null,
180
- "inline_lag_streak": 0,
181
- "tick_count": 5,
182
- "last_action": "bugbot_triggered",
183
- "status": "in_progress",
184
- "last_updated": "2026-05-02T10:00:00Z"
185
- }
186
- }
287
+ "session_id": "20260502050000",
288
+ "team_name": "bugteam-20260502050000",
289
+ "prs": {
290
+ "289": {
291
+ "owner": "jl-cmd",
292
+ "repo": "claude-code-config",
293
+ "branch": "feat/shared-pr-loop-extraction",
294
+ "phase": "BUGBOT",
295
+ "current_head": "f9a7d49e",
296
+ "bugbot_clean_at": null,
297
+ "inline_lag_streak": 0,
298
+ "tick_count": 5,
299
+ "last_action": "bugbot_triggered",
300
+ "status": "in_progress",
301
+ "last_updated": "2026-05-02T10:00:00Z"
302
+ }
303
+ }
187
304
  }
188
305
  ```
189
306
 
190
- **`team_name` field (Path A only):** when `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`, the orchestrator owns a single long-lived team for the whole sweep — see §Orchestrator team lifecycle.
191
-
192
- **`status` values:** `fresh` | `in_progress` | `awaiting_bugbot` | `awaiting_bugteam` | `converged` | `blocked`
193
-
194
- **Write rule:** Teammates write their result by reading the current file, merging **only** their PR's keyed entry under `prs`, and persisting the merged document back. Writes are keyed on `pr_number`; other PRs' entries are untouched in the merge logic — **but** see **Concurrency** below so parallel teammates never clobber each other.
195
-
196
- **Concurrency (mandatory):** When multiple teammates can finish in the same wall-clock window (including the case where **10+** idle notifications arrive together), a naive read–modify–write on `state.json` **loses updates** (two writers read the same revision; the second `write` overwrites the first). Every `state.json` update from a teammate **must** use **serialized access** plus **atomic publish**:
197
-
198
- 1. **Acquire** an exclusive lock in the same directory as `state.json`, for example a sibling path `state.json.lock` created with an **atomic create-only** primitive (`mkdir` on Unix when the path does not exist; on Windows `New-Item` / `md` guarded so only one creator succeeds, or a host file lock API). If acquisition fails because the lock exists, sleep with jitter and **retry** until held (cap retries and escalate per **Stop conditions** if the lock never clears — indicates a stuck teammate).
199
- 2. **Read** `state.json`, merge this teammate's `prs[<pr_number>]` object only, then **write** the full merged JSON to `state.json.tmp` in that directory.
200
- 3. **Replace** `state.json` atomically from `state.json.tmp` (`os.replace` / same-volume rename semantics so readers never see a half-written file).
307
+ **`team_name` field (Path A only):** when
308
+ `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`, the orchestrator owns a single
309
+ long-lived team for the whole sweep see §Orchestrator team lifecycle.
310
+
311
+ **`status` values:** `fresh` | `in_progress` | `awaiting_bugbot` |
312
+ `awaiting_bugteam` | `converged` | `blocked`
313
+
314
+ **Write rule:** Teammates write their result by reading the current file,
315
+ merging **only** their PR's keyed entry under `prs`, and persisting the merged
316
+ document back. Writes are keyed on `pr_number`; other PRs' entries are untouched
317
+ in the merge logic **but** see **Concurrency** below so parallel teammates
318
+ never clobber each other.
319
+
320
+ **Concurrency (mandatory):** When multiple teammates can finish in the same
321
+ wall-clock window (including the case where **10+** idle notifications arrive
322
+ together), a naive read–modify–write on `state.json` **loses updates** (two
323
+ writers read the same revision; the second `write` overwrites the first). Every
324
+ `state.json` update from a teammate **must** use **serialized access** plus
325
+ **atomic publish**:
326
+
327
+ 1. **Acquire** an exclusive lock in the same directory as `state.json`, for
328
+ example a sibling path `state.json.lock` created with an **atomic
329
+ create-only** primitive (`mkdir` on Unix when the path does not exist; on
330
+ Windows `New-Item` / `md` guarded so only one creator succeeds, or a host
331
+ file lock API). If acquisition fails because the lock exists, sleep with
332
+ jitter and **retry** until held (cap retries and escalate per **Stop
333
+ conditions** if the lock never clears — indicates a stuck teammate).
334
+ 2. **Read** `state.json`, merge this teammate's `prs[<pr_number>]` object only,
335
+ then **write** the full merged JSON to `state.json.tmp` in that directory.
336
+ 3. **Replace** `state.json` atomically from `state.json.tmp` (`os.replace` /
337
+ same-volume rename semantics so readers never see a half-written file).
201
338
  4. **Release** the lock (`rmdir` / `Remove-Item` on the lock path).
202
339
 
203
- **Orchestrator `state.json` writes (traffic metadata only):** Teammates own audit/fix payloads. The orchestrator **must not** merge finding bodies, file contents, or teammate-owned fields other than the two narrow exceptions below. It **must** use the **same §Concurrency lock** for any orchestrator write.
204
-
205
- 1. **Per-tick `tick_count` bump (mandatory):** At the **start** of each orchestrator tick, before spawning teammates for that tick, perform one locked read–merge–atomic publish: for **every** `prs[<pr_number>]` whose `status` is **not** `converged` or `blocked`, increment `tick_count` by **1** (initialize to `0` if missing) and refresh `last_updated`. This is for human-readable progress only there is **no** tick ceiling; the loop ends only on convergence or a **Stop conditions** branch.
206
- 2. **`phase` when only the orchestrator decides:** If the orchestrator applies a **Step 2 §Per-tick** phase transition (including **BUGTEAM §(d)** branches that set `phase = BUGBOT` without an immediate teammate `state.json` write) and no teammate merge occurs in the same tick for that PR, the orchestrator performs one locked merge that sets only `prs[<pr_number>].phase` (and `last_updated`) for the affected PR.
207
-
208
- **Orchestrator reads this file at the start of every tick** instead of relying on conversation context for cross-PR state.
340
+ **Orchestrator `state.json` writes (traffic metadata only):** Teammates own
341
+ audit/fix payloads. The orchestrator **must not** merge finding bodies, file
342
+ contents, or teammate-owned fields other than the two narrow exceptions below.
343
+ It **must** use the **same §Concurrency lock** for any orchestrator write.
344
+
345
+ 1. **Per-tick `tick_count` bump (mandatory):** At the **start** of each
346
+ orchestrator tick, before spawning teammates for that tick, perform one
347
+ locked read–merge–atomic publish: for **every** `prs[<pr_number>]` whose
348
+ `status` is **not** `converged` or `blocked`, increment `tick_count` by **1**
349
+ (initialize to `0` if missing) and refresh `last_updated`. This is for
350
+ human-readable progress only — there is **no** tick ceiling; the loop ends
351
+ only on convergence or a **Stop conditions** branch.
352
+ 2. **`phase` when only the orchestrator decides:** If the orchestrator applies a
353
+ **Step 2 §Per-tick** phase transition (including **BUGTEAM §(d)** branches
354
+ that set `phase = BUGBOT` without an immediate teammate `state.json` write)
355
+ and no teammate merge occurs in the same tick for that PR, the orchestrator
356
+ performs one locked merge that sets only `prs[<pr_number>].phase` (and
357
+ `last_updated`) for the affected PR.
358
+
359
+ **Orchestrator reads this file at the start of every tick** instead of relying
360
+ on conversation context for cross-PR state.
209
361
 
210
362
  ### Orchestrator team lifecycle (Path A only)
211
363
 
212
- **Applies when bugteam Path A is in use** (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`). When agent teams are disabled, this section is skipped and bugteam runs Path B with no team state. See [Team infrastructure detection](#team-infrastructure-detection-for-pr-converge-pacing-and-docs-cross-links-only) for the routing rule.
213
-
214
- **Why the orchestrator owns the team:** The bugteam skill's per-invocation `TeamCreate` / `TeamDelete` cycle assumes one bugteam invocation per session. In multi-PR converge, the orchestrator runs bugteam **per PR per BUGTEAM tick** — many invocations across the sweep. If each invocation tried to `TeamCreate`, the second one would fail with `Already leading team "<existing>"`; if each invocation called `TeamDelete` at exit, the next BUGTEAM tick would have nothing to attach to and would also create a fresh team that races other PRs' work. The orchestrator instead creates one team for the whole sweep and tears it down on full convergence — see [bugteam Team lifecycle](../bugteam/SKILL.md#team-lifecycle-path-a-only).
364
+ **Applies when bugteam Path A is in use**
365
+ (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`). When agent teams are disabled, this
366
+ section is skipped and bugteam runs Path B with no team state. See [Team
367
+ infrastructure
368
+ detection](#team-infrastructure-detection-for-pr-converge-pacing-and-docs-cross-
369
+ links-only) for the routing rule.
370
+
371
+ **Why the orchestrator owns the team:** The bugteam skill's per-invocation
372
+ `TeamCreate` / `TeamDelete` cycle assumes one bugteam invocation per session. In
373
+ multi-PR converge, the orchestrator runs bugteam **per PR per BUGTEAM tick** —
374
+ many invocations across the sweep. If each invocation tried to `TeamCreate`, the
375
+ second one would fail with `Already leading team "<existing>"`; if each
376
+ invocation called `TeamDelete` at exit, the next BUGTEAM tick would have nothing
377
+ to attach to and would also create a fresh team that races other PRs' work. The
378
+ orchestrator instead creates one team for the whole sweep and tears it down on
379
+ full convergence — see [bugteam Team
380
+ lifecycle](../bugteam/SKILL.md#team-lifecycle-path-a-only).
215
381
 
216
382
  **At session start (before the first tick spawns any teammate):**
217
383
 
218
- 1. Compute `team_name = "bugteam-<session_id>"` using the same `session_id` as the §Per-PR state file.
219
- 2. `TeamCreate(team_name=<team_name>, description="pr-converge sweep <session_id>", agent_type="team-lead")`. The orchestrator becomes the lead.
220
- 3. Locked write to `state.json` (per §Concurrency): merge `team_name` at the document root.
384
+ 1. Compute `team_name = "bugteam-<session_id>"` using the same `session_id` as
385
+ the §Per-PR state file.
386
+ 2. `TeamCreate(team_name=<team_name>, description="pr-converge sweep
387
+ <session_id>", agent_type="team-lead")`. The orchestrator becomes the lead.
388
+ 3. Locked write to `state.json` (per §Concurrency): merge `team_name` at the
389
+ document root.
221
390
 
222
- **At every BUGTEAM tick (per PR):** invoke bugteam in attach mode by setting both env vars before the call:
391
+ **At every BUGTEAM tick (per PR):** invoke bugteam in attach mode by setting
392
+ both env vars before the call:
223
393
 
224
394
  - `BUGTEAM_TEAM_LIFECYCLE=attach`
225
395
  - `BUGTEAM_TEAM_NAME=<state.team_name>`
226
396
 
227
- When the orchestrator drives bugteam via `Skill({skill: "bugteam", ...})`, set both env vars in the parent process before the `Skill` invocation. When bugteam runs inside a delegated worker (typical multi-PR fan-out), the spawn prompt must export the same two env vars at the top of the worker's bash environment so bugteam reads them on entry.
397
+ When the orchestrator drives bugteam via `Skill({skill: "bugteam", ...})`, set
398
+ both env vars in the parent process before the `Skill` invocation. When bugteam
399
+ runs inside a delegated worker (typical multi-PR fan-out), the spawn prompt must
400
+ export the same two env vars at the top of the worker's bash environment so
401
+ bugteam reads them on entry.
228
402
 
229
- **Teardown (only when every PR is terminal):** the orchestrator scans `state.json` and considers the sweep done when **every** `prs[<pr_number>].status` is either `converged` or `blocked`. At that point, and only at that point:
403
+ **Teardown (only when every PR is terminal):** the orchestrator scans
404
+ `state.json` and considers the sweep done when **every**
405
+ `prs[<pr_number>].status` is either `converged` or `blocked`. At that point, and
406
+ only at that point:
230
407
 
231
408
  1. `TeamDelete()` (orchestrator is the lead; no arguments).
232
- 2. Locked write to `state.json`: clear `team_name` from the document root (so a stale value cannot leak into a follow-up sweep).
409
+ 2. Locked write to `state.json`: clear `team_name` from the document root (so a
410
+ stale value cannot leak into a follow-up sweep).
233
411
  3. Continue with the §Memory cleanup of `<TMPDIR>/pr-converge-<session_id>/`.
234
412
 
235
- A user-stop or hard-blocker exit that ends the sweep before convergence still calls `TeamDelete()` here, because the orchestrator is shutting down. The only path that does **not** call `TeamDelete()` is "tick scheduled, sweep continuing" — which is the common case.
413
+ A user-stop or hard-blocker exit that ends the sweep before convergence still
414
+ calls `TeamDelete()` here, because the orchestrator is shutting down. The only
415
+ path that does **not** call `TeamDelete()` is "tick scheduled, sweep continuing"
416
+ — which is the common case.
236
417
 
237
418
  ### Teammate spawning rules
238
419
 
239
- When the orchestrator receives results from one or more PRs simultaneously (e.g. 10+ teammate idle notifications arrive together), it spawns one new agent **per PR** in a single parallel message — never processes any PR inline.
420
+ When the orchestrator receives results from one or more PRs simultaneously (e.g.
421
+ 10+ teammate idle notifications arrive together), it spawns one new agent **per
422
+ PR** in a single parallel message — never processes any PR inline.
240
423
 
241
424
  #### Audit result → fix worker per PR
242
425
 
243
426
  When a bugfind teammate reports completion (findings or clean):
244
427
 
245
- - Spawn **one fix worker per PR** with findings (Claude Code: `clean-coder` teammate / `Agent`; Cursor `Task`: `generalPurpose` + clean-coder **Read** preamble per [Fix protocol](#fix-protocol)). That worker:
428
+ - Spawn **one fix worker per PR** with findings (Claude Code: `clean-coder`
429
+ teammate / `Agent`; Cursor `Task`: `generalPurpose` + clean-coder **Read**
430
+ preamble per [Fix protocol](#fix-protocol)). That worker:
246
431
  1. Reads the outcomes XML for the PR.
247
432
  2. Applies TDD fixes (test first, then production code).
248
433
  3. Commits and pushes one fix commit.
249
- 4. Replies inline to each addressed finding comment via `reply_to_inline_comment.py`.
250
- 5. **Writes its result to `state.json`** (per §Concurrency) (`last_action: "fix_pushed"`, `current_head: <new SHA>`, `bugbot_clean_at: null`, `phase: "BUGBOT"`, `status: "awaiting_bugbot"`, `last_updated` as an ISO-8601 UTC timestamp).
434
+ 4. Replies inline to each addressed finding comment via
435
+ `reply_to_inline_comment.py`.
436
+ 5. **Writes its result to `state.json`** (per §Concurrency) (`last_action:
437
+ "fix_pushed"`, `current_head: <new SHA>`, `bugbot_clean_at: null`, `phase:
438
+ "BUGBOT"`, `status: "awaiting_bugbot"`, `last_updated` as an ISO-8601 UTC
439
+ timestamp).
251
440
  6. Goes idle.
252
441
 
253
- - For PRs with zero findings: spawn **one `general-purpose` agent** per PR. That agent:
254
- 1. If `bugbot_clean_at == current_head` (back-to-back clean): run `mark_pr_ready.py`, append one convergence row to `<TMPDIR>/pr-converge-<session_id>/converged.log` per §Memory (same `session_id` as `state.json`), then **write `state.json`** (per §Concurrency) setting this PR's entry to at least `status: "converged"`, `last_action: "converged"` (or `marked_ready`), `phase: "BUGBOT"`, and `last_updated` to an ISO-8601 UTC timestamp — **before** going idle. Omitting this write leaves the orchestrator on later ticks with a stale `awaiting_bugteam` / `in_progress` row and risks duplicate work.
255
- 2. Otherwise: update `state.json` (per §Concurrency) with `last_action: "audit_clean"`, `status: "awaiting_bugbot"`, `phase: "BUGBOT"`, then trigger bugbot via `trigger_bugbot.py`.
442
+ - For PRs with zero findings: spawn **one `general-purpose` agent** per PR. That
443
+ agent:
444
+ 1. If `bugbot_clean_at == current_head` (back-to-back clean): run
445
+ `mark_pr_ready.py`, append one convergence row to
446
+ `<TMPDIR>/pr-converge-<session_id>/converged.log` per §Memory (same
447
+ `session_id` as `state.json`), then **write `state.json`** (per
448
+ §Concurrency) setting this PR's entry to at least `status: "converged"`,
449
+ `last_action: "converged"` (or `marked_ready`), `phase: "BUGBOT"`, and
450
+ `last_updated` to an ISO-8601 UTC timestamp — **before** going idle.
451
+ Omitting this write leaves the orchestrator on later ticks with a stale
452
+ `awaiting_bugteam` / `in_progress` row and risks duplicate work.
453
+ 2. Otherwise: update `state.json` (per §Concurrency) with `last_action:
454
+ "audit_clean"`, `status: "awaiting_bugbot"`, `phase: "BUGBOT"`, then
455
+ trigger bugbot via `trigger_bugbot.py`.
256
456
  3. Goes idle.
257
457
 
258
458
  #### Fix result → general-purpose per PR
@@ -262,190 +462,358 @@ When a bugfix (clean-coder) teammate goes idle after pushing a fix:
262
462
  - Spawn **one `general-purpose` agent** per PR. That agent:
263
463
  1. Reads `state.json` for its PR.
264
464
  2. Triggers bugbot via `trigger_bugbot.py`.
265
- 3. Polls `fetch_bugbot_reviews.py` every 60s (up to 10 polls) until a review anchored to `current_head` appears.
266
- 4. **Poll / classify loop** (repeat from **4a** whenever **4c** schedules a retry):
465
+ 3. Polls `fetch_bugbot_reviews.py` every 60s (up to 10 polls) until a review
466
+ anchored to `current_head` appears.
467
+ 4. **Poll / classify loop** (repeat from **4a** whenever **4c** schedules a
468
+ retry):
267
469
  - **4a.** Fetches inline comments via `fetch_bugbot_inline_comments.py`.
268
- - **4b.** Classify — same three outcomes as Step 2 BUGBOT once a review exists at `current_head`:
269
- - **`clean`:** Review body indicates clean against `current_head` and zero unaddressed inline findings.
270
- - **`dirty`:** At least one unaddressed inline finding for `current_head` (actionable for the Fix protocol / `clean-coder`).
271
- - **`inline_lag`:** Review body indicates findings against `current_head`, but the inline-comments API returns zero matching comments for `current_head` (transient desync between review body and inline API — Step 2 BUGBOT fourth bullet).
272
- - **4c.** **If `inline_lag`:** Locked merge to `state.json` (per §Concurrency): increment `inline_lag_streak` (treat missing as `0` before increment); set `last_action: "inline_lag_wait"`, `phase: "BUGBOT"`, `last_updated`, and keep `status` consistent with monitoring (for example `awaiting_bugbot`). If `inline_lag_streak >= 3`, **hard blocker** per §Stop conditions (structurally inconsistent review); report and go idle **without** classifying as `dirty`. Otherwise sleep **60 seconds** and repeat from **4a** (re-fetch inline only — do not re-run step 2 or step 3).
273
- - **4d.** **If `clean`:** Exit the loop. Locked merge: set `bugbot_clean_at` to `current_head`, reset `inline_lag_streak` to `0`, update `last_action`, `status`, and **`phase`: `BUGTEAM`** (next work is second audit).
274
- - **4e.** **If `dirty`:** Exit the loop. Locked merge: reset `inline_lag_streak` to `0`, record findings count, update `last_action`, `status`, and **`phase`: `BUGBOT`** (next work is another fix pass).
470
+ - **4b.** Classify — same three outcomes as Step 2 BUGBOT once a review
471
+ exists at `current_head`:
472
+ - **`clean`:** Review body indicates clean against `current_head` and
473
+ zero unaddressed inline findings.
474
+ - **`dirty`:** At least one unaddressed inline finding for `current_head`
475
+ (actionable for the Fix protocol / `clean-coder`).
476
+ - **`inline_lag`:** Review body indicates findings against
477
+ `current_head`, but the inline-comments API returns zero matching
478
+ comments for `current_head` (transient desync between review body and
479
+ inline API — Step 2 BUGBOT fourth bullet).
480
+ - **4c.** **If `inline_lag`:** Locked merge to `state.json` (per
481
+ §Concurrency): increment `inline_lag_streak` (treat missing as `0` before
482
+ increment); set `last_action: "inline_lag_wait"`, `phase: "BUGBOT"`,
483
+ `last_updated`, and keep `status` consistent with monitoring (for example
484
+ `awaiting_bugbot`). If `inline_lag_streak >= 3`, **hard blocker** per
485
+ §Stop conditions (structurally inconsistent review); report and go idle
486
+ **without** classifying as `dirty`. Otherwise sleep **60 seconds** and
487
+ repeat from **4a** (re-fetch inline only — do not re-run step 2 or step
488
+ 3).
489
+ - **4d.** **If `clean`:** Exit the loop. Locked merge: set
490
+ `bugbot_clean_at` to `current_head`, reset `inline_lag_streak` to `0`,
491
+ update `last_action`, `status`, and **`phase`: `BUGTEAM`** (next work is
492
+ second audit).
493
+ - **4e.** **If `dirty`:** Exit the loop. Locked merge: reset
494
+ `inline_lag_streak` to `0`, record findings count, update `last_action`,
495
+ `status`, and **`phase`: `BUGBOT`** (next work is another fix pass).
275
496
  5. Reports back to orchestrator: one-line summary of outcome.
276
497
 
277
- - Orchestrator reads the updated `state.json` and spawns the appropriate next agent:
278
- - Result `clean` → spawn a `general-purpose` agent to run BUGTEAM phase (**bugteam** via `Skill` when available in that worker’s registry, else inline bugteam `SKILL.md` + Path B deltas per §Second-audit execution).
279
- - Monitor exited on **`dirty` (step 4e)** with actionable inline threads → spawn the same **fix worker** (same as "audit result with findings" above). Do **not** spawn `clean-coder` when the monitor only saw **`inline_lag`** (4c retries) without reaching **4e** — that path retries or escalates via the **`inline_lag_streak` ≥ 3** hard blocker in **Stop conditions** instead of a fix pass.
498
+ - Orchestrator reads the updated `state.json` and spawns the appropriate next
499
+ agent:
500
+ - Result `clean` → spawn a `general-purpose` agent to run BUGTEAM phase
501
+ (**bugteam** via `Skill` when available in that worker’s registry, else
502
+ inline bugteam `SKILL.md` + Path B deltas per §Second-audit execution).
503
+ - Monitor exited on **`dirty` (step 4e)** with actionable inline threads →
504
+ spawn the same **fix worker** (same as "audit result with findings" above).
505
+ Do **not** spawn `clean-coder` when the monitor only saw **`inline_lag`**
506
+ (4c retries) without reaching **4e** — that path retries or escalates via
507
+ the **`inline_lag_streak` ≥ 3** hard blocker in **Stop conditions** instead
508
+ of a fix pass.
280
509
 
281
510
  ### What the orchestrator does per tick
282
511
 
283
- 1. Perform the **per-tick `tick_count` bump** in §Orchestrator `state.json` writes (traffic metadata only) for every non-terminal PR under `prs`.
512
+ 1. Perform the **per-tick `tick_count` bump** in §Orchestrator `state.json`
513
+ writes (traffic metadata only) for every non-terminal PR under `prs`.
284
514
  2. Read `state.json`.
285
- 3. For each PR with new teammate results (idle notifications), spawn the next agent per the rules above — all in one parallel message.
515
+ 3. For each PR with new teammate results (idle notifications), spawn the next
516
+ agent per the rules above — all in one parallel message.
286
517
  4. Re-read `state.json` if needed for scheduling.
287
518
  5. Call `ScheduleWakeup` with the appropriate delay.
288
519
  6. Nothing else.
289
520
 
290
521
  ## Memory
291
522
 
292
- **Run directory** `<TMPDIR>/pr-converge-<session_id>/` (same `session_id` as §Per-PR state file) holds **`state.json`** and optional **`converged.log`**.
293
- Treat both as **durable for this converge run**: keep them from first create through **every tick** until **each** PR under `prs` is **`converged`**
294
- or **`blocked`**, or a **Stop conditions** branch ends the loop. **After** that, deleting the whole directory is safe — **`mark_pr_ready.py` /
295
- `gh pr ready`** on GitHub is the canonical record of ready state. This skill is a **folder skill**, not a Cursor plugin package; do **not** rely on
296
- `${CLAUDE_PLUGIN_DATA}`. OS or disk cleanup of `<TMPDIR>` (reboot, policy) can still remove files mid-run; that is environmental risk, not intentional
297
- behavior of this spec.
523
+ **Run directory** `<TMPDIR>/pr-converge-<session_id>/` (same `session_id` as
524
+ §Per-PR state file) holds **`state.json`** and optional **`converged.log`**.
525
+ Treat both as **durable for this converge run**: keep them from first create
526
+ through **every tick** until **each** PR under `prs` is **`converged`** or
527
+ **`blocked`**, or a **Stop conditions** branch ends the loop. **After** that,
528
+ deleting the whole directory is safe — **`mark_pr_ready.py` / `gh pr ready`** on
529
+ GitHub is the canonical record of ready state. This skill is a **folder skill**,
530
+ not a Cursor plugin package; do **not** rely on `${CLAUDE_PLUGIN_DATA}`. OS or
531
+ disk cleanup of `<TMPDIR>` (reboot, policy) can still remove files mid-run; that
532
+ is environmental risk, not intentional behavior of this spec.
298
533
 
299
534
  **`converged.log` (multi-PR only — requires `state.json`):**
300
535
 
301
- - **Path:** `<TMPDIR>/pr-converge-<session_id>/converged.log` (sibling of `state.json`).
302
- - **Format:** one tab-separated row per converged PR — `<ISO8601_UTC>\t<owner>/<repo>#<number>\tbugbot=<SHA>\t<SECOND_AUDIT_LABEL>=<SHA>` where `<SECOND_AUDIT_LABEL>` is always `bugteam` (second audit is the bugteam skill) per §Second-audit execution.
303
- - **Append site:** the agent that runs `mark_pr_ready.py` (see §Audit result → general-purpose convergence branch and Step 2 BUGTEAM second branch). Append **before** the locked `state.json` publish so the log row survives a failed or retried merge.
304
- - **Never read inside the loop.** The orchestrator and teammates never gate behavior on this file; it is for the user and follow-up tooling only.
305
-
306
- **Single-PR runs without `state.json`:** do **not** append `converged.log`; the in-conversation summary plus GitHub ready state are enough.
536
+ - **Path:** `<TMPDIR>/pr-converge-<session_id>/converged.log` (sibling of
537
+ `state.json`).
538
+ - **Format:** one tab-separated row per converged PR: ISO8601 UTC, then
539
+ owner/repo#number, then bugbot SHA, then second-audit SHA (label is always
540
+ `bugteam`) per §Second-audit execution.
541
+ - **Append site:** the agent that runs `mark_pr_ready.py` (see §Audit result
542
+ general-purpose convergence branch and Step 2 BUGTEAM second branch). Append
543
+ **before** the locked `state.json` publish so the log row survives a failed or
544
+ retried merge.
545
+ - **Never read inside the loop.** The orchestrator and teammates never gate
546
+ behavior on this file; it is for the user and follow-up tooling only.
547
+
548
+ **Single-PR runs without `state.json`:** do **not** append `converged.log`; the
549
+ in-conversation summary plus GitHub ready state are enough.
307
550
 
308
551
  ## Invocation modes
309
552
 
310
- - **`/pr-converge`** (default): runs **one tick**, then **Step 4** per [Pacing workflows](#pacing-workflows-load-exactly-one) — same loop-until-convergence semantics whether the user typed it once, a `ScheduleWakeup` fires with `prompt: "/pr-converge"`, or AHK sends `continue` (`workflows/schedule-wakeup-loop.md`, `workflows/ahk-auto-continue-loop.md`). Omit the next wakeup only on convergence or another **Stop conditions** branch.
311
- - **`/loop /pr-converge`**: optional **harness wrapper** when the parent only executes wakeup `prompt`s that are routed through the `/loop` skill; behavior is equivalent to default `/pr-converge` for per-tick work and Step 4. Use `prompt: "/loop /pr-converge"` in `ScheduleWakeup` only when that wrapper is required for the next firing to run.
553
+ - **`/pr-converge`** (default): runs **one tick**, then **Step 4** per [Pacing
554
+ workflows](#pacing-workflows-load-exactly-one) same loop-until-convergence
555
+ semantics whether the user typed it once, a `ScheduleWakeup` fires with
556
+ `prompt: "/pr-converge"`, or AHK sends `continue`
557
+ (`workflows/schedule-wakeup-loop.md`, `workflows/ahk-auto-continue-loop.md`).
558
+ Omit the next wakeup only on convergence or another **Stop conditions**
559
+ branch.
560
+ - **`/loop /pr-converge`**: optional **harness wrapper** when the parent only
561
+ executes wakeup `prompt`s that are routed through the `/loop` skill; behavior
562
+ is equivalent to default `/pr-converge` for per-tick work and Step 4. Use
563
+ `prompt: "/loop /pr-converge"` in `ScheduleWakeup` only when that wrapper is
564
+ required for the next firing to run.
312
565
 
313
566
  ## State across ticks
314
567
 
315
- **Dual persistence:** When `<TMPDIR>/pr-converge-<session_id>/state.json` exists (multi-PR or file-backed session per §Multi-PR orchestration model),
316
- the orchestrator and teammates treat **that file** as the source of truth for `phase`, heads, counters, and status — not the conversation
317
- transcript. When **no** `state.json` is in use (typical single-PR `/pr-converge` in Cursor), track the following **in each assistant turn as plain
318
- text** so the **next tick that resumes in this transcript** can re-read them from conversation context:
319
-
320
- - `phase`: `BUGBOT` or `BUGTEAM`. Start in `BUGBOT` on the first tick of a fresh loop.
321
- - `bugbot_clean_at`: the HEAD SHA at which bugbot last reported clean, or `null`. Reset to `null` whenever a new commit is pushed.
322
- - `inline_lag_streak`: integer counter, initialized to `0`. Tracks consecutive ticks where bugbot's review body indicates findings against
323
- `current_head` but the inline-comments API returns zero matching comments. Reset to `0` on any other branch outcome.
324
- - `tick_count`: integer, initialized to `0`. Increment on every tick (observability only; no ceiling).
325
-
326
- Each tick begins by reading the prior tick's state line from the most recent assistant message (when **no** `state.json`) and ends by emitting the
327
- updated state line; when `state.json` is in use, follow §What the orchestrator does per tick instead.
568
+ **Dual persistence:** When `<TMPDIR>/pr-converge-<session_id>/state.json` exists
569
+ (multi-PR or file-backed session per §Multi-PR orchestration model), the
570
+ orchestrator and teammates treat **that file** as the source of truth for
571
+ `phase`, heads, counters, and status not the conversation transcript. When
572
+ **no** `state.json` is in use (typical single-PR `/pr-converge` in Cursor),
573
+ track the following **in each assistant turn as plain text** so the **next tick
574
+ that resumes in this transcript** can re-read them from conversation context:
575
+
576
+ - `phase`: `BUGBOT` or `BUGTEAM`. Start in `BUGBOT` on the first tick of a fresh
577
+ loop.
578
+ - `bugbot_clean_at`: the HEAD SHA at which bugbot last reported clean, or
579
+ `null`. Reset to `null` whenever a new commit is pushed.
580
+ - `inline_lag_streak`: integer counter, initialized to `0`. Tracks consecutive
581
+ ticks where bugbot's review body indicates findings against `current_head` but
582
+ the inline-comments API returns zero matching comments. Reset to `0` on any
583
+ other branch outcome.
584
+ - `tick_count`: integer, initialized to `0`. Increment on every tick
585
+ (observability only; no ceiling).
586
+
587
+ Each tick begins by reading the prior tick's state line from the most recent
588
+ assistant message (when **no** `state.json`) and ends by emitting the updated
589
+ state line; when `state.json` is in use, follow §What the orchestrator does per
590
+ tick instead.
328
591
 
329
592
  ## Per-tick work
330
593
 
331
594
  ### Step 1: Resolve current HEAD and PR context
332
595
 
333
- Read the prior tick's state line from the most recent assistant message (or initialize all fields if none). **Increment `tick_count` by 1** in the **conversation state line** when **no** `state.json` is in use (single-PR-only invocation); when `state.json` exists, **do not** increment here — the orchestrator's per-tick bump in §Orchestrator `state.json` writes is the sole increment for that store.
596
+ Read the prior tick's state line from the most recent assistant message (or
597
+ initialize all fields if none). **Increment `tick_count` by 1** in the
598
+ **conversation state line** when **no** `state.json` is in use (single-PR-only
599
+ invocation); when `state.json` exists, **do not** increment here — the
600
+ orchestrator's per-tick bump in §Orchestrator `state.json` writes is the sole
601
+ increment for that store.
334
602
 
335
603
  ```bash
336
604
  python "${CLAUDE_SKILL_DIR}/scripts/view_pr_context.py"
337
605
  ```
338
606
 
339
- Output is a JSON object with `number`, `url`, `headRefOid`, `baseRefName`, `headRefName`, `isDraft`. Capture `number` (`<NUMBER>`), `headRefOid` (`current_head`), owner/repo (from `url`), branch name (`<BRANCH>`).
607
+ Output is a JSON object with `number`, `url`, `headRefOid`, `baseRefName`,
608
+ `headRefName`, `isDraft`. Capture `number` (`<NUMBER>`), `headRefOid`
609
+ (`current_head`), owner/repo (from `url`), branch name (`<BRANCH>`).
340
610
 
341
611
  ### Step 2: Branch on `phase`
342
612
 
343
613
  #### `phase == BUGBOT`
344
614
 
345
- a. Fetch Cursor Bugbot reviews newest-first and walk backwards until the first clean review. The script enforces the gh-paginate rule (uses `--paginate --slurp` plus Python JSON handling — see [`scripts/README.md`](scripts/README.md) and [`../../rules/gh-paginate.md`](../../rules/gh-paginate.md)) and classifies each review:
615
+ a. Fetch Cursor Bugbot reviews newest-first and walk backwards until the first
616
+ clean review. The script enforces the gh-paginate rule (uses `--paginate
617
+ --slurp` plus Python JSON handling — see
618
+ [`scripts/README.md`](scripts/README.md) and
619
+ [`../../rules/gh-paginate.md`](../../rules/gh-paginate.md)) and classifies each
620
+ review:
346
621
 
347
622
  ```bash
348
- python "${CLAUDE_SKILL_DIR}/scripts/fetch_bugbot_reviews.py" \
349
- --owner <OWNER> --repo <REPO> --number <NUMBER>
623
+ python "${CLAUDE_SKILL_DIR}/scripts/fetch_bugbot_reviews.py" \
624
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
350
625
  ```
351
626
 
352
- Output is a JSON array of `{review_id, commit_id, submitted_at, body, classification}`, newest-first, with `classification` already set to `"dirty"` or `"clean"`. Track dirty entries in a temp file as you walk; the Fix protocol reads it back later in this tick:
627
+ Output is a JSON array of `{review_id, commit_id, submitted_at, body,
628
+ classification}`, newest-first, with `classification` already set to `"dirty"`
629
+ or `"clean"`. Track dirty entries in a temp file as you walk; the Fix protocol
630
+ reads it back later in this tick:
353
631
 
354
632
  ```bash
355
- dirty_reviews_path=$(mktemp "${TMPDIR:-/tmp}/pr-converge-bugbot.XXXXXX")
356
- : > "$dirty_reviews_path"
633
+ dirty_reviews_path=$(mktemp "${TMPDIR:-/tmp}/pr-converge-bugbot.XXXXXX")
634
+ : > "$dirty_reviews_path"
357
635
  ```
358
636
 
359
- Iterate from index 0 (most recent) toward older entries:
637
+ Iterate from index 0 (most recent) toward older entries:
360
638
 
361
- - For a dirty review, append one JSON line to `$dirty_reviews_path` with `{review_id, commit_id, submitted_at, body}`.
362
- - Stop at the first clean review. Older reviews are presumed addressed at that clean checkpoint and are not re-read.
639
+ - For a dirty review, append one JSON line to `$dirty_reviews_path` with
640
+ `{review_id, commit_id, submitted_at, body}`.
641
+ - Stop at the first clean review. Older reviews are presumed addressed at
642
+ that clean checkpoint and are not re-read.
363
643
  - When index 0 is itself clean, `$dirty_reviews_path` stays empty.
364
644
 
365
- Capture `commit_id`, `submitted_at`, body, and `classification` of the index-0 review for the decision branches below. When a branch routes to the **Fix protocol**, read every entry from `$dirty_reviews_path` and address all of them — not just index 0.
645
+ Capture `commit_id`, `submitted_at`, body, and `classification` of the index-0
646
+ review for the decision branches below. When a branch routes to the **Fix
647
+ protocol**, read every entry from `$dirty_reviews_path` and address all of them
648
+ — not just index 0.
366
649
 
367
- b. Fetch unaddressed inline comments from `cursor[bot]` for the **newest submitted Bugbot review** on `current_head`. The script enforces the same `--paginate --slurp` pattern as `fetch_bugbot_reviews.py`, resolves that review via the reviews list, then returns only inline rows whose `pull_request_review_id` matches that review (so stale threads from an older Bugbot review on the same SHA are excluded).
650
+ b. Fetch unaddressed inline comments from `cursor[bot]` for the **newest
651
+ submitted Bugbot review** on `current_head`. The script enforces the same
652
+ `--paginate --slurp` pattern as `fetch_bugbot_reviews.py`, resolves that review
653
+ via the reviews list, then returns only inline rows whose
654
+ `pull_request_review_id` matches that review (so stale threads from an older
655
+ Bugbot review on the same SHA are excluded).
368
656
 
369
657
  ```bash
370
- python "${CLAUDE_SKILL_DIR}/scripts/fetch_bugbot_inline_comments.py" \
371
- --owner <OWNER> --repo <REPO> --number <NUMBER> --commit "$current_head"
658
+ python "${CLAUDE_SKILL_DIR}/scripts/fetch_bugbot_inline_comments.py" \
659
+ --owner <OWNER> --repo <REPO> --number <NUMBER> --commit "$current_head"
372
660
  ```
373
661
 
374
- Output is a JSON array of `{comment_id, commit_id, path, line, body}` for those matching inline comments.
375
-
376
- c. Decide (the four branches below cover every input combination — match the first branch whose predicate holds):
377
- - **No bugbot review yet, OR latest bugbot review's `commit_id` differs from `current_head`:** Re-trigger bugbot (Step 3), set
378
- `bugbot_clean_at = null`, reset `inline_lag_streak = 0`, schedule next wakeup, return.
379
- - **Latest review's `commit_id == current_head` AND zero unaddressed inline findings AND review body indicates clean:** Set `bugbot_clean_at
380
- = current_head`. Reset `inline_lag_streak = 0`. Transition `phase = BUGTEAM`. Continue to BUGTEAM in this same tick — back-to-back
381
- convergence requires the second audit on the same HEAD before the next wakeup is scheduled.
382
- - **Latest review's `commit_id == current_head` with unaddressed inline findings (review body indicates findings):** Apply the **Fix
383
- protocol** below. Reset `inline_lag_streak = 0`. When **`state.json`** is in use, the clean-coder teammate pushes, replies inline, writes
384
- `state.json`, then goes idle; **Step 3** (`trigger_bugbot.py` on the new HEAD) runs **after** via the orchestrator-spawned follow-up agent
385
- (§Fix result general-purpose). When **no** `state.json` (typical single-PR Cursor tick), complete implement → push → inline replies → Step 3
386
- in the same tick per your loaded pacing workflow. Schedule next wakeup, return.
387
- - **Latest review's `commit_id == current_head` AND review body indicates findings AND inline-comments API returns zero matching comments
388
- for `current_head`:** Treat as transient API propagation lag. Increment `inline_lag_streak`. When `inline_lag_streak >= 3`, escalate as a hard
389
- blocker; report and terminate with no loop pacing; stop the AHK auto-typer per `workflows/ahk-auto-continue-loop.md` if that path was active.
390
- Otherwise complete **Step 4** using the **BUGBOT inline-lag** section of the pacing workflow you loaded ([Pacing workflows](#pacing-workflows-load-exactly-one)); if no workflow file applies, schedule the next wakeup at `delaySeconds: 60`.
391
-
392
- **Gotcha (Bugbot already clean on `HEAD`, but another `bugbot run` fires):** When the latest Bugbot review on `current_head` already indicates
393
- **clean / no issues** (the branch that sets `bugbot_clean_at` and transitions to **`phase = BUGTEAM`**), the next action must be the **second
394
- audit in the same tick** per §Second-audit execution never a redundant `bugbot run`. If merged findings require commits, continue with **Fix
395
- protocol** per [Fix protocol](#fix-protocol) (`Task` with `generalPurpose` and the clean-coder **Read** preamble). If **`Task`** cannot be invoked, STOP and notify the user.
662
+ Output is a JSON array of `{comment_id, commit_id, path, line, body}` for those
663
+ matching inline comments.
664
+
665
+ c. Decide (the four branches below cover every input combination match the
666
+ first branch whose predicate holds):
667
+ - **No bugbot review yet, OR latest bugbot review's `commit_id` differs from
668
+ `current_head`:** Re-trigger bugbot (Step 3), set `bugbot_clean_at = null`,
669
+ reset `inline_lag_streak = 0`, schedule next wakeup, return.
670
+ - **Latest review's `commit_id == current_head` AND zero unaddressed inline
671
+ findings AND review body indicates clean:** Set `bugbot_clean_at =
672
+ current_head`. Reset `inline_lag_streak = 0`. Transition `phase = BUGTEAM`.
673
+ Continue to BUGTEAM in this same tick back-to-back convergence requires
674
+ the second audit on the same HEAD before the next wakeup is scheduled.
675
+ - **Latest review's `commit_id == current_head` with unaddressed inline
676
+ findings (review body indicates findings):** Apply the **Fix protocol**
677
+ below. Reset `inline_lag_streak = 0`. When **`state.json`** is in use, the
678
+ clean-coder teammate pushes, replies inline, writes `state.json`, then goes
679
+ idle; **Step 3** (`trigger_bugbot.py` on the new HEAD) runs **after** via
680
+ the orchestrator-spawned follow-up agent (§Fix result general-purpose).
681
+ When **no** `state.json` (typical single-PR Cursor tick), complete
682
+ implement push inline replies Step 3 in the same tick per your loaded
683
+ pacing workflow. Schedule next wakeup, return.
684
+ - **Latest review's `commit_id == current_head` AND review body indicates
685
+ findings AND inline-comments API returns zero matching comments for
686
+ `current_head`:** Treat as transient API propagation lag. Increment
687
+ `inline_lag_streak`. When `inline_lag_streak >= 3`, escalate as a hard
688
+ blocker; report and terminate with no loop pacing; stop the AHK auto-typer
689
+ per `workflows/ahk-auto-continue-loop.md` if that path was active.
690
+ Otherwise complete **Step 4** using the **BUGBOT inline-lag** section of
691
+ the pacing workflow you loaded ([Pacing
692
+ workflows](#pacing-workflows-load-exactly-one)); if no workflow file
693
+ applies, schedule the next wakeup at `delaySeconds: 60`.
694
+
695
+ **Gotcha (Bugbot already clean on `HEAD`, but another `bugbot run` fires):**
696
+ When the latest Bugbot review on `current_head` already indicates **clean / no
697
+ issues** (the branch that sets `bugbot_clean_at` and transitions to **`phase =
698
+ BUGTEAM`**), the next action must be the **second audit in the same tick** per
699
+ §Second-audit execution — never a redundant `bugbot run`. If merged findings
700
+ require commits, continue with **Fix protocol** per [Fix
701
+ protocol](#fix-protocol) (`Task` with `generalPurpose` and the clean-coder
702
+ **Read** preamble). If **`Task`** cannot be invoked, STOP and notify the user.
396
703
 
397
704
  #### `phase == BUGTEAM`
398
705
 
399
706
  a. Run **bugteam** (second audit) on the current PR.
400
707
 
401
- - **When `Skill` is invokable** (see [Pacing workflows](#pacing-workflows-load-exactly-one) tool-inventory rules — same session): invoke **bugteam** with the `Skill` tool. Path A vs Path B is selected **inside** bugteam per [bugteam Path routing](../bugteam/SKILL.md#path-routing-mandatory-first-branch); pr-converge does not branch on `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` here.
708
+ - **When `Skill` is invokable** (see [Pacing
709
+ workflows](#pacing-workflows-load-exactly-one) tool-inventory rules — same
710
+ session): invoke **bugteam** with the `Skill` tool. Path A vs Path B is
711
+ selected **inside** bugteam per [bugteam Path
712
+ routing](../bugteam/SKILL.md#path-routing-mandatory-first-branch);
713
+ pr-converge does not branch on `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` here.
402
714
 
403
715
  ```
404
- Skill({skill: "bugteam", args: "https://github.com/<OWNER>/<REPO>/pull/<NUMBER>"})
716
+ Skill({skill: "bugteam", args:
717
+ "https://github.com/<OWNER>/<REPO>/pull/<NUMBER>"})
405
718
  ```
406
719
 
407
- Wait for completion; capture exit and final summary (convergence vs findings) for Step **(c)**.
720
+ Wait for completion; capture exit and final summary (convergence vs findings)
721
+ for Step **(c)**.
408
722
 
409
- - **When `Skill` is not invokable** (typical `Task` teammate): that worker executes **bugteam** by reading [`../bugteam/SKILL.md`](../bugteam/SKILL.md) and, if Path B applies, [`../bugteam/reference/workflow-path-b-task-harness.md`](../bugteam/reference/workflow-path-b-task-harness.md) — same **`code-quality-agent`** / **`clean-coder`** loop and gates as Path A; only harness steps differ per that workflow file.
723
+ - **When `Skill` is not invokable** (typical `Task` teammate): that worker
724
+ executes **bugteam** by reading
725
+ [`../bugteam/SKILL.md`](../bugteam/SKILL.md) and, if Path B applies,
726
+ [path-b doc][path-b] — same **`code-quality-agent`** / **`clean-coder`**
727
+ loop and gates as Path A; only harness steps differ per that workflow file.
410
728
 
411
- b. **Re-resolve current HEAD now** because the second audit may have pushed commits during its run. The `current_head` from Step 1 is potentially
412
- stale at this point:
729
+ b. **Re-resolve current HEAD now** because the second audit may have pushed
730
+ commits during its run. The `current_head` from Step 1 is potentially stale at
731
+ this point:
413
732
  ```bash
414
- new_head=$(python "${CLAUDE_SKILL_DIR}/scripts/resolve_pr_head.py" \
415
- --owner <OWNER> --repo <REPO> --number <NUMBER>)
733
+ new_head=$(python "${CLAUDE_SKILL_DIR}/scripts/resolve_pr_head.py" \
734
+ --owner <OWNER> --repo <REPO> --number <NUMBER>)
416
735
  ```
417
- If `new_head != current_head`, set `current_head = new_head` AND set `bugbot_clean_at = null`. The new commits invalidate bugbot's prior clean.
418
-
419
- c. Inspect the bugteam outcome. It reports either `convergence (zero findings)` or a list of unfixed findings with file:line (same semantics for both bugteam workflows).
420
-
421
- d. Decide based on the (post-second-audit) state order matters; check pushed-during-second-audit FIRST so a convergence report against a stale HEAD never falsely terminates:
422
- - **Second audit pushed during this tick (i.e., `bugbot_clean_at` was just reset to `null` in step b):** Re-trigger bugbot in this same tick (Step 3) so the new HEAD enters bugbot's queue immediately, transition `phase = BUGBOT`, schedule next wakeup, return.
423
- - **Second audit reports convergence AND `bugbot_clean_at == current_head` (no push during this tick):** This is back-to-back clean — necessary, but not sufficient on its own. Run the **§Convergence gates** below to clear the Copilot-findings, mergeability, and post-convergence Copilot-request gates. Only when all four gates pass do you mark the PR ready and **omit loop pacing** per the **Convergence** section of whichever pacing workflow was active.
424
- - **Second audit reports convergence BUT `bugbot_clean_at != current_head` (no push during this tick):** Transition `phase = BUGBOT`, schedule next wakeup, return.
425
- - **Second audit reports findings without committing fixes:** apply the **Fix protocol** below; **Step 3** on the new HEAD runs after fix handoff per §Multi-PR or in-tick for single-PR. Transition `phase = BUGBOT`, schedule next wakeup, return.
736
+ If `new_head != current_head`, set `current_head = new_head` AND set
737
+ `bugbot_clean_at = null`. The new commits invalidate bugbot's prior clean.
738
+
739
+ c. Inspect the bugteam outcome. It reports either `convergence (zero findings)`
740
+ or a list of unfixed findings with file:line (same semantics for both bugteam
741
+ workflows).
742
+
743
+ d. Decide based on the (post-second-audit) state order matters; check
744
+ pushed-during-second-audit FIRST so a convergence report against a stale HEAD
745
+ never falsely terminates:
746
+ - **Second audit pushed during this tick (i.e., `bugbot_clean_at` was just
747
+ reset to `null` in step b):** Re-trigger bugbot in this same tick (Step 3)
748
+ so the new HEAD enters bugbot's queue immediately, transition `phase =
749
+ BUGBOT`, schedule next wakeup, return.
750
+ - **Second audit reports convergence AND `bugbot_clean_at == current_head`
751
+ (no push during this tick):** This is back-to-back clean — necessary, but
752
+ not sufficient on its own. Run the **§Convergence gates** below to clear
753
+ the Copilot-findings, mergeability, and post-convergence Copilot-request
754
+ gates. Only when all four gates pass do you mark the PR ready and **omit
755
+ loop pacing** per the **Convergence** section of whichever pacing workflow
756
+ was active.
757
+ - **Second audit reports convergence BUT `bugbot_clean_at != current_head`
758
+ (no push during this tick):** Transition `phase = BUGBOT`, schedule next
759
+ wakeup, return.
760
+ - **Second audit reports findings without committing fixes:** apply the **Fix
761
+ protocol** below; **Step 3** on the new HEAD runs after fix handoff per
762
+ §Multi-PR or in-tick for single-PR. Transition `phase = BUGBOT`, schedule
763
+ next wakeup, return.
426
764
 
427
765
  ### Convergence gates
428
766
 
429
- Run **only** when Step 2 BUGTEAM reports `convergence (zero findings)` AND `bugbot_clean_at == current_head` (back-to-back clean) AND no push occurred during the bugteam tick. The gates run in order; the first one that fails determines next-tick behavior. Only after all four gates pass do you mark the PR ready.
767
+ Run **only** when Step 2 BUGTEAM reports `convergence (zero findings)` AND
768
+ `bugbot_clean_at == current_head` (back-to-back clean) AND no push occurred
769
+ during the bugteam tick. The gates run in order; the first one that fails
770
+ determines next-tick behavior. Only after all four gates pass do you mark the PR
771
+ ready.
430
772
 
431
773
  #### (a) Copilot findings gate
432
774
 
433
- Fetch the latest Copilot reviewer (`copilot-pull-request-reviewer[bot]`) review on the PR and any inline comments anchored to the most recent Copilot review on `current_head`:
775
+ Fetch the latest Copilot reviewer (`copilot-pull-request-reviewer[bot]`) review
776
+ on the PR and any inline comments anchored to the most recent Copilot review on
777
+ `current_head`:
434
778
 
435
779
  ```bash
436
780
  python "${CLAUDE_SKILL_DIR}/scripts/fetch_copilot_reviews.py" \
437
- --owner <OWNER> --repo <REPO> --number <NUMBER>
781
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
438
782
 
439
783
  python "${CLAUDE_SKILL_DIR}/scripts/fetch_copilot_inline_comments.py" \
440
- --owner <OWNER> --repo <REPO> --number <NUMBER> --commit "$current_head"
784
+ --owner <OWNER> --repo <REPO> --number <NUMBER> --commit "$current_head"
441
785
  ```
442
786
 
443
- Decide (the four branches below cover every input combination — match the first whose predicate holds):
444
-
445
- - **A Copilot review exists at `current_head` AND its `classification == "dirty"` AND inline comments returned non-empty for the matching `pull_request_review_id`:** Treat as a Fix protocol input (same shape as bugbot dirty). Read every inline finding, apply the **Fix protocol** below (TDD test → production fix → push → reply inline on each thread), reset `bugbot_clean_at = null` AND `copilot_clean_at = null`, transition `phase = BUGBOT`, run **Step 3** (`trigger_bugbot.py`) on the new HEAD, schedule next wakeup, return. The full back-to-back-clean cycle plus all four gates must hold again on the new HEAD before convergence.
446
- - **A Copilot review exists at `current_head` AND its `classification == "dirty"` AND inline comments are empty for the matching `pull_request_review_id`:** Copilot posted findings only in the review body (`CHANGES_REQUESTED` or `COMMENTED` with non-empty body and no inline threads). Treat the review body as the finding source: parse the body for actionable findings, apply the **Fix protocol** using the body excerpts in place of inline threads (TDD test → production fix → push). Post the reply as a top-level review reply on the Copilot review (not an inline-thread reply, because no inline threads exist) acknowledging the addressed findings and citing the new HEAD SHA. Reset `bugbot_clean_at = null` AND `copilot_clean_at = null`, transition `phase = BUGBOT`, run **Step 3** (`trigger_bugbot.py`) on the new HEAD, schedule next wakeup, return. Convergence still requires the full back-to-back-clean cycle on the new HEAD.
447
- - **A Copilot review exists at `current_head` AND its `classification == "clean"` (state `APPROVED`):** Set `copilot_clean_at = current_head`. Continue to gate **(b)**.
448
- - **No Copilot review has been posted on `current_head` yet:** Skip — gate **(c)** below issues the proactive request. Continue to gate **(b)**.
787
+ Decide (the four branches below cover every input combination — match the first
788
+ whose predicate holds):
789
+
790
+ - **A Copilot review exists at `current_head` AND its `classification ==
791
+ "dirty"` AND inline comments returned non-empty for the matching
792
+ `pull_request_review_id`:** Treat as a Fix protocol input (same shape as
793
+ bugbot dirty). Read every inline finding, apply the **Fix protocol** below
794
+ (TDD test → production fix → push → reply inline on each thread), reset
795
+ `bugbot_clean_at = null` AND `copilot_clean_at = null`, transition `phase =
796
+ BUGBOT`, run **Step 3** (`trigger_bugbot.py`) on the new HEAD, schedule next
797
+ wakeup, return. The full back-to-back-clean cycle plus all four gates must
798
+ hold again on the new HEAD before convergence.
799
+ - **A Copilot review exists at `current_head` AND its `classification ==
800
+ "dirty"` AND inline comments are empty for the matching
801
+ `pull_request_review_id`:** Copilot posted findings only in the review body
802
+ (`CHANGES_REQUESTED` or `COMMENTED` with non-empty body and no inline
803
+ threads). Treat the review body as the finding source: parse the body for
804
+ actionable findings, apply the **Fix protocol** using the body excerpts in
805
+ place of inline threads (TDD test → production fix → push). Post the reply as
806
+ a top-level review reply on the Copilot review (not an inline-thread reply,
807
+ because no inline threads exist) acknowledging the addressed findings and
808
+ citing the new HEAD SHA. Reset `bugbot_clean_at = null` AND `copilot_clean_at
809
+ = null`, transition `phase = BUGBOT`, run **Step 3** (`trigger_bugbot.py`) on
810
+ the new HEAD, schedule next wakeup, return. Convergence still requires the
811
+ full back-to-back-clean cycle on the new HEAD.
812
+ - **A Copilot review exists at `current_head` AND its `classification ==
813
+ "clean"` (state `APPROVED`):** Set `copilot_clean_at = current_head`. Continue
814
+ to gate **(b)**.
815
+ - **No Copilot review has been posted on `current_head` yet:** Skip — gate
816
+ **(c)** below issues the proactive request. Continue to gate **(b)**.
449
817
 
450
818
  #### (b) Mergeability gate
451
819
 
@@ -453,249 +821,491 @@ Resolve the PR's mergeability state:
453
821
 
454
822
  ```bash
455
823
  python "${CLAUDE_SKILL_DIR}/scripts/check_pr_mergeability.py" \
456
- --owner <OWNER> --repo <REPO> --number <NUMBER>
824
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
457
825
  ```
458
826
 
459
- Output is `{"mergeable", "mergeStateStatus", "headRefOid"}`. Persist `mergeStateStatus` into `merge_state_status` (state line or `state.json`). Decide:
460
-
461
- - **`mergeStateStatus == "CLEAN"` AND `mergeable == "MERGEABLE"`:** Continue to gate **(c)**.
462
- - **`mergeStateStatus == "DIRTY"` (or `mergeable == "CONFLICTING"`):** Do **not** mark ready. Invoke the **`rebase`** skill ([`../rebase/SKILL.md`](../rebase/SKILL.md)) and follow its Phase 1–4 protocol against the PR's base ref. After a successful rebase + force-with-lease push, the new HEAD invalidates every prior clean state — reset `bugbot_clean_at = null`, `copilot_clean_at = null`, `merge_state_status = null`, transition `phase = BUGBOT`, run **Step 3** (`trigger_bugbot.py`) on the new HEAD, schedule next wakeup, return. The convergence loop re-runs from scratch on the new HEAD.
463
- - **`mergeStateStatus` is `BLOCKED`, `BEHIND`, or `UNKNOWN` for non-conflict reasons (e.g., required checks pending, branch behind base without conflicts that GitHub cannot auto-resolve):** This is a **hard blocker** per §Stop conditions — do not invent a fix. Report the specific `mergeStateStatus`, omit loop pacing per the active workflow, stop the AHK auto-typer if that path was in use.
827
+ Output is `{"mergeable", "mergeStateStatus", "headRefOid"}`. Persist
828
+ `mergeStateStatus` into `merge_state_status` (state line or `state.json`).
829
+ Decide:
830
+
831
+ - **`mergeStateStatus == "CLEAN"` AND `mergeable == "MERGEABLE"`:** Continue to
832
+ gate **(c)**.
833
+ - **`mergeStateStatus == "DIRTY"` (or `mergeable == "CONFLICTING"`):** Do
834
+ **not** mark ready. Invoke the **`rebase`** skill
835
+ ([`../rebase/SKILL.md`](../rebase/SKILL.md)) and follow its Phase 1–4 protocol
836
+ against the PR's base ref. After a successful rebase + force-with-lease push,
837
+ the new HEAD invalidates every prior clean state — reset `bugbot_clean_at =
838
+ null`, `copilot_clean_at = null`, `merge_state_status = null`, transition
839
+ `phase = BUGBOT`, run **Step 3** (`trigger_bugbot.py`) on the new HEAD,
840
+ schedule next wakeup, return. The convergence loop re-runs from scratch on the
841
+ new HEAD.
842
+ - **`mergeStateStatus` is `BLOCKED`, `BEHIND`, or `UNKNOWN` for non-conflict
843
+ reasons (e.g., required checks pending, branch behind base without conflicts
844
+ that GitHub cannot auto-resolve):** This is a **hard blocker** per §Stop
845
+ conditions — do not invent a fix. Report the specific `mergeStateStatus`, omit
846
+ loop pacing per the active workflow, stop the AHK auto-typer if that path was
847
+ in use.
464
848
 
465
849
  #### (c) Post-convergence Copilot review request
466
850
 
467
- Once gates (a) and (b) both pass with the strong outcomes (Copilot already clean at `current_head` *or* no Copilot review on `current_head` yet, AND `mergeStateStatus == "CLEAN"`), request a Copilot review:
851
+ Once gates (a) and (b) both pass with the strong outcomes (Copilot already clean
852
+ at `current_head` *or* no Copilot review on `current_head` yet, AND
853
+ `mergeStateStatus == "CLEAN"`), request a Copilot review:
468
854
 
469
855
  ```bash
470
856
  python "${CLAUDE_SKILL_DIR}/scripts/request_copilot_review.py" \
471
- --owner <OWNER> --repo <REPO> --number <NUMBER>
857
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
472
858
  ```
473
859
 
474
- The reviewer ID literal `copilot-pull-request-reviewer[bot]` (with the `[bot]` suffix) is load-bearing — `Copilot`, `copilot`, and `github-copilot` all silently no-op per [`../copilot-review/SKILL.md`](../copilot-review/SKILL.md). After the request, schedule the next wakeup (one ScheduleWakeup cycle, or AHK tick) and return — the next tick checks Copilot's response.
475
-
476
- When the next tick fires and `phase == BUGTEAM` with all prior state preserved, re-run gate **(a)** as the first thing. Decide:
477
-
478
- - **Copilot review at `current_head` is `clean` (state `APPROVED`):** Set `copilot_clean_at = current_head`. Mark the PR ready (`mark_pr_ready.py`), report convergence (see §(d) below for the report format), terminate per **§Stop conditions / Convergence**.
479
- - **Copilot review at `current_head` is `dirty`:** Still mark the current PR ready (this is the documented "we still allow it to be 'clean'" rule — the four gates are bugbot CLEAN ∧ bugteam CLEAN ∧ `mergeStateStatus == CLEAN` ∧ either Copilot CLEAN at HEAD or a follow-up PR captures Copilot findings). Before terminating, build a markdown findings file from `fetch_copilot_inline_comments.py` output (one checklist item per finding with file:line and excerpted body), then open a follow-up draft PR off `current_head`:
860
+ The reviewer ID literal `copilot-pull-request-reviewer[bot]` (with the `[bot]`
861
+ suffix) is load-bearing — `Copilot`, `copilot`, and `github-copilot` all
862
+ silently no-op per [`../copilot-review/SKILL.md`](../copilot-review/SKILL.md).
863
+ After the request, schedule the next wakeup (one ScheduleWakeup cycle, or AHK
864
+ tick) and return the next tick checks Copilot's response.
865
+
866
+ When the next tick fires and `phase == BUGTEAM` with all prior state preserved,
867
+ re-run gate **(a)** as the first thing. Decide:
868
+
869
+ - **Copilot review at `current_head` is `clean` (state `APPROVED`):** Set
870
+ `copilot_clean_at = current_head`. Mark the PR ready (`mark_pr_ready.py`),
871
+ report convergence (see §(d) below for the report format), terminate per
872
+ **§Stop conditions / Convergence**.
873
+ - **Copilot review at `current_head` is `dirty`:** Still mark the current PR
874
+ ready (this is the documented "we still allow it to be 'clean'" rule — the
875
+ four gates are bugbot CLEAN ∧ bugteam CLEAN ∧ `mergeStateStatus == CLEAN` ∧
876
+ either Copilot CLEAN at HEAD or a follow-up PR captures Copilot findings).
877
+ Before terminating, build a markdown findings file from
878
+ `fetch_copilot_inline_comments.py` output (one checklist item per finding with
879
+ file:line and excerpted body), then open a follow-up draft PR off
880
+ `current_head`:
480
881
 
481
882
  ```bash
482
- python "${CLAUDE_SKILL_DIR}/scripts/open_followup_copilot_pr.py" \
483
- --owner <OWNER> --repo <REPO> \
484
- --parent-number <NUMBER> --head "$current_head" \
485
- --findings-file <PATH_TO_FINDINGS_MD>
883
+ python "${CLAUDE_SKILL_DIR}/scripts/open_followup_copilot_pr.py" \
884
+ --owner <OWNER> --repo <REPO> \
885
+ --parent-number <NUMBER> --head "$current_head" \
886
+ --findings-file <PATH_TO_FINDINGS_MD>
486
887
  ```
487
888
 
488
- The follow-up branch name is `chore/copilot-followup-<NUMBER>-<short_sha>` and the PR title is `chore: address Copilot findings from PR #<NUMBER>`. Queue `/pr-converge` on the new PR for the user to invoke (do **not** inline-spawn another converge loop in the same session). Report **both** PR URLs to the user. The current PR's convergence is final at the original HEAD; the new PR runs its own convergence cycle that itself satisfies all four gates.
889
+ The follow-up branch name is `chore/copilot-followup-<NUMBER>-<short_sha>` and
890
+ the PR title is `chore: address Copilot findings from PR #<NUMBER>`. Queue
891
+ `/pr-converge` on the new PR for the user to invoke (do **not** inline-spawn
892
+ another converge loop in the same session). Report **both** PR URLs to the user.
893
+ The current PR's convergence is final at the original HEAD; the new PR runs its
894
+ own convergence cycle that itself satisfies all four gates.
489
895
 
490
- - **No Copilot review has appeared at `current_head` yet (still propagating):** Schedule one more wakeup cycle (270s when `ScheduleWakeup` is available, AHK cadence otherwise) and re-check on the next tick. After three consecutive empty waits, escalate as a hard blocker per §Stop conditions (Copilot has not produced a review on the requested commit despite the request).
896
+ - **No Copilot review has appeared at `current_head` yet (still propagating):**
897
+ Schedule one more wakeup cycle (270s when `ScheduleWakeup` is available, AHK
898
+ cadence otherwise) and re-check on the next tick. After three consecutive
899
+ empty waits, escalate as a hard blocker per §Stop conditions (Copilot has not
900
+ produced a review on the requested commit despite the request).
491
901
 
492
902
  #### (d) Mark ready and report
493
903
 
494
- Only when all four gates pass — bugbot CLEAN ∧ bugteam CLEAN ∧ `mergeStateStatus == "CLEAN"` ∧ Copilot CLEAN at HEAD (or the post-convergence request returned dirty and the follow-up PR is open) — run:
904
+ Only when all four gates pass — bugbot CLEAN ∧ bugteam CLEAN ∧ `mergeStateStatus
905
+ == "CLEAN"` ∧ Copilot CLEAN at HEAD (or the post-convergence request returned
906
+ dirty and the follow-up PR is open) — run:
495
907
 
496
908
  ```bash
497
909
  python "${CLAUDE_SKILL_DIR}/scripts/mark_pr_ready.py" \
498
- --owner <OWNER> --repo <REPO> --number <NUMBER>
910
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
499
911
  ```
500
912
 
501
- When scripts are unavailable, `gh pr ready <NUMBER> --repo <OWNER>/<REPO>` is an equivalent human-visible outcome. When `state.json` is in use, append the convergence row to `<TMPDIR>/pr-converge-<session_id>/converged.log` per §Memory; when not, skip file append. Report: `PR #<NUMBER> converged: bugbot CLEAN at <SHA>, bugteam CLEAN at <SHA>, mergeStateStatus CLEAN, copilot <CLEAN|FOLLOWUP_PR_URL>; marked ready for review`. **Omit loop pacing** per the **Convergence** section of whichever pacing workflow was active.
913
+ When scripts are unavailable, `gh pr ready <NUMBER> --repo <OWNER>/<REPO>` is an
914
+ equivalent human-visible outcome. When `state.json` is in use, append the
915
+ convergence row to `<TMPDIR>/pr-converge-<session_id>/converged.log` per
916
+ §Memory; when not, skip file append. Report: `PR #<NUMBER> converged: bugbot
917
+ CLEAN at <SHA>, bugteam CLEAN at <SHA>, mergeStateStatus CLEAN, copilot
918
+ <CLEAN|FOLLOWUP_PR_URL>; marked ready for review`. **Omit loop pacing** per the
919
+ **Convergence** section of whichever pacing workflow was active.
502
920
 
503
921
  ### Step 3: Re-trigger bugbot
504
922
 
505
- Used in Step 2 BUGBOT branch 1, in Step 2 BUGTEAM branch 1, and in the Fix protocol. Prefer the portable script (temp body file, `gh pr comment --body-file`):
923
+ Used in Step 2 BUGBOT branch 1, in Step 2 BUGTEAM branch 1, and in the Fix
924
+ protocol. Prefer the portable script (temp body file, `gh pr comment
925
+ --body-file`):
506
926
 
507
927
  ```bash
508
928
  python "${CLAUDE_SKILL_DIR}/scripts/trigger_bugbot.py" \
509
- --owner <OWNER> --repo <REPO> --number <NUMBER>
929
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
510
930
  ```
511
931
 
512
932
  **Bundled PowerShell alternative** (same gh-body-file contract):
513
933
 
514
934
  ```bash
515
935
  POST_BUGBOT_RUN="$HOME/.claude/skills/pr-converge/scripts/post-bugbot-run.ps1"
516
- pwsh -NoProfile -ExecutionPolicy Bypass -File "$POST_BUGBOT_RUN" "https://github.com/<OWNER>/<REPO>/pull/<NUMBER>"
936
+ pwsh -NoProfile -ExecutionPolicy Bypass -File "$POST_BUGBOT_RUN" \
937
+ "https://github.com/<OWNER>/<REPO>/pull/<NUMBER>"
517
938
  ```
518
939
 
519
940
  Shorthand `owner/repo#number`:
520
941
 
521
942
  ```bash
522
943
  POST_BUGBOT_RUN="$HOME/.claude/skills/pr-converge/scripts/post-bugbot-run.ps1"
523
- pwsh -NoProfile -ExecutionPolicy Bypass -File "$POST_BUGBOT_RUN" "<OWNER>/<REPO>#<NUMBER>"
944
+ pwsh -NoProfile -ExecutionPolicy Bypass -File "$POST_BUGBOT_RUN" \
945
+ "<OWNER>/<REPO>#<NUMBER>"
524
946
  ```
525
947
 
526
948
  Explicit repository and number:
527
949
 
528
950
  ```bash
529
951
  POST_BUGBOT_RUN="$HOME/.claude/skills/pr-converge/scripts/post-bugbot-run.ps1"
530
- pwsh -NoProfile -ExecutionPolicy Bypass -File "$POST_BUGBOT_RUN" -Repository "<OWNER>/<REPO>" -Number <NUMBER>
952
+ pwsh -NoProfile -ExecutionPolicy Bypass -File "$POST_BUGBOT_RUN" \
953
+ -Repository "<OWNER>/<REPO>" -Number <NUMBER>
531
954
  ```
532
955
 
533
- `bugbot run` is empirically the only re-trigger Cursor Bugbot recognizes; alternative phrasings (`re-review`, `bugbot please`, etc.) silently no-op.
534
-
535
- If you cannot run the scripts above, use the Write tool to a temp file, then `gh pr comment <NUMBER> --repo <OWNER>/<REPO> --body-file <path>` yourself.
536
- The body file must contain exactly the literal phrase `bugbot run` followed by a newline empirically the only re-trigger Cursor Bugbot
537
- recognizes; alternative phrasings (`re-review`, `bugbot please`, etc.) silently no-op.
538
-
539
- **Gotcha (duplicate `bugbot run` while a review is already queued):** Do not post another `bugbot run` when Bugbot has already picked up the latest trigger. On GitHub, the bugbot review signal is an **eyes** (`:eyes:`) reaction on the **most recent** `bugbot run` PR comment (Bugbot acknowledging the job). When that reaction is present, skip Step 3 for this wait cycle — a second comment spams the PR and can confuse tick logic; wait for the review to finish or for `HEAD` to change before re-triggering per Step 2.
540
-
541
- **Default loop:** After each tick, run **Step 4** whenever pacing still applies — meaning convergence has not yet omitted pacing and no **Stop conditions** branch has omitted pacing for this tick. That rule covers the **first** user-typed **`/pr-converge`** the same as later wakeups or `continue` ticks: each invocation completes one tick, then **Step 4** loads the pacing workflow and schedules the next entry when the workflow says to. For **`ScheduleWakeup`**, set **`prompt: "/pr-converge"`** by default (`workflows/schedule-wakeup-loop.md`); set **`prompt: "/loop /pr-converge"`** when the harness requires the `/loop` wrapper for the next firing to execute.
542
-
543
- When **`ScheduleWakeup` is unavailable**, run **Step 4** on the AHK workflow row; that path keeps **`/pr-converge`** on loop-until-done semantics per `workflows/ahk-auto-continue-loop.md`. When **no** pacing mechanism is active (no `ScheduleWakeup` tool and AHK not started per that file), end the tick with **return** only — there is nothing to schedule. Elsewhere, **schedule next wakeup, return** means run **Step 4** below; when Step 4 schedules nothing, treat that phrase as **return** only.
544
-
545
- **Gotcha (Bugbot found errors, but a redundant `bugbot run` instead of a fix push):** When the latest Bugbot review on `current_head` still has
546
- **unaddressed findings** (inline threads and/or a non-clean review body), **do not** post another `bugbot run` on that same SHA as a
547
- substitute for fixing the code. A second trigger without a new commit cannot resolve the findings it only duplicates noise and breaks tick
548
- expectations. Follow the **Fix protocol** end-to-end: spawn **`Task`** with **`subagent_type: "generalPurpose"`** and the clean-coder **Read** preamble from [Fix protocol](#fix-protocol) (never ad-hoc shell or a bare `generalPurpose` prompt for production edits), **commit and push** with mandatory pre-commit and pre-push hook validation (full stop and notify the user if hooks did not
549
- run or were bypassed), reply inline on each thread, **then** Step 3 `bugbot run` against the new SHA.
956
+ `bugbot run` is empirically the only re-trigger Cursor Bugbot recognizes;
957
+ alternative phrasings (`re-review`, `bugbot please`, etc.) silently no-op.
958
+
959
+ If you cannot run the scripts above, use the Write tool to a temp file, then `gh
960
+ pr comment <NUMBER> --repo <OWNER>/<REPO> --body-file <path>` yourself. The body
961
+ file must contain exactly the literal phrase `bugbot run` followed by a newline
962
+ empirically the only re-trigger Cursor Bugbot recognizes; alternative
963
+ phrasings (`re-review`, `bugbot please`, etc.) silently no-op.
964
+
965
+ **Gotcha (duplicate `bugbot run` while a review is already queued):** Do not
966
+ post another `bugbot run` when Bugbot has already picked up the latest trigger.
967
+ On GitHub, the bugbot review signal is an **eyes** (`:eyes:`) reaction on the
968
+ **most recent** `bugbot run` PR comment (Bugbot acknowledging the job). When
969
+ that reaction is present, skip Step 3 for this wait cycle a second comment
970
+ spams the PR and can confuse tick logic; wait for the review to finish or for
971
+ `HEAD` to change before re-triggering per Step 2.
972
+
973
+ **Default loop:** After each tick, run **Step 4** whenever pacing still applies
974
+ — meaning convergence has not yet omitted pacing and no **Stop conditions**
975
+ branch has omitted pacing for this tick. That rule covers the **first**
976
+ user-typed **`/pr-converge`** the same as later wakeups or `continue` ticks:
977
+ each invocation completes one tick, then **Step 4** loads the pacing workflow
978
+ and schedules the next entry when the workflow says to. For
979
+ **`ScheduleWakeup`**, set **`prompt: "/pr-converge"`** by default
980
+ (`workflows/schedule-wakeup-loop.md`); set **`prompt: "/loop /pr-converge"`**
981
+ when the harness requires the `/loop` wrapper for the next firing to execute.
982
+
983
+ When **`ScheduleWakeup` is unavailable**, run **Step 4** on the AHK workflow
984
+ row; that path keeps **`/pr-converge`** on loop-until-done semantics per
985
+ `workflows/ahk-auto-continue-loop.md`. When **no** pacing mechanism is active
986
+ (no `ScheduleWakeup` tool and AHK not started per that file), end the tick with
987
+ **return** only — there is nothing to schedule. Elsewhere, **schedule next
988
+ wakeup, return** means run **Step 4** below; when Step 4 schedules nothing,
989
+ treat that phrase as **return** only.
990
+
991
+ **Gotcha (Bugbot found errors, but a redundant `bugbot run` instead of a fix
992
+ push):** When the latest Bugbot review on `current_head` still has **unaddressed
993
+ findings** (inline threads and/or a non-clean review body), **do not** post
994
+ another `bugbot run` on that same SHA as a substitute for fixing the code. A
995
+ second trigger without a new commit cannot resolve the findings — it only
996
+ duplicates noise and breaks tick expectations. Follow the **Fix protocol**
997
+ end-to-end: spawn **`Task`** with **`subagent_type: "generalPurpose"`** and the
998
+ clean-coder **Read** preamble from [Fix protocol](#fix-protocol) (never ad-hoc
999
+ shell or a bare `generalPurpose` prompt for production edits), **commit and
1000
+ push** with mandatory pre-commit and pre-push hook validation (full stop and
1001
+ notify the user if hooks did not run or were bypassed), reply inline on each
1002
+ thread, **then** Step 3 `bugbot run` against the new SHA.
550
1003
 
551
1004
  ### Step 4: Loop pacing
552
1005
 
553
- **`ScheduleWakeup` field hints** (when not using the workflow files — not recommended; prefer [Pacing workflows](#pacing-workflows-load-exactly-one)):
554
-
555
- - `delaySeconds: 270` whenever bugbot was just re-triggered (whether by Step 3 directly, by **Step 3** after a fix via the follow-up agent chain, or by BUGTEAM branch 1's same-tick re-trigger). Bugbot finishes a review in 1–4 minutes, so 270s stays under the 5-minute prompt-cache TTL while giving a margin past bugbot's typical upper bound. The single exception is the BUGBOT inline-lag branch, which uses `delaySeconds: 60` because no re-trigger fired and the only thing being awaited is GitHub's inline-comments API catching up.
556
- - `reason`: one short sentence on what is being awaited, including the current `phase` and `bugbot_clean_at` SHA when set.
557
- - `prompt: "/pr-converge"` default; re-enters this skill on the next firing with default loop semantics. If the harness requires the `/loop` wrapper for wakeups, `prompt: "/loop /pr-converge"` is equivalent (`workflows/schedule-wakeup-loop.md`).
558
-
559
- Throughout Step 2 and the Fix protocol, **schedule next wakeup, return** means: load the correct pacing workflow (see
560
- [Pacing workflows](#pacing-workflows-load-exactly-one)), then execute **Step 4** exactly as that file specifies (pace the next tick, then
561
- return).
562
-
563
- **Entry paths** include `/pr-converge`, `/loop /pr-converge` when the harness uses that wrapper, an AHK `continue` tick, or a `ScheduleWakeup` whose `prompt` is `/pr-converge` or `/loop /pr-converge` per the schedule-wakeup workflow.
564
-
565
- **On convergence:** apply the **Convergence** section of the **same** pacing workflow file you are using for this session (omit wakeups / stop
566
- AHK per that file).
1006
+ **`ScheduleWakeup` field hints** (when not using the workflow files — not
1007
+ recommended; prefer [Pacing workflows](#pacing-workflows-load-exactly-one)):
1008
+
1009
+ - `delaySeconds: 270` whenever bugbot was just re-triggered (whether by Step 3
1010
+ directly, by **Step 3** after a fix via the follow-up agent chain, or by
1011
+ BUGTEAM branch 1's same-tick re-trigger). Bugbot finishes a review in 1–4
1012
+ minutes, so 270s stays under the 5-minute prompt-cache TTL while giving a
1013
+ margin past bugbot's typical upper bound. The single exception is the BUGBOT
1014
+ inline-lag branch, which uses `delaySeconds: 60` because no re-trigger fired
1015
+ and the only thing being awaited is GitHub's inline-comments API catching up.
1016
+ - `reason`: one short sentence on what is being awaited, including the current
1017
+ `phase` and `bugbot_clean_at` SHA when set.
1018
+ - `prompt: "/pr-converge"` default; re-enters this skill on the next firing
1019
+ with default loop semantics. If the harness requires the `/loop` wrapper for
1020
+ wakeups, `prompt: "/loop /pr-converge"` is equivalent
1021
+ (`workflows/schedule-wakeup-loop.md`).
1022
+
1023
+ Throughout Step 2 and the Fix protocol, **schedule next wakeup, return** means:
1024
+ load the correct pacing workflow (see [Pacing
1025
+ workflows](#pacing-workflows-load-exactly-one)), then execute **Step 4** exactly
1026
+ as that file specifies (pace the next tick, then return).
1027
+
1028
+ **Entry paths** include `/pr-converge`, `/loop /pr-converge` when the harness
1029
+ uses that wrapper, an AHK `continue` tick, or a `ScheduleWakeup` whose `prompt`
1030
+ is `/pr-converge` or `/loop /pr-converge` per the schedule-wakeup workflow.
1031
+
1032
+ **On convergence:** apply the **Convergence** section of the **same** pacing
1033
+ workflow file you are using for this session (omit wakeups / stop AHK per that
1034
+ file).
567
1035
 
568
1036
  ## Fix protocol
569
1037
 
570
1038
  ### Cursor `Task` registry (single-PR / Cursor host)
571
1039
 
572
- Cursor's **`Task`** tool validates `subagent_type` against a **fixed enum**; **`"clean-coder"` is not a valid value**. When **no** `state.json` is in use
573
- (typical single-PR Cursor tick), **production edits** use **`Task`** with **`subagent_type: "generalPurpose"`** and the clean-coder contract in the **`prompt`**
574
- per the **Implement** bullet below - not a separate `clean-coder` spawn.
575
-
576
- The fix protocol is executed by a **`clean-coder` teammate** when **`state.json`** drives the session (§Multi-PR orchestration model), or by the **`Task` + `generalPurpose`** path in the **main session** when **no** `state.json` is in use (typical single-PR Cursor). The orchestrator **never** performs production edits inline in multi-PR mode. Pre-commit and pre-push hook handling is governed by §Ground rules and the gates below.
577
-
578
- **Multi-PR (`state.json`) teammate obligations** (in addition to TDD, commit, push):
579
-
580
- - Replies inline on each addressed finding thread via `reply_to_inline_comment.py` (what changed and the commit identifier), matching §Audit result → fix worker step 4 — **before** writing `state.json` and going idle.
581
- - Writes `last_action: "fix_pushed"`, `current_head: <new SHA>`, `bugbot_clean_at: null`, `phase: "BUGBOT"`, `status: "awaiting_bugbot"`, and `last_updated` (ISO-8601 UTC) to `state.json` (per §Concurrency).
582
- - Goes idle. The orchestrator spawns the follow-up `general-purpose` agent for bugbot trigger and monitoring.
583
-
584
- **The orchestrator does not reply to inline comments, does not trigger bugbot, and does not read repository source files during the fix phase** when the multi-PR model is active.
1040
+ Cursor's **`Task`** tool validates `subagent_type` against a **fixed enum**;
1041
+ **`"clean-coder"` is not a valid value**. When **no** `state.json` is in use
1042
+ (typical single-PR Cursor tick), **production edits** use **`Task`** with
1043
+ **`subagent_type: "generalPurpose"`** and the clean-coder contract in the
1044
+ **`prompt`** per the **Implement** bullet below - not a separate `clean-coder`
1045
+ spawn.
1046
+
1047
+ The fix protocol is executed by a **`clean-coder` teammate** when
1048
+ **`state.json`** drives the session (§Multi-PR orchestration model), or by the
1049
+ **`Task` + `generalPurpose`** path in the **main session** when **no**
1050
+ `state.json` is in use (typical single-PR Cursor). The orchestrator **never**
1051
+ performs production edits inline in multi-PR mode. Pre-commit and pre-push hook
1052
+ handling is governed by §Ground rules and the gates below.
1053
+
1054
+ **Multi-PR (`state.json`) teammate obligations** (in addition to TDD, commit,
1055
+ push):
1056
+
1057
+ - Replies inline on each addressed finding thread via
1058
+ `reply_to_inline_comment.py` (what changed and the commit identifier),
1059
+ matching §Audit result → fix worker step 4 — **before** writing `state.json`
1060
+ and going idle.
1061
+ - Writes `last_action: "fix_pushed"`, `current_head: <new SHA>`,
1062
+ `bugbot_clean_at: null`, `phase: "BUGBOT"`, `status: "awaiting_bugbot"`, and
1063
+ `last_updated` (ISO-8601 UTC) to `state.json` (per §Concurrency).
1064
+ - Goes idle. The orchestrator spawns the follow-up `general-purpose` agent for
1065
+ bugbot trigger and monitoring.
1066
+
1067
+ **The orchestrator does not reply to inline comments, does not trigger bugbot,
1068
+ and does not read repository source files during the fix phase** when the
1069
+ multi-PR model is active.
585
1070
 
586
1071
  **Single-PR (no `state.json`) — same gates, main session executor:**
587
1072
 
588
1073
  - Read each referenced file:line.
589
- - Write a failing test first when the finding has behavior to test. For pure doc, comment, or naming nits with no behavior, go straight to the fix.
590
- - **Implement** by invoking **`Task`** with **`subagent_type: "generalPurpose"`**. The **`prompt`** MUST begin by requiring the subagent to **Read** the clean-coder agent markdown **before** editing production files: on macOS/Linux `$HOME/.claude/agents/clean-coder.md`, on Windows `%USERPROFILE%\.claude\agents\clean-coder.md`. The prompt MUST state that file is binding for code generation (naming, TDD when behavior changes, hook-safe single commit, scope limited to the listed findings). Do **not** use ad-hoc shell edits for production code on this path. Do **not** emit a bare `generalPurpose` prompt that omits the clean-coder file step. If **`Task`** cannot be invoked, **full stop** and tell the user – do not substitute another subagent type for production edits.
1074
+ - Write a failing test first when the finding has behavior to test. For pure
1075
+ doc, comment, or naming nits with no behavior, go straight to the fix.
1076
+ - **Implement** by invoking **`Task`** with **`subagent_type:
1077
+ "generalPurpose"`**. The **`prompt`** MUST begin by requiring the subagent to
1078
+ **Read** the clean-coder agent markdown **before** editing production files:
1079
+ on macOS/Linux `$HOME/.claude/agents/clean-coder.md`, on Windows
1080
+ `%USERPROFILE%\.claude\agents\clean-coder.md`. The prompt MUST state that file
1081
+ is binding for code generation (naming, TDD when behavior changes, hook-safe
1082
+ single commit, scope limited to the listed findings). Do **not** use ad-hoc
1083
+ shell edits for production code on this path. Do **not** emit a bare
1084
+ `generalPurpose` prompt that omits the clean-coder file step. If **`Task`**
1085
+ cannot be invoked, **full stop** and tell the user – do not substitute another
1086
+ subagent type for production edits.
591
1087
  - Stage the affected files and create one new commit on the existing branch:
592
1088
  ```bash
593
- git add <files> && git commit -m "fix(review): <brief summary>"
1089
+ git add <files> && git commit -m "fix(review): <brief summary>"
594
1090
  ```
595
- **Pre-commit gate:** Never pass `--no-verify`, `--no-gpg-sign` (unless the user has explicitly required otherwise), or any flag that skips hooks. After `git commit`, confirm from the **same terminal transcript** that the **pre-commit** hook ran (visible hook output or your configured hook runner's success banner) and exited **0**. If the transcript shows hooks were **skipped**, **bypassed**, or **did not run** when your repo expects them, **full stop** — do not push, do not reply inline, do not trigger Bugbot — and notify the user with what you observed. When a hook **rejects** (non-zero exit), read the message, fix the cause, retry commit until hooks pass.
1091
+ **Pre-commit gate:** Never pass `--no-verify`, `--no-gpg-sign` (unless the user
1092
+ has explicitly required otherwise), or any flag that skips hooks. After `git
1093
+ commit`, confirm from the **same terminal transcript** that the **pre-commit**
1094
+ hook ran (visible hook output or your configured hook runner's success banner)
1095
+ and exited **0**. If the transcript shows hooks were **skipped**, **bypassed**,
1096
+ or **did not run** when your repo expects them, **full stop** — do not push, do
1097
+ not reply inline, do not trigger Bugbot — and notify the user with what you
1098
+ observed. When a hook **rejects** (non-zero exit), read the message, fix the
1099
+ cause, retry commit until hooks pass.
596
1100
  - Push the new commit:
597
1101
  ```bash
598
- git push origin <BRANCH>
1102
+ git push origin <BRANCH>
599
1103
  ```
600
- **Pre-push gate:** Never pass `--no-verify` or equivalent. After `git push`, confirm from the **same terminal transcript** that **pre-push** ran (when your repo defines a pre-push hook) and exited **0**. If push output shows pre-push was **skipped**, **bypassed**, or **absent** when it should have run, **full stop** — do not update `current_head`, do not reply inline, do not trigger Bugbot — and notify the user. Capture the new HEAD SHA only after both gates pass. Set `current_head` to it. Set `bugbot_clean_at = null`.
601
- - Reply inline on each addressed comment thread using `--body-file` (per gh-body-file rule):
1104
+ **Pre-push gate:** Never pass `--no-verify` or equivalent. After `git push`,
1105
+ confirm from the **same terminal transcript** that **pre-push** ran (when your
1106
+ repo defines a pre-push hook) and exited **0**. If push output shows pre-push
1107
+ was **skipped**, **bypassed**, or **absent** when it should have run, **full
1108
+ stop** — do not update `current_head`, do not reply inline, do not trigger
1109
+ Bugbot — and notify the user. Capture the new HEAD SHA only after both gates
1110
+ pass. Set `current_head` to it. Set `bugbot_clean_at = null`.
1111
+ - Reply inline on each addressed comment thread using `--body-file` (per
1112
+ gh-body-file rule):
602
1113
  ```bash
603
- python "${CLAUDE_SKILL_DIR}/scripts/reply_to_inline_comment.py" \
604
- --owner <OWNER> --repo <REPO> --number <NUMBER> \
605
- --comment-id <COMMENT_ID> --body-file <path/to/reply.md>
1114
+ python "${CLAUDE_SKILL_DIR}/scripts/reply_to_inline_comment.py" \
1115
+ --owner <OWNER> --repo <REPO> --number <NUMBER> \
1116
+ --comment-id <COMMENT_ID> --body-file <path/to/reply.md>
606
1117
  ```
607
- - **After pushing a fix, always run Step 3 (`bugbot run`) in the same tick** when you would otherwise wait for Bugbot — regardless of which phase originated the findings. Step 3 is the **mechanism** that restarts Bugbot on the new `HEAD`, but the **meaning** is broader: a new commit **resets the full convergence cycle**. Prior bugbot clean and prior second-audit clean on an older SHA **do not** count toward convergence on the new `HEAD`. You must **again** obtain **bugbot CLEAN** on `current_head`, then **second-audit CLEAN** on that same `HEAD` with **no intervening push** (the same back-to-back rule as Step 2). Re-triggering Bugbot in the same tick after the push saves a full wakeup cycle compared to deferring Step 3 to the next tick.
1118
+ - **After pushing a fix, always run Step 3 (`bugbot run`) in the same tick**
1119
+ when you would otherwise wait for Bugbot — regardless of which phase
1120
+ originated the findings. Step 3 is the **mechanism** that restarts Bugbot on
1121
+ the new `HEAD`, but the **meaning** is broader: a new commit **resets the full
1122
+ convergence cycle**. Prior bugbot clean and prior second-audit clean on an
1123
+ older SHA **do not** count toward convergence on the new `HEAD`. You must
1124
+ **again** obtain **bugbot CLEAN** on `current_head`, then **second-audit
1125
+ CLEAN** on that same `HEAD` with **no intervening push** (the same
1126
+ back-to-back rule as Step 2). Re-triggering Bugbot in the same tick after the
1127
+ push saves a full wakeup cycle compared to deferring Step 3 to the next tick.
608
1128
 
609
1129
  ## Stop conditions
610
1130
 
611
- - **Convergence** (back-to-back clean ∧ no outstanding Copilot findings on `current_head` ∧ `mergeStateStatus == "CLEAN"` with `mergeable == "MERGEABLE"` ∧ post-convergence Copilot request resolved (either `clean` at `current_head`, or `dirty` with a follow-up PR opened per §Convergence gates (c)) — see §Convergence gates for the full ordered check): prefer `mark_pr_ready.py`; when unavailable use `gh pr ready`. When `state.json` is in use, append the convergence row to `<TMPDIR>/pr-converge-<session_id>/converged.log` per §Memory; otherwise skip file append. Report the §Convergence gates (d) summary line, then **omit loop pacing** per **Convergence** in the pacing workflow from the Step 4 table (or omit `ScheduleWakeup` when no workflow file applies). End any ongoing loops once all PRs are converged.
612
- - **Hard blocker:** API auth failure persists across two ticks, a CI regression whose root cause falls outside this PR, a hook rejection investigated through three commits and still unresolved, `inline_lag_streak >= 3`, **bugteam** (either workflow) reports a stuck state, or the post-convergence Copilot request fails to surface a review on `current_head` after three consecutive wakeup cycles. Report the specific blocker and the diagnosis, then **omit loop pacing** per the active workflow; stop the AHK auto-typer per `workflows/ahk-auto-continue-loop.md` **Stop / safety** if that path was in use.
613
- - **Hard blocker (`mergeStateStatus` non-CLEAN non-DIRTY):** `mergeStateStatus` is `BLOCKED`, `UNKNOWN`, or `BEHIND` (required checks pending, branch behind base without textual conflicts, or GitHub returns an indeterminate state). Investigate before retrying; the `rebase` skill addresses `DIRTY` (textual merge conflicts) — it does not address these states. Report the specific `mergeStateStatus`, then **omit loop pacing** per the active workflow; stop the AHK auto-typer per `workflows/ahk-auto-continue-loop.md` **Stop / safety** if that path was in use.
614
- - **User stops the loop:** user says "stop the converge loop" → **omit loop pacing** per the active workflow; stop the AHK auto-typer per `workflows/ahk-auto-continue-loop.md` **Stop / safety** if that path was in use.
1131
+ - **Convergence** (back-to-back clean ∧ no outstanding Copilot findings on
1132
+ `current_head` `mergeStateStatus == "CLEAN"` with `mergeable == "MERGEABLE"`
1133
+ post-convergence Copilot request resolved (either `clean` at `current_head`,
1134
+ or `dirty` with a follow-up PR opened per §Convergence gates (c)) see
1135
+ §Convergence gates for the full ordered check): prefer `mark_pr_ready.py`;
1136
+ when unavailable use `gh pr ready`. When `state.json` is in use, append the
1137
+ convergence row to `<TMPDIR>/pr-converge-<session_id>/converged.log` per
1138
+ §Memory; otherwise skip file append. Report the §Convergence gates (d) summary
1139
+ line, then **omit loop pacing** per **Convergence** in the pacing workflow
1140
+ from the Step 4 table (or omit `ScheduleWakeup` when no workflow file
1141
+ applies). End any ongoing loops once all PRs are converged.
1142
+ - **Hard blocker:** API auth failure persists across two ticks, a CI regression
1143
+ whose root cause falls outside this PR, a hook rejection investigated through
1144
+ three commits and still unresolved, `inline_lag_streak >= 3`, **bugteam**
1145
+ (either workflow) reports a stuck state, or the post-convergence Copilot
1146
+ request fails to surface a review on `current_head` after three consecutive
1147
+ wakeup cycles. Report the specific blocker and the diagnosis, then **omit loop
1148
+ pacing** per the active workflow; stop the AHK auto-typer per
1149
+ `workflows/ahk-auto-continue-loop.md` **Stop / safety** if that path was in
1150
+ use.
1151
+ - **Hard blocker (`mergeStateStatus` non-CLEAN non-DIRTY):** `mergeStateStatus`
1152
+ is `BLOCKED`, `UNKNOWN`, or `BEHIND` (required checks pending, branch behind
1153
+ base without textual conflicts, or GitHub returns an indeterminate state).
1154
+ Investigate before retrying; the `rebase` skill addresses `DIRTY` (textual
1155
+ merge conflicts) — it does not address these states. Report the specific
1156
+ `mergeStateStatus`, then **omit loop pacing** per the active workflow; stop
1157
+ the AHK auto-typer per `workflows/ahk-auto-continue-loop.md` **Stop / safety**
1158
+ if that path was in use.
1159
+ - **User stops the loop:** user says "stop the converge loop" → **omit loop
1160
+ pacing** per the active workflow; stop the AHK auto-typer per
1161
+ `workflows/ahk-auto-continue-loop.md` **Stop / safety** if that path was in
1162
+ use.
615
1163
 
616
1164
  ## Ground rules
617
1165
 
618
- - **Append commits.** Each tick adds at most one new fix commit. Multiple findings within one tick collapse into a single commit; the next tick handles the next round.
619
- - **Bugbot findings on the current SHA mean fix-then-push-then-`bugbot run`, not another naked `bugbot run`.** Unaddressed Bugbot errors require the Fix protocol before Step 3; posting `bugbot run` again without a new commit does not clear the review state.
620
- - **`bugbot_clean_at` resets on every push.** A new commit invalidates bugbot's prior clean by definition — bugbot must re-review the new HEAD before convergence can be claimed.
621
- - **`copilot_clean_at` and `merge_state_status` reset on every push.** The same invalidation rule applies to the Copilot reviewer's prior clean and the prior `gh pr view` mergeability snapshot — both must be re-checked on the new HEAD before §Convergence gates can pass.
622
- - **Convergence requires four gates on the same HEAD.** (1) Back-to-back clean (Bugbot **bugteam** — team or background-agent workflow — with no intervening fixes), (2) no outstanding Copilot findings on `current_head`, (3) `mergeStateStatus == "CLEAN"` with `mergeable == "MERGEABLE"`, and (4) the post-convergence Copilot request resolved (either Copilot clean at HEAD, or a follow-up PR opened off HEAD that captures the Copilot findings — see §Convergence gates). Any one gate failing leaves the PR in-progress.
623
- - **Clean Bugbot on `HEAD` means advance to second audit, not another `bugbot run`.** After Bugbot reports clean on the current SHA, set `bugbot_clean_at` and run the BUGTEAM phase per Step 2 — never post `bugbot run` as a substitute.
624
- - **The `bugbot run` comment is load-bearing.** Use the literal phrase `bugbot run` exactly — empirically the only re-trigger Cursor Bugbot recognizes; alternative phrasings silently no-op.
625
- - **`gh pr ready` / `mark_pr_ready.py` is the convergence action.** Mark the PR ready for review and stop there. Merge, additional reviewers, title, and body remain the user's decisions; the skill's contract ends at "ready for review."
626
- - **Honor pre-push and pre-commit hooks.** When a hook rejects the change, read its output, fix the underlying issue (the failing test, the missing constant, the broken import), and retry.
627
- - **Adapt when reality contradicts on-disk state.** This skill is a state machine, but the spec assumes `state.json` (when used) and `git`/`gh` agree with the live PR. When they diverge — the user pushed manually between ticks, the branch was force-reset, the worktree moved, the PR was closed/merged externally, or `gh` auth dropped mid-tick — **do not execute the spec literally against stale state**. Report the specific drift and escalate as a hard blocker per §Stop conditions; let the user decide whether to reset the loop, refresh credentials, or stop.
1166
+ - **Append commits.** Each tick adds at most one new fix commit. Multiple
1167
+ findings within one tick collapse into a single commit; the next tick handles
1168
+ the next round.
1169
+ - **Bugbot findings on the current SHA mean fix-then-push-then-`bugbot run`, not
1170
+ another naked `bugbot run`.** Unaddressed Bugbot errors require the Fix
1171
+ protocol before Step 3; posting `bugbot run` again without a new commit does
1172
+ not clear the review state.
1173
+ - **`bugbot_clean_at` resets on every push.** A new commit invalidates bugbot's
1174
+ prior clean by definition bugbot must re-review the new HEAD before
1175
+ convergence can be claimed.
1176
+ - **`copilot_clean_at` and `merge_state_status` reset on every push.** The same
1177
+ invalidation rule applies to the Copilot reviewer's prior clean and the prior
1178
+ `gh pr view` mergeability snapshot — both must be re-checked on the new HEAD
1179
+ before §Convergence gates can pass.
1180
+ - **Convergence requires four gates on the same HEAD.** (1) Back-to-back clean
1181
+ (Bugbot ∧ **bugteam** — team or background-agent workflow — with no
1182
+ intervening fixes), (2) no outstanding Copilot findings on `current_head`, (3)
1183
+ `mergeStateStatus == "CLEAN"` with `mergeable == "MERGEABLE"`, and (4) the
1184
+ post-convergence Copilot request resolved (either Copilot clean at HEAD, or a
1185
+ follow-up PR opened off HEAD that captures the Copilot findings — see
1186
+ §Convergence gates). Any one gate failing leaves the PR in-progress.
1187
+ - **Clean Bugbot on `HEAD` means advance to second audit, not another `bugbot
1188
+ run`.** After Bugbot reports clean on the current SHA, set `bugbot_clean_at`
1189
+ and run the BUGTEAM phase per Step 2 — never post `bugbot run` as a
1190
+ substitute.
1191
+ - **The `bugbot run` comment is load-bearing.** Use the literal phrase `bugbot
1192
+ run` exactly — empirically the only re-trigger Cursor Bugbot recognizes;
1193
+ alternative phrasings silently no-op.
1194
+ - **`gh pr ready` / `mark_pr_ready.py` is the convergence action.** Mark the PR
1195
+ ready for review and stop there. Merge, additional reviewers, title, and body
1196
+ remain the user's decisions; the skill's contract ends at "ready for review."
1197
+ - **Honor pre-push and pre-commit hooks.** When a hook rejects the change, read
1198
+ its output, fix the underlying issue (the failing test, the missing constant,
1199
+ the broken import), and retry.
1200
+ - **Adapt when reality contradicts on-disk state.** This skill is a state
1201
+ machine, but the spec assumes `state.json` (when used) and `git`/`gh` agree
1202
+ with the live PR. When they diverge — the user pushed manually between ticks,
1203
+ the branch was force-reset, the worktree moved, the PR was closed/merged
1204
+ externally, or `gh` auth dropped mid-tick — **do not execute the spec
1205
+ literally against stale state**. Report the specific drift and escalate as a
1206
+ hard blocker per §Stop conditions; let the user decide whether to reset the
1207
+ loop, refresh credentials, or stop.
628
1208
 
629
1209
  ## Examples
630
1210
 
631
- <example>
632
- User: `/pr-converge`
633
- Claude: [PR context + one tick of bugbot/bugteam work; then Step 4 per loaded pacing workflow — default loop until convergence or stop]
1211
+ <example> User: `/pr-converge` Claude: [PR context + one tick of bugbot/bugteam
1212
+ work; then Step 4 per loaded pacing workflow — default loop until
1213
+ convergence or stop]
634
1214
  </example>
635
1215
 
636
- <example>
637
- User: `/loop /pr-converge`
638
- Claude: [same per-tick work and Step 4 as bare `/pr-converge` — harness wrapper only when the host routes wakeups through `/loop`]
1216
+ <example> User: `/loop /pr-converge` Claude: [same per-tick work and Step 4 as
1217
+ bare `/pr-converge` — harness wrapper only when the host routes
1218
+ wakeups through `/loop`]
639
1219
  </example>
640
1220
 
641
- <example>
642
- Tick fires in BUGBOT phase, latest bugbot review is against an older commit.
643
- Claude: [posts `bugbot run` comment, sets `bugbot_clean_at = null`, completes Step 4 per `workflows/schedule-wakeup-loop.md` when on that path
644
- (e.g. 270s wakeup), returns]
1221
+ <example> Tick fires in BUGBOT phase, latest bugbot review is against an older
1222
+ commit. Claude: [posts `bugbot run` comment, sets `bugbot_clean_at =
1223
+ null`, completes Step 4 per `workflows/schedule-wakeup-loop.md` when
1224
+ on that path (e.g. 270s wakeup), returns]
645
1225
  </example>
646
1226
 
647
- <example>
648
- Tick fires in BUGBOT phase, bugbot has 2 unaddressed findings on HEAD.
649
- Claude: [TDD-fixes both, one commit, pushes, replies inline on both threads, posts `bugbot run`, Step 4 per schedule-wakeup workflow at 270s
650
- when on that path, returns]
1227
+ <example> Tick fires in BUGBOT phase, bugbot has 2 unaddressed findings on HEAD.
1228
+ Claude: [TDD-fixes both, one commit, pushes, replies inline on both
1229
+ threads, posts `bugbot run`, Step 4 per schedule-wakeup workflow at
1230
+ 270s when on that path, returns]
651
1231
  </example>
652
1232
 
653
- <example>
654
- Tick fires in BUGBOT phase, bugbot is clean against HEAD.
655
- Claude: [sets `bugbot_clean_at = HEAD`, transitions `phase = BUGTEAM`, runs `Skill({skill: "bugteam", ...})` in the same tick — bugteam Path routing picks Path A vs Path B internally]
1233
+ <example> Tick fires in BUGBOT phase, bugbot is clean against HEAD. Claude:
1234
+ [sets `bugbot_clean_at = HEAD`, transitions `phase = BUGTEAM`, runs
1235
+ `Skill({skill: "bugteam", ...})` in the same tick — bugteam Path
1236
+ routing picks Path A vs Path B internally]
656
1237
  </example>
657
1238
 
658
- <example>
659
- In BUGTEAM phase, bugteam (team workflow) reports convergence and `bugbot_clean_at == current_head`.
660
- Claude: [runs `gh pr ready <NUMBER>`, reports "PR converged: bugbot CLEAN at <SHA>, bugteam CLEAN at <SHA>; marked ready for review", applies
661
- **Convergence** from the active pacing workflow]
1239
+ <example> In BUGTEAM phase, bugteam (team workflow) reports convergence and
1240
+ `bugbot_clean_at == current_head`. Claude: [runs `gh pr ready
1241
+ <NUMBER>`, reports "PR converged: bugbot CLEAN at <SHA>, bugteam CLEAN
1242
+ at <SHA>; marked ready for review", applies **Convergence** from the
1243
+ active pacing workflow]
662
1244
  </example>
663
1245
 
664
- <example>
665
- In BUGTEAM phase, bugteam pushed a fix commit during its run.
666
- Claude: [re-resolves HEAD, sets `bugbot_clean_at = null`, posts `bugbot run` in this same tick, transitions `phase = BUGBOT`, Step 4 per
667
- schedule-wakeup workflow at 270s when on that path]
1246
+ <example> In BUGTEAM phase, bugteam pushed a fix commit during its run. Claude:
1247
+ [re-resolves HEAD, sets `bugbot_clean_at = null`, posts `bugbot run`
1248
+ in this same tick, transitions `phase = BUGBOT`, Step 4 per
1249
+ schedule-wakeup workflow at 270s when on that path]
668
1250
  </example>
669
1251
 
670
- <example>
671
- Tick fires in BUGBOT phase, bugbot review body says "found 3 potential issues" against HEAD but the inline-comments API returns zero matching
672
- comments for `current_head`.
673
- Claude: [increments `inline_lag_streak` to 1, Step 4 inline-lag rules from the active pacing workflow (60s `ScheduleWakeup` vs AHK cadence),
674
- returns; expects inline comments on the next tick]
1252
+ <example> Tick fires in BUGBOT phase, bugbot review body says "found 3 potential
1253
+ issues" against HEAD but the inline-comments API returns zero matching
1254
+ comments for `current_head`. Claude: [increments `inline_lag_streak`
1255
+ to 1, Step 4 inline-lag rules from the active pacing workflow (60s
1256
+ `ScheduleWakeup` vs AHK cadence), returns; expects inline comments on
1257
+ the next tick]
675
1258
  </example>
676
1259
 
677
- <example>
678
- BUGTEAM tick with no agent teams: `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` is unset; bugteam Path B applies inside `Skill({skill: "bugteam", ...})`.
679
- Claude: [invokes bugteam; bugteam runs Path B per `bugteam/SKILL.md` + `bugteam/reference/workflow-path-b-task-harness.md`; applies Step 2 §(b)–(d) unchanged against the skill outcome]
1260
+ <example> BUGTEAM tick with no agent teams:
1261
+ `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` is unset; bugteam Path B
1262
+ applies inside `Skill({skill: "bugteam", ...})`. Claude: [invokes
1263
+ bugteam; bugteam runs Path B per `bugteam/SKILL.md` +
1264
+ `bugteam/reference/workflow-path-b-task-harness.md`; applies Step 2
1265
+ §(b)–(d) unchanged against the skill outcome]
680
1266
  </example>
681
1267
 
682
- <example>
683
- Back-to-back clean reached, but `gh pr view` shows `mergeStateStatus: DIRTY` (the base branch advanced and produced merge conflicts).
684
- Claude: [runs §Convergence gates (b); does NOT mark ready; invokes the `rebase` skill per `../rebase/SKILL.md` Phase 1–4; after force-with-lease push, resets `bugbot_clean_at = null`, `copilot_clean_at = null`, `merge_state_status = null`, transitions `phase = BUGBOT`, posts `bugbot run` against the new HEAD, schedules next wakeup; the convergence loop re-runs from scratch on the new HEAD]
1268
+ <example> Back-to-back clean reached, but `gh pr view` shows `mergeStateStatus:
1269
+ DIRTY` (the base branch advanced and produced merge conflicts).
1270
+ Claude: [runs §Convergence gates (b); does NOT mark ready; invokes the
1271
+ `rebase` skill per `../rebase/SKILL.md` Phase 1–4; after
1272
+ force-with-lease push, resets `bugbot_clean_at = null`,
1273
+ `copilot_clean_at = null`, `merge_state_status = null`, transitions
1274
+ `phase = BUGBOT`, posts `bugbot run` against the new HEAD, schedules
1275
+ next wakeup; the convergence loop re-runs from scratch on the new
1276
+ HEAD]
685
1277
  </example>
686
1278
 
687
- <example>
688
- Back-to-back clean reached, mergeability is CLEAN, and Copilot already posted a review at `current_head` whose `state == "CHANGES_REQUESTED"` with two unaddressed inline findings.
689
- Claude: [runs §Convergence gates (a); reads each finding, applies the Fix protocol (TDD test → fix → push → reply inline on both threads), resets `bugbot_clean_at = null` and `copilot_clean_at = null`, transitions `phase = BUGBOT`, posts `bugbot run` on the new HEAD, schedules next wakeup; the back-to-back-clean cycle plus all four gates must hold again on the new HEAD]
1279
+ <example> Back-to-back clean reached, mergeability is CLEAN, and Copilot already
1280
+ posted a review at `current_head` whose `state == "CHANGES_REQUESTED"`
1281
+ with two unaddressed inline findings. Claude: [runs §Convergence gates
1282
+ (a); reads each finding, applies the Fix protocol (TDD test → fix →
1283
+ push → reply inline on both threads), resets `bugbot_clean_at = null`
1284
+ and `copilot_clean_at = null`, transitions `phase = BUGBOT`, posts
1285
+ `bugbot run` on the new HEAD, schedules next wakeup; the
1286
+ back-to-back-clean cycle plus all four gates must hold again on the
1287
+ new HEAD]
690
1288
  </example>
691
1289
 
692
- <example>
693
- Back-to-back clean reached, mergeability is CLEAN, no Copilot review exists yet on `current_head`. Claude requests Copilot via `request_copilot_review.py` and waits one tick.
694
- Next tick: Copilot review at `current_head` is `state: APPROVED`.
695
- Claude: [sets `copilot_clean_at = current_head`; runs `mark_pr_ready.py`; reports "PR #N converged: bugbot CLEAN at <SHA>, bugteam CLEAN at <SHA>, mergeStateStatus CLEAN, copilot CLEAN; marked ready for review"; applies **Convergence** from the active pacing workflow]
1290
+ <example> Back-to-back clean reached, mergeability is CLEAN, no Copilot review
1291
+ exists yet on `current_head`. Claude requests Copilot via
1292
+ `request_copilot_review.py` and waits one tick. Next tick: Copilot
1293
+ review at `current_head` is `state: APPROVED`. Claude: [sets
1294
+ `copilot_clean_at = current_head`; runs `mark_pr_ready.py`; reports
1295
+ "PR #N converged: bugbot CLEAN at <SHA>, bugteam CLEAN at <SHA>,
1296
+ mergeStateStatus CLEAN, copilot CLEAN; marked ready for review";
1297
+ applies **Convergence** from the active pacing workflow]
696
1298
  </example>
697
1299
 
698
- <example>
699
- Back-to-back clean reached, mergeability is CLEAN, post-convergence Copilot review fired and returned `state: CHANGES_REQUESTED` with inline findings on `current_head`.
700
- Claude: [still marks the PR ready (the four-gate rule allows convergence when a follow-up captures Copilot findings); builds a markdown findings checklist from `fetch_copilot_inline_comments.py`; runs `open_followup_copilot_pr.py` off `current_head` to create branch `chore/copilot-followup-<NUMBER>-<short_sha>` with title `chore: address Copilot findings from PR #<NUMBER>`; reports both PR URLs to the user; queues `/pr-converge` on the new PR for the user to invoke; current PR's convergence is final]
1300
+ <example> Back-to-back clean reached, mergeability is CLEAN, post-convergence
1301
+ Copilot review fired and returned `state: CHANGES_REQUESTED` with
1302
+ inline findings on `current_head`. Claude: [still marks the PR ready
1303
+ (the four-gate rule allows convergence when a follow-up captures
1304
+ Copilot findings); builds a markdown findings checklist from
1305
+ `fetch_copilot_inline_comments.py`; runs `open_followup_copilot_pr.py`
1306
+ off `current_head` to create branch
1307
+ `chore/copilot-followup-<NUMBER>-<short_sha>` with title `chore:
1308
+ address Copilot findings from PR
1309
+ # <NUMBER>`; reports both PR URLs to the user; queues `/pr-converge` on the new
1310
+ # PR for the user to invoke; current PR's convergence is final]
701
1311
  </example>