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.
- package/package.json +1 -1
- package/skills/bugteam/SKILL.md +332 -108
- package/skills/bugteam/scripts/reflow_skill_md.py +298 -0
- package/skills/bugteam/test_team_lifecycle.py +9 -0
- package/skills/pr-converge/SKILL.md +1005 -395
- package/skills/pr-converge/scripts/reflow_skill_md.py +288 -0
- package/skills/pr-converge/test_team_lifecycle.py +9 -0
|
@@ -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
|
|
6
|
-
exposes `Skill`; bugteam `SKILL.md` **Path routing** picks Path A vs Path
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
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`**
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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,
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
102
|
-
|
|
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/`):
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
**
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
-
|
|
144
|
-
-
|
|
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**,
|
|
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-
|
|
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
|
|
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
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
**
|
|
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**
|
|
213
|
-
|
|
214
|
-
|
|
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
|
|
219
|
-
|
|
220
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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`
|
|
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
|
|
250
|
-
|
|
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
|
|
254
|
-
|
|
255
|
-
|
|
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
|
|
266
|
-
|
|
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
|
|
269
|
-
|
|
270
|
-
- **`
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
278
|
-
|
|
279
|
-
-
|
|
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`
|
|
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
|
|
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
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
|
302
|
-
|
|
303
|
-
- **
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|
311
|
-
-
|
|
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
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
|
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`,
|
|
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
|
|
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
|
-
|
|
349
|
-
|
|
623
|
+
python "${CLAUDE_SKILL_DIR}/scripts/fetch_bugbot_reviews.py" \
|
|
624
|
+
--owner <OWNER> --repo <REPO> --number <NUMBER>
|
|
350
625
|
```
|
|
351
626
|
|
|
352
|
-
|
|
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
|
-
|
|
356
|
-
|
|
633
|
+
dirty_reviews_path=$(mktemp "${TMPDIR:-/tmp}/pr-converge-bugbot.XXXXXX")
|
|
634
|
+
: > "$dirty_reviews_path"
|
|
357
635
|
```
|
|
358
636
|
|
|
359
|
-
|
|
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
|
|
362
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
371
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
- **
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
- **Latest review's `commit_id == current_head`
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
- **Latest review's `commit_id == current_head`
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
|
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
|
-
|
|
716
|
+
Skill({skill: "bugteam", args:
|
|
717
|
+
"https://github.com/<OWNER>/<REPO>/pull/<NUMBER>"})
|
|
405
718
|
```
|
|
406
719
|
|
|
407
|
-
|
|
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
|
|
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
|
|
412
|
-
|
|
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
|
-
|
|
415
|
-
|
|
733
|
+
new_head=$(python "${CLAUDE_SKILL_DIR}/scripts/resolve_pr_head.py" \
|
|
734
|
+
--owner <OWNER> --repo <REPO> --number <NUMBER>)
|
|
416
735
|
```
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
781
|
+
--owner <OWNER> --repo <REPO> --number <NUMBER>
|
|
438
782
|
|
|
439
783
|
python "${CLAUDE_SKILL_DIR}/scripts/fetch_copilot_inline_comments.py" \
|
|
440
|
-
|
|
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
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
- **A Copilot review exists at `current_head` AND its `classification ==
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
|
|
824
|
+
--owner <OWNER> --repo <REPO> --number <NUMBER>
|
|
457
825
|
```
|
|
458
826
|
|
|
459
|
-
Output is `{"mergeable", "mergeStateStatus", "headRefOid"}`. Persist
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
- **`mergeStateStatus
|
|
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
|
|
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
|
-
|
|
857
|
+
--owner <OWNER> --repo <REPO> --number <NUMBER>
|
|
472
858
|
```
|
|
473
859
|
|
|
474
|
-
The reviewer ID literal `copilot-pull-request-reviewer[bot]` (with the `[bot]`
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
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):**
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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"
|
|
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"
|
|
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"
|
|
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;
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
**
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
- `
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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**;
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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
|
|
590
|
-
|
|
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
|
-
|
|
1089
|
+
git add <files> && git commit -m "fix(review): <brief summary>"
|
|
594
1090
|
```
|
|
595
|
-
|
|
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
|
-
|
|
1102
|
+
git push origin <BRANCH>
|
|
599
1103
|
```
|
|
600
|
-
|
|
601
|
-
|
|
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
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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**
|
|
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
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
- **`
|
|
626
|
-
|
|
627
|
-
|
|
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
|
-
|
|
633
|
-
|
|
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
|
-
|
|
638
|
-
|
|
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
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
|
|
655
|
-
|
|
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
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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
|
-
|
|
679
|
-
|
|
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
|
-
|
|
684
|
-
Claude: [runs §Convergence gates (b); does NOT mark ready; invokes the
|
|
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
|
-
|
|
689
|
-
Claude: [runs §Convergence gates
|
|
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
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
-
|
|
700
|
-
|
|
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>
|