claude-dev-env 1.35.0 → 1.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/agents/clean-coder.md +109 -1
  2. package/bin/install.mjs +28 -8
  3. package/bin/install.test.mjs +9 -1
  4. package/docs/CODE_RULES.md +3 -0
  5. package/docs/agents-md-alignment-plan.md +123 -0
  6. package/hooks/blocking/code_rules_enforcer.py +451 -39
  7. package/hooks/blocking/es_exe_path_rewriter.py +10 -4
  8. package/hooks/blocking/test_code_rules_enforcer.py +182 -0
  9. package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +106 -0
  10. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +173 -0
  11. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +191 -0
  12. package/hooks/blocking/test_code_rules_enforcer_constant_equality.py +40 -0
  13. package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +291 -0
  14. package/hooks/blocking/test_code_rules_enforcer_loop_variable_naming.py +87 -3
  15. package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +49 -0
  16. package/hooks/blocking/test_code_rules_enforcer_sys_path_insert.py +157 -0
  17. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +244 -0
  18. package/hooks/blocking/test_es_exe_path_rewriter.py +81 -3
  19. package/hooks/blocking/test_windows_rmtree_blocker.py +120 -8
  20. package/hooks/blocking/windows_rmtree_blocker.py +23 -6
  21. package/hooks/config/banned_identifiers_constants.py +24 -0
  22. package/hooks/config/hardcoded_user_path_constants.py +12 -0
  23. package/hooks/config/hook_log_extractor_constants.py +1 -1
  24. package/hooks/config/pre_tool_use_stdin.py +48 -0
  25. package/hooks/config/setup_project_paths_constants.py +4 -0
  26. package/hooks/config/stuttering_check_config.py +14 -0
  27. package/hooks/config/stuttering_import_binding_constants.py +11 -0
  28. package/hooks/config/sys_path_insert_constants.py +4 -0
  29. package/hooks/config/test_banned_identifiers_constants.py +48 -0
  30. package/hooks/config/test_hardcoded_user_path_constants.py +78 -0
  31. package/hooks/config/test_hook_log_extractor_constants.py +3 -3
  32. package/hooks/config/test_pre_tool_use_stdin.py +80 -0
  33. package/hooks/config/unused_module_import_constants.py +7 -0
  34. package/hooks/config/windows_rmtree_blocker_constants.py +3 -0
  35. package/hooks/diagnostic/hook_log_stop_wrapper.py +7 -4
  36. package/hooks/git-hooks/config.py +3 -3
  37. package/hooks/git-hooks/test_gate_utils.py +10 -10
  38. package/hooks/mypy.ini +2 -0
  39. package/package.json +1 -1
  40. package/rules/gh-paginate.md +125 -0
  41. package/skills/bugteam/CONSTRAINTS.md +12 -6
  42. package/skills/bugteam/SKILL.md +77 -91
  43. package/skills/bugteam/SKILL_EVALS.md +25 -23
  44. package/skills/bugteam/reference/README.md +2 -0
  45. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  46. package/skills/bugteam/reference/teardown-publish-permissions.md +1 -1
  47. package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +113 -0
  48. package/skills/bugteam/reference/workflow-path-b-task-harness.md +48 -0
  49. package/skills/bugteam/test_skill_additions.py +13 -4
  50. package/skills/bugteam/test_team_lifecycle.py +94 -0
  51. package/skills/findbugs/SKILL.md +3 -3
  52. package/skills/fixbugs/SKILL.md +4 -4
  53. package/skills/monitor-open-prs/SKILL.md +32 -2
  54. package/skills/monitor-open-prs/test_team_lifecycle.py +46 -0
  55. package/skills/pr-converge/SKILL.md +562 -97
  56. package/skills/pr-converge/scripts/README.md +145 -0
  57. package/skills/pr-converge/scripts/caller-window-pid.ps1 +86 -0
  58. package/skills/pr-converge/scripts/check_pr_mergeability.py +79 -0
  59. package/skills/pr-converge/scripts/config/pr_converge_constants.py +65 -0
  60. package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +176 -0
  61. package/skills/pr-converge/scripts/cursor-agents-continue-caller.cmd +9 -0
  62. package/skills/pr-converge/scripts/cursor-agents-continue-stop-others.ps1 +16 -0
  63. package/skills/pr-converge/scripts/cursor-agents-continue.ahk +172 -0
  64. package/skills/pr-converge/scripts/cursor-agents-continue.cmd +2 -0
  65. package/skills/pr-converge/scripts/evict_cached_config_modules.py +20 -0
  66. package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +110 -0
  67. package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +103 -0
  68. package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +112 -0
  69. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +121 -0
  70. package/skills/pr-converge/scripts/mark_pr_ready.py +54 -0
  71. package/skills/pr-converge/scripts/open_followup_copilot_pr.py +136 -0
  72. package/skills/pr-converge/scripts/post-bugbot-run.helpers.ps1 +49 -0
  73. package/skills/pr-converge/scripts/post-bugbot-run.ps1 +33 -0
  74. package/skills/pr-converge/scripts/reply_to_inline_comment.py +84 -0
  75. package/skills/pr-converge/scripts/request_copilot_review.py +71 -0
  76. package/skills/pr-converge/scripts/resolve_pr_head.py +58 -0
  77. package/skills/pr-converge/scripts/review_field_helpers.py +43 -0
  78. package/skills/pr-converge/scripts/test_check_pr_mergeability.py +126 -0
  79. package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +22 -0
  80. package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +342 -0
  81. package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +220 -0
  82. package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +372 -0
  83. package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +280 -0
  84. package/skills/pr-converge/scripts/test_mark_pr_ready.py +69 -0
  85. package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +236 -0
  86. package/skills/pr-converge/scripts/test_post_bugbot_run.py +195 -0
  87. package/skills/pr-converge/scripts/test_reply_to_inline_comment.py +159 -0
  88. package/skills/pr-converge/scripts/test_request_copilot_review.py +101 -0
  89. package/skills/pr-converge/scripts/test_resolve_pr_head.py +79 -0
  90. package/skills/pr-converge/scripts/test_review_field_helpers.py +80 -0
  91. package/skills/pr-converge/scripts/test_trigger_bugbot.py +139 -0
  92. package/skills/pr-converge/scripts/test_view_pr_context.py +111 -0
  93. package/skills/pr-converge/scripts/trigger_bugbot.py +77 -0
  94. package/skills/pr-converge/scripts/view_pr_context.py +47 -0
  95. package/skills/pr-converge/test_team_lifecycle.py +47 -0
  96. package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +108 -0
  97. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +37 -0
  98. package/skills/qbug/SKILL.md +4 -4
  99. package/skills/qbug/test_qbug_skill_post_fix_audit.py +2 -2
  100. package/skills/resume-review/SKILL.md +261 -0
  101. package/skills/bugteam/scripts/README.md +0 -58
  102. package/skills/bugteam/scripts/_claude_permissions_common.py +0 -219
  103. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +0 -633
  104. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +0 -260
  105. package/skills/bugteam/scripts/bugteam_preflight.py +0 -201
  106. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +0 -17
  107. package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -109
  108. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -135
  109. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +0 -271
  110. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -267
  111. package/skills/bugteam/scripts/test_bugteam_preflight.py +0 -189
  112. package/skills/bugteam/scripts/test_claude_permissions_common.py +0 -44
  113. /package/skills/{bugteam → pr-converge}/scripts/config/__init__.py +0 -0
@@ -2,70 +2,354 @@
2
2
  name: pr-converge
3
3
  description: >-
4
4
  Drives the current PR to convergence by alternating Cursor Bugbot and the
5
- in-house bugteam audit. Each invocation runs one tick of work in the main
6
- session: fetches the latest reviewer state, applies TDD fixes for any
7
- findings, pushes one commit per tick, replies inline, and re-triggers the
8
- reviewer. To loop automatically, invoke as `/loop /pr-converge` — the /loop
9
- skill self-paces re-entry via ScheduleWakeup. Convergence requires a
10
- back-to-back clean cycle (bugbot CLEAN immediately followed by bugteam CLEAN
11
- with no intervening fixes), at which point the PR is flipped to ready for
12
- review and the loop terminates. Triggers: '/pr-converge', 'drive PR to
13
- convergence', 'loop bugbot and bugteam', 'babysit bugbot and bugteam',
14
- 'until both are clean', 'converge this PR'.
5
+ second audit (**bugteam** always `Skill({skill: "bugteam", ...})` when the host
6
+ exposes `Skill`; bugteam `SKILL.md` **Path routing** picks Path A vs Path B from
7
+ `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`; per-path harness in
8
+ `bugteam/reference/workflow-path-a-orchestrated-teams.md` and
9
+ `bugteam/reference/workflow-path-b-task-harness.md`). Each invocation runs one
10
+ tick of work in the main session: fetches the latest reviewer state, applies
11
+ TDD fixes for any findings, pushes one commit per tick, replies inline (or
12
+ delegates fixes per §Multi-PR orchestration model), and re-triggers reviewers.
13
+ Default behavior loops until back-to-back clean: pace the next tick with
14
+ ScheduleWakeup when the harness exposes it, otherwise use the AHK
15
+ auto-continue driver (see workflows/ahk-auto-continue-loop.md). Pacing details
16
+ live in workflows next to SKILL.md — load exactly one per Step 4.
17
+ Convergence requires four gates on the same HEAD: (1) a back-to-back clean
18
+ cycle (bugbot CLEAN immediately followed by second-audit CLEAN with no
19
+ intervening fixes), (2) no outstanding Copilot reviewer findings, (3)
20
+ `mergeStateStatus == CLEAN` with `mergeable == MERGEABLE` (a `DIRTY`
21
+ state triggers the `rebase` skill; non-CLEAN non-DIRTY states are hard
22
+ blockers), and (4) the post-convergence Copilot review request returns
23
+ clean — or, if it surfaces findings, the PR is still flipped ready and a
24
+ follow-up draft PR is opened off the converged HEAD with those findings as
25
+ a checklist. After all gates pass the PR is flipped to ready for review and
26
+ the loop terminates.
27
+ Multi-PR runs persist traffic in `<TMPDIR>/pr-converge-<session_id>/state.json`
28
+ per §Multi-PR orchestration model; single-PR-only runs may use the conversation
29
+ state line instead. Triggers: '/pr-converge', 'drive PR to
30
+ convergence', 'loop bugbot and bugteam',
31
+ 'babysit bugbot and bugteam', 'until both are clean', 'converge this PR'.
15
32
  ---
16
33
 
17
34
  # PR Converge
18
35
 
19
- Runs one tick of the bugbot ↔ bugteam convergence loop in the main session. Designed to be invoked under `/loop /pr-converge` so the parent's ScheduleWakeup paces re-entry. Self-terminates the loop on convergence (back-to-back clean) by flipping the PR to ready for review and omitting the next ScheduleWakeup.
36
+ Each **invocation** runs **one tick** of the bugbot ↔ second-audit loop in the **parent session** (fetch state, address findings under the Fix
37
+ protocol when needed, at most one fix commit per tick, inline replies or teammate handoffs, Bugbot re-trigger rules in Step 2 / Step 3).
38
+ **By default** the skill **keeps going** until back-to-back clean on the same `HEAD`: after each tick, **Step 4** schedules the next tick with
39
+ `ScheduleWakeup` when the tool exists, otherwise uses the **AHK auto-continue** driver. On convergence, mark the PR ready (`gh pr ready` or
40
+ `mark_pr_ready.py` per §Step 2), then **stop all pacing** (omit further `ScheduleWakeup`; stop the AHK auto-typer when that fallback was in use).
41
+ Default entry is **`/pr-converge`** (loops per Step 4). When the host exposes `ScheduleWakeup`, wakeups use `prompt: "/pr-converge"` unless the
42
+ harness requires the `/loop` wrapper for wakeup execution (`workflows/schedule-wakeup-loop.md`).
20
43
 
21
- ## Why the work runs in the main session, not a background subagent
44
+ ## Table of contents
22
45
 
23
- `ScheduleWakeup` is a primitive of the parent harness; it is not exposed to `general-purpose` subagents. A prior version of this skill spawned a background subagent and instructed it to call `ScheduleWakeup` at the end of each tick. The subagent's tool registry returned "No matching deferred tools found" for `ScheduleWakeup`, so the loop could never self-perpetuate — it ran exactly one tick and stalled. Running the loop in the main session via `/loop /pr-converge` puts the work on the same harness that owns `ScheduleWakeup`, eliminating that failure mode.
46
+ 1. [Parent session](#parent-session)
47
+ 2. [Pacing workflows (load exactly one)](#pacing-workflows-load-exactly-one)
48
+ 3. [State across ticks](#state-across-ticks)
49
+ 4. [Per-tick work](#per-tick-work)
50
+ - [Step 1: Resolve current HEAD and PR context](#step-1-resolve-current-head-and-pr-context)
51
+ - [Step 2: Branch on `phase`](#step-2-branch-on-phase)
52
+ - [Convergence gates](#convergence-gates)
53
+ - [Step 3: Re-trigger bugbot](#step-3-re-trigger-bugbot)
54
+ - [Step 4: Loop pacing](#step-4-loop-pacing)
55
+ 5. [Fix protocol](#fix-protocol)
56
+ 6. [Stop conditions](#stop-conditions)
57
+ 7. [Ground rules](#ground-rules)
58
+ 8. [Examples](#examples)
24
59
 
25
- ## When this skill applies
60
+ ## Parent session
26
61
 
27
- The user is on a PR branch and wants both reviewers — Cursor's Bugbot AND the in-house `/bugteam` audit to keep re-reviewing after each push, with findings auto-addressed between ticks. The PR stays in draft until convergence; on convergence the skill flips it to ready for review.
62
+ Use this skill on a **draft PR** where **Cursor Bugbot** and the **`/bugteam`** audit should **re-run after each push**, with
63
+ **findings fixed between rounds**, until **back-to-back clean** on the same `HEAD`; then **mark the PR ready for review**.
64
+
65
+ Run **every converge tick** in the **parent harness session** (the conversation where the user invoked `/pr-converge`).
66
+ **Loop pacing** (how the next tick is scheduled) is split into two workflow files — load **exactly one** per **Step 4**; see
67
+ [Pacing workflows](#pacing-workflows-load-exactly-one).
68
+
69
+ This skill **complements** **bugteam** (same skill, **team** vs **background-agent** workflow per §Second-audit execution): it sequences Bugbot
70
+ re-reviews, second-audit runs, the Fix protocol, and inline replies or teammate handoffs between pushes until back-to-back clean. On every
71
+ BUGTEAM tick, run **bugteam** — never a hand-rolled substitute audit. **Fix protocol** production
72
+ edits in the **main Cursor session** use **`Task`** with **`subagent_type: "generalPurpose"`** plus the clean-coder **Read** preamble in the **`prompt`**
73
+ (see [Fix protocol](#fix-protocol)) - Cursor does not accept `subagent_type: "clean-coder"`. When **`state.json`** drives multi-PR orchestration,
74
+ the **`clean-coder` teammate** path in that model is unchanged. **Loop pacing** stays
75
+ in the **main** session when this host exposes `ScheduleWakeup`; otherwise use the AHK workflow file row below.
76
+
77
+ ## Pacing workflows (load exactly one)
78
+
79
+ Before **Step 4** on each tick, **only the parent session** that is executing this skill's Step 4 (not a `Task` / `Explore` child) picks **one**
80
+ workflow row using the steps below, then **Use the Read tool** on that row's file path next to this skill's `SKILL.md` (installed copies usually
81
+ live under `$HOME/.claude/skills/pr-converge/`):
82
+
83
+ 1. **Open the tool inventory for this turn** — every function or tool **name** the harness allows **this** assistant to invoke **in this message**
84
+ (the same catalog that lists companions such as `Read`, `Task`, and your harness's shell or terminal tool).
85
+ 2. **`ScheduleWakeup` is invokable** if that catalog contains a **top-level** callable entry whose name is exactly **`ScheduleWakeup`**. If the
86
+ catalog lists only indirect gateways (for example **`call_mcp_tool`** with server-qualified MCP tools inside descriptors), **`ScheduleWakeup` is
87
+ not invokable** here unless **`ScheduleWakeup`** itself still appears as its own invocable name in that same catalog — when in doubt, **not**
88
+ invokable.
89
+ 3. **Pick the table row** — invokable → **`ScheduleWakeup` available**; otherwise → **`ScheduleWakeup` not available** (includes missing, empty, or
90
+ unreadable catalogs: **fail closed** to the AHK workflow; do **not** attempt `ScheduleWakeup`).
91
+
92
+ | Route | Read this file |
93
+ | --- | --- |
94
+ | `ScheduleWakeup` available | `workflows/schedule-wakeup-loop.md` |
95
+ | `ScheduleWakeup` not available | `workflows/ahk-auto-continue-loop.md` |
96
+
97
+ All pacing-specific instructions for that route — delays, prompts, AHK setup, `continue` handling, convergence cleanup for the auto-typer,
98
+ inline-lag pacing split, and route-only gotchas — live **only** in that workflow file. This `SKILL.md` keeps shared bugbot / second-audit / Fix
99
+ protocol / stop rules.
100
+
101
+ - **`/pr-converge`** (default): loops until convergence. After each tick (unless converged or stopped), run **Step 4**, which starts by loading
102
+ the correct workflow row from the table above.
103
+
104
+ ## Progressive disclosure (skill folder)
105
+
106
+ This skill is a **folder** (`SKILL.md` plus `scripts/` plus `workflows/`): wrappers centralize gh pagination and body-file rules so the model composes orchestration instead of re-deriving CLI footguns. Read in this order ([Anthropic — internal patterns for Claude Code skills](https://x.com/trq212/status/2033949937936085378)):
107
+
108
+ 1. This `SKILL.md` — phase graph, teammate contracts, stop conditions.
109
+ 2. [`scripts/README.md`](scripts/README.md) — argv, stdout JSON shapes, pointers to `../../rules/gh-paginate.md` and `../../rules/gh-body-file.md`.
110
+ 3. [`../bugteam/reference/workflow-path-b-task-harness.md`](../bugteam/reference/workflow-path-b-task-harness.md) **on demand** — bugteam **Path B** harness only (read after bugteam `SKILL.md` **Path routing** selects Path B). Path A harness: [`../bugteam/reference/workflow-path-a-orchestrated-teams.md`](../bugteam/reference/workflow-path-a-orchestrated-teams.md).
111
+ 4. Individual script source or `--help` — only when a call fails or `${CLAUDE_SKILL_DIR}` resolves unexpectedly.
112
+
113
+ Taxonomy: **CI/CD & Deployment** in the [`babysit-pr` archetype](https://x.com/trq212/status/2033949937936085378) — monitors a PR, applies fixes between reviewer ticks, and flips it ready-for-review on convergence. If the doc feels broad, use **§Multi-PR orchestration model** as the workflow spine and **§Per-tick work** as the single-PR linearization.
114
+
115
+ ## Gotchas
116
+
117
+ Non-default behaviors worth burning in; add a bullet here when a real run fails in a new way ([same source](https://x.com/trq212/status/2033949937936085378)):
118
+
119
+ - **`ScheduleWakeup` is not in subagent tool registries** — a background `general-purpose` tick cannot schedule the next re-entry; only the parent session where this skill runs with `ScheduleWakeup` in the tool registry can call it.
120
+ - **Bugbot only recognizes the literal re-trigger phrase `bugbot run`** — other comment text no-ops; prefer `trigger_bugbot.py` (temp body file) or
121
+ the bundled `packages/claude-dev-env/skills/pr-converge/scripts/post-bugbot-run.ps1` so backticks in prose never corrupt the PR comment.
122
+ - **Review body and inline comments can desync for the same `commit_id`** — “dirty body, zero inline rows at `current_head`” is **`inline_lag`**, not **`dirty`**; bump `inline_lag_streak`, wait 60s, retry fetch (Step 2 BUGBOT fourth branch; §Fix result → general-purpose steps 4c–4e).
123
+ - **`state.json` without the §Concurrency lock loses merges** when several teammates finish in one wall-clock window.
124
+ - **`tick_count` must not double-increment** — conversation line (Step 1) only when **no** `state.json`; with `state.json`, only the orchestrator bump in §Orchestrator `state.json` writes increments `tick_count`.
125
+ - **Back-to-back clean is necessary but not sufficient — `mergeStateStatus` gates the ready flip** — a PR can be back-to-back clean (bugbot CLEAN ∧ bugteam CLEAN at the same HEAD) yet still have merge conflicts with the base branch. Before flipping ready, run `check_pr_mergeability.py` and confirm `mergeStateStatus == "CLEAN"` AND `mergeable == "MERGEABLE"`. When `mergeStateStatus == "DIRTY"` (or `mergeable == "CONFLICTING"`), invoke the **`rebase`** skill ([`../rebase/SKILL.md`](../rebase/SKILL.md), Phase 1–4); after a successful rebase + force-with-lease push, the new HEAD invalidates prior clean state — reset `bugbot_clean_at = null`, `copilot_clean_at = null`, transition `phase = BUGBOT`, retrigger bugbot, schedule next tick. Non-`CLEAN` non-`DIRTY` states (`BLOCKED`, `BEHIND`, `UNKNOWN`) are hard blockers per §Stop conditions.
126
+ - **Copilot findings on `current_head` block convergence** — Copilot (`copilot-pull-request-reviewer[bot]`) findings are evaluated *after* bugbot CLEAN ∧ bugteam CLEAN at the same HEAD. When `fetch_copilot_reviews.py` returns a review at `current_head` whose `state == "CHANGES_REQUESTED"` (or `state == "COMMENTED"` with a non-empty body) and there are unaddressed inline findings from `fetch_copilot_inline_comments.py`, treat the result as a Fix protocol input (same shape as bugbot dirty): TDD fix → push → reply inline → reset `bugbot_clean_at = null` AND `copilot_clean_at = null` → transition `phase = BUGBOT` → retrigger bugbot → schedule. The full back-to-back clean cycle must be met again. If no Copilot review exists on `current_head` yet, this gotcha does **not** apply — the proactive request happens in §Convergence gates step (c).
127
+ - **Post-convergence Copilot request runs once, regardless of outcome** — after every other gate passes (bugbot CLEAN ∧ bugteam CLEAN ∧ no outstanding Copilot findings on HEAD ∧ `mergeStateStatus == "CLEAN"`), call `request_copilot_review.py` and wait one tick. A clean Copilot review marks the PR ready and terminates. A Copilot review with findings on `current_head` still marks the PR ready (this is the "we still allow it to be 'clean'" rule), but before terminating runs `open_followup_copilot_pr.py` to capture the findings as a draft PR off `current_head`. The follow-up PR runs its own `/pr-converge` cycle (queued for the user — never inline-spawn another converge loop in the same session). The reviewer ID literal is `copilot-pull-request-reviewer[bot]` with the `[bot]` suffix — `Copilot`, `copilot`, and `github-copilot` all silently no-op per [`../copilot-review/SKILL.md`](../copilot-review/SKILL.md).
128
+
129
+ ## Second-audit execution (bugteam — Path A vs Path B)
130
+
131
+ The **second audit** (BUGTEAM phase) is **always** the **bugteam** skill: preflight, CODE_RULES gate, **`code-quality-agent`** / **`clean-coder`** loop,
132
+ audit rubric, outcome shape, and Step 2 BUGTEAM §(b)–(d) contract all live in [`../bugteam/SKILL.md`](../bugteam/SKILL.md) plus `PROMPTS.md` /
133
+ `EXAMPLES.md` / `CONSTRAINTS.md` — do not re-spec them here.
134
+
135
+ **Path routing is bugteam-internal:** [bugteam `SKILL.md` — Path routing](../bugteam/SKILL.md#path-routing-mandatory-first-branch) (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` equals **`1`** → Path A orchestrated teams; otherwise → Path B Task harness). **Harness-only** execution: Path A — [`../bugteam/reference/workflow-path-a-orchestrated-teams.md`](../bugteam/reference/workflow-path-a-orchestrated-teams.md); Path B — [`../bugteam/reference/workflow-path-b-task-harness.md`](../bugteam/reference/workflow-path-b-task-harness.md).
136
+
137
+ **pr-converge rule:** Prefer **`Skill({skill: "bugteam", args: "<PR URL or args>"})`** wherever the tool registry exposes `Skill` — bugteam executes the correct path. When **`Skill` is not invokable** (typical delegated teammate), that worker still runs **bugteam** by loading **`../bugteam/SKILL.md`** from the same checkout and following **Path routing** plus [`../bugteam/reference/workflow-path-b-task-harness.md`](../bugteam/reference/workflow-path-b-task-harness.md) when Path B applies; never replace bugteam with a hand-rolled audit.
138
+
139
+ ### Team infrastructure detection (for pr-converge pacing and docs cross-links only)
140
+
141
+ This mirrors bugteam **Path routing** so pr-converge prose stays aligned with host capability checks elsewhere in this file:
142
+
143
+ - **`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` == `1`** (trimmed) → bugteam **Path A** when `/bugteam` runs inside Claude Code with teams.
144
+ - **Otherwise** → bugteam **Path B** Task harness inside the same bugteam `SKILL.md` contract.
145
+
146
+ ## Multi-PR orchestration model
147
+
148
+ ### Core rule: orchestrator is a traffic controller only
149
+
150
+ The orchestrator (main session) **never** reads **repository source files**, writes code, audits findings, or does any per-PR **codebase** work inline. It **always** reads `state.json` for traffic state and may write only the narrow fields in §Orchestrator `state.json` writes; it receives teammate handoffs and spawns the next worker. Every unit of audit/fix work runs inside a dedicated teammate.
151
+
152
+ This is a [workflow-style skill](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices#use-workflows-for-complex-tasks): the orchestrator decomposes the multi-PR problem into parallel per-PR subworkflows, each owned by a short-lived teammate. The orchestrator's only job is to keep the state file consistent and spawn the next agent in each chain.
153
+
154
+ ### Per-PR state file
155
+
156
+ Create once at session start; each teammate writes its result back before going idle:
157
+
158
+ **Path:** `<TMPDIR>/pr-converge-<session_id>/state.json`
159
+
160
+ **Session ID:** `YYYYMMDDHHMMSS` captured once when the loop starts.
161
+
162
+ **Directory lifecycle:** Keep `<TMPDIR>/pr-converge-<session_id>/` for the **whole converge run** (every tick) until **each** `prs[...]` is
163
+ **`converged`** or **`blocked`**, or the user stops. **Then** delete that folder if you want reclaim — **`mark_pr_ready.py` / `gh pr ready`** on
164
+ GitHub is the canonical record of ready state. See [Memory](#memory) for the optional append-only log in the same directory.
165
+
166
+ **Barebones schema:**
167
+
168
+ ```json
169
+ {
170
+ "session_id": "20260502050000",
171
+ "team_name": "bugteam-20260502050000",
172
+ "prs": {
173
+ "289": {
174
+ "owner": "jl-cmd",
175
+ "repo": "claude-code-config",
176
+ "branch": "feat/shared-pr-loop-extraction",
177
+ "phase": "BUGBOT",
178
+ "current_head": "f9a7d49e",
179
+ "bugbot_clean_at": null,
180
+ "inline_lag_streak": 0,
181
+ "tick_count": 5,
182
+ "last_action": "bugbot_triggered",
183
+ "status": "in_progress",
184
+ "last_updated": "2026-05-02T10:00:00Z"
185
+ }
186
+ }
187
+ }
188
+ ```
189
+
190
+ **`team_name` field (Path A only):** when `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`, the orchestrator owns a single long-lived team for the whole sweep — see §Orchestrator team lifecycle.
191
+
192
+ **`status` values:** `fresh` | `in_progress` | `awaiting_bugbot` | `awaiting_bugteam` | `converged` | `blocked`
193
+
194
+ **Write rule:** Teammates write their result by reading the current file, merging **only** their PR's keyed entry under `prs`, and persisting the merged document back. Writes are keyed on `pr_number`; other PRs' entries are untouched in the merge logic — **but** see **Concurrency** below so parallel teammates never clobber each other.
195
+
196
+ **Concurrency (mandatory):** When multiple teammates can finish in the same wall-clock window (including the case where **10+** idle notifications arrive together), a naive read–modify–write on `state.json` **loses updates** (two writers read the same revision; the second `write` overwrites the first). Every `state.json` update from a teammate **must** use **serialized access** plus **atomic publish**:
197
+
198
+ 1. **Acquire** an exclusive lock in the same directory as `state.json`, for example a sibling path `state.json.lock` created with an **atomic create-only** primitive (`mkdir` on Unix when the path does not exist; on Windows `New-Item` / `md` guarded so only one creator succeeds, or a host file lock API). If acquisition fails because the lock exists, sleep with jitter and **retry** until held (cap retries and escalate per **Stop conditions** if the lock never clears — indicates a stuck teammate).
199
+ 2. **Read** `state.json`, merge this teammate's `prs[<pr_number>]` object only, then **write** the full merged JSON to `state.json.tmp` in that directory.
200
+ 3. **Replace** `state.json` atomically from `state.json.tmp` (`os.replace` / same-volume rename semantics so readers never see a half-written file).
201
+ 4. **Release** the lock (`rmdir` / `Remove-Item` on the lock path).
202
+
203
+ **Orchestrator `state.json` writes (traffic metadata only):** Teammates own audit/fix payloads. The orchestrator **must not** merge finding bodies, file contents, or teammate-owned fields other than the two narrow exceptions below. It **must** use the **same §Concurrency lock** for any orchestrator write.
204
+
205
+ 1. **Per-tick `tick_count` bump (mandatory):** At the **start** of each orchestrator tick, before spawning teammates for that tick, perform one locked read–merge–atomic publish: for **every** `prs[<pr_number>]` whose `status` is **not** `converged` or `blocked`, increment `tick_count` by **1** (initialize to `0` if missing) and refresh `last_updated`. This is for human-readable progress only — there is **no** tick ceiling; the loop ends only on convergence or a **Stop conditions** branch.
206
+ 2. **`phase` when only the orchestrator decides:** If the orchestrator applies a **Step 2 §Per-tick** phase transition (including **BUGTEAM §(d)** branches that set `phase = BUGBOT` without an immediate teammate `state.json` write) and no teammate merge occurs in the same tick for that PR, the orchestrator performs one locked merge that sets only `prs[<pr_number>].phase` (and `last_updated`) for the affected PR.
207
+
208
+ **Orchestrator reads this file at the start of every tick** instead of relying on conversation context for cross-PR state.
209
+
210
+ ### Orchestrator team lifecycle (Path A only)
211
+
212
+ **Applies when bugteam Path A is in use** (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`). When agent teams are disabled, this section is skipped and bugteam runs Path B with no team state. See [Team infrastructure detection](#team-infrastructure-detection-for-pr-converge-pacing-and-docs-cross-links-only) for the routing rule.
213
+
214
+ **Why the orchestrator owns the team:** The bugteam skill's per-invocation `TeamCreate` / `TeamDelete` cycle assumes one bugteam invocation per session. In multi-PR converge, the orchestrator runs bugteam **per PR per BUGTEAM tick** — many invocations across the sweep. If each invocation tried to `TeamCreate`, the second one would fail with `Already leading team "<existing>"`; if each invocation called `TeamDelete` at exit, the next BUGTEAM tick would have nothing to attach to and would also create a fresh team that races other PRs' work. The orchestrator instead creates one team for the whole sweep and tears it down on full convergence — see [bugteam Team lifecycle](../bugteam/SKILL.md#team-lifecycle-path-a-only).
215
+
216
+ **At session start (before the first tick spawns any teammate):**
217
+
218
+ 1. Compute `team_name = "bugteam-<session_id>"` using the same `session_id` as the §Per-PR state file.
219
+ 2. `TeamCreate(team_name=<team_name>, description="pr-converge sweep <session_id>", agent_type="team-lead")`. The orchestrator becomes the lead.
220
+ 3. Locked write to `state.json` (per §Concurrency): merge `team_name` at the document root.
221
+
222
+ **At every BUGTEAM tick (per PR):** invoke bugteam in attach mode by setting both env vars before the call:
223
+
224
+ - `BUGTEAM_TEAM_LIFECYCLE=attach`
225
+ - `BUGTEAM_TEAM_NAME=<state.team_name>`
226
+
227
+ When the orchestrator drives bugteam via `Skill({skill: "bugteam", ...})`, set both env vars in the parent process before the `Skill` invocation. When bugteam runs inside a delegated worker (typical multi-PR fan-out), the spawn prompt must export the same two env vars at the top of the worker's bash environment so bugteam reads them on entry.
228
+
229
+ **Teardown (only when every PR is terminal):** the orchestrator scans `state.json` and considers the sweep done when **every** `prs[<pr_number>].status` is either `converged` or `blocked`. At that point, and only at that point:
230
+
231
+ 1. `TeamDelete()` (orchestrator is the lead; no arguments).
232
+ 2. Locked write to `state.json`: clear `team_name` from the document root (so a stale value cannot leak into a follow-up sweep).
233
+ 3. Continue with the §Memory cleanup of `<TMPDIR>/pr-converge-<session_id>/`.
234
+
235
+ A user-stop or hard-blocker exit that ends the sweep before convergence still calls `TeamDelete()` here, because the orchestrator is shutting down. The only path that does **not** call `TeamDelete()` is "tick scheduled, sweep continuing" — which is the common case.
236
+
237
+ ### Teammate spawning rules
238
+
239
+ When the orchestrator receives results from one or more PRs simultaneously (e.g. 10+ teammate idle notifications arrive together), it spawns one new agent **per PR** in a single parallel message — never processes any PR inline.
240
+
241
+ #### Audit result → fix worker per PR
242
+
243
+ When a bugfind teammate reports completion (findings or clean):
244
+
245
+ - Spawn **one fix worker per PR** with findings (Claude Code: `clean-coder` teammate / `Agent`; Cursor `Task`: `generalPurpose` + clean-coder **Read** preamble per [Fix protocol](#fix-protocol)). That worker:
246
+ 1. Reads the outcomes XML for the PR.
247
+ 2. Applies TDD fixes (test first, then production code).
248
+ 3. Commits and pushes one fix commit.
249
+ 4. Replies inline to each addressed finding comment via `reply_to_inline_comment.py`.
250
+ 5. **Writes its result to `state.json`** (per §Concurrency) (`last_action: "fix_pushed"`, `current_head: <new SHA>`, `bugbot_clean_at: null`, `phase: "BUGBOT"`, `status: "awaiting_bugbot"`, `last_updated` as an ISO-8601 UTC timestamp).
251
+ 6. Goes idle.
252
+
253
+ - For PRs with zero findings: spawn **one `general-purpose` agent** per PR. That agent:
254
+ 1. If `bugbot_clean_at == current_head` (back-to-back clean): run `mark_pr_ready.py`, append one convergence row to `<TMPDIR>/pr-converge-<session_id>/converged.log` per §Memory (same `session_id` as `state.json`), then **write `state.json`** (per §Concurrency) setting this PR's entry to at least `status: "converged"`, `last_action: "converged"` (or `marked_ready`), `phase: "BUGBOT"`, and `last_updated` to an ISO-8601 UTC timestamp — **before** going idle. Omitting this write leaves the orchestrator on later ticks with a stale `awaiting_bugteam` / `in_progress` row and risks duplicate work.
255
+ 2. Otherwise: update `state.json` (per §Concurrency) with `last_action: "audit_clean"`, `status: "awaiting_bugbot"`, `phase: "BUGBOT"`, then trigger bugbot via `trigger_bugbot.py`.
256
+ 3. Goes idle.
257
+
258
+ #### Fix result → general-purpose per PR
259
+
260
+ When a bugfix (clean-coder) teammate goes idle after pushing a fix:
261
+
262
+ - Spawn **one `general-purpose` agent** per PR. That agent:
263
+ 1. Reads `state.json` for its PR.
264
+ 2. Triggers bugbot via `trigger_bugbot.py`.
265
+ 3. Polls `fetch_bugbot_reviews.py` every 60s (up to 10 polls) until a review anchored to `current_head` appears.
266
+ 4. **Poll / classify loop** (repeat from **4a** whenever **4c** schedules a retry):
267
+ - **4a.** Fetches inline comments via `fetch_bugbot_inline_comments.py`.
268
+ - **4b.** Classify — same three outcomes as Step 2 BUGBOT once a review exists at `current_head`:
269
+ - **`clean`:** Review body indicates clean against `current_head` and zero unaddressed inline findings.
270
+ - **`dirty`:** At least one unaddressed inline finding for `current_head` (actionable for the Fix protocol / `clean-coder`).
271
+ - **`inline_lag`:** Review body indicates findings against `current_head`, but the inline-comments API returns zero matching comments for `current_head` (transient desync between review body and inline API — Step 2 BUGBOT fourth bullet).
272
+ - **4c.** **If `inline_lag`:** Locked merge to `state.json` (per §Concurrency): increment `inline_lag_streak` (treat missing as `0` before increment); set `last_action: "inline_lag_wait"`, `phase: "BUGBOT"`, `last_updated`, and keep `status` consistent with monitoring (for example `awaiting_bugbot`). If `inline_lag_streak >= 3`, **hard blocker** per §Stop conditions (structurally inconsistent review); report and go idle **without** classifying as `dirty`. Otherwise sleep **60 seconds** and repeat from **4a** (re-fetch inline only — do not re-run step 2 or step 3).
273
+ - **4d.** **If `clean`:** Exit the loop. Locked merge: set `bugbot_clean_at` to `current_head`, reset `inline_lag_streak` to `0`, update `last_action`, `status`, and **`phase`: `BUGTEAM`** (next work is second audit).
274
+ - **4e.** **If `dirty`:** Exit the loop. Locked merge: reset `inline_lag_streak` to `0`, record findings count, update `last_action`, `status`, and **`phase`: `BUGBOT`** (next work is another fix pass).
275
+ 5. Reports back to orchestrator: one-line summary of outcome.
276
+
277
+ - Orchestrator reads the updated `state.json` and spawns the appropriate next agent:
278
+ - Result `clean` → spawn a `general-purpose` agent to run BUGTEAM phase (**bugteam** via `Skill` when available in that worker’s registry, else inline bugteam `SKILL.md` + Path B deltas per §Second-audit execution).
279
+ - Monitor exited on **`dirty` (step 4e)** with actionable inline threads → spawn the same **fix worker** (same as "audit result with findings" above). Do **not** spawn `clean-coder` when the monitor only saw **`inline_lag`** (4c retries) without reaching **4e** — that path retries or escalates via the **`inline_lag_streak` ≥ 3** hard blocker in **Stop conditions** instead of a fix pass.
280
+
281
+ ### What the orchestrator does per tick
282
+
283
+ 1. Perform the **per-tick `tick_count` bump** in §Orchestrator `state.json` writes (traffic metadata only) for every non-terminal PR under `prs`.
284
+ 2. Read `state.json`.
285
+ 3. For each PR with new teammate results (idle notifications), spawn the next agent per the rules above — all in one parallel message.
286
+ 4. Re-read `state.json` if needed for scheduling.
287
+ 5. Call `ScheduleWakeup` with the appropriate delay.
288
+ 6. Nothing else.
289
+
290
+ ## Memory
291
+
292
+ **Run directory** `<TMPDIR>/pr-converge-<session_id>/` (same `session_id` as §Per-PR state file) holds **`state.json`** and optional **`converged.log`**.
293
+ Treat both as **durable for this converge run**: keep them from first create through **every tick** until **each** PR under `prs` is **`converged`**
294
+ or **`blocked`**, or a **Stop conditions** branch ends the loop. **After** that, deleting the whole directory is safe — **`mark_pr_ready.py` /
295
+ `gh pr ready`** on GitHub is the canonical record of ready state. This skill is a **folder skill**, not a Cursor plugin package; do **not** rely on
296
+ `${CLAUDE_PLUGIN_DATA}`. OS or disk cleanup of `<TMPDIR>` (reboot, policy) can still remove files mid-run; that is environmental risk, not intentional
297
+ behavior of this spec.
298
+
299
+ **`converged.log` (multi-PR only — requires `state.json`):**
300
+
301
+ - **Path:** `<TMPDIR>/pr-converge-<session_id>/converged.log` (sibling of `state.json`).
302
+ - **Format:** one tab-separated row per converged PR — `<ISO8601_UTC>\t<owner>/<repo>#<number>\tbugbot=<SHA>\t<SECOND_AUDIT_LABEL>=<SHA>` where `<SECOND_AUDIT_LABEL>` is always `bugteam` (second audit is the bugteam skill) per §Second-audit execution.
303
+ - **Append site:** the agent that runs `mark_pr_ready.py` (see §Audit result → general-purpose convergence branch and Step 2 BUGTEAM second branch). Append **before** the locked `state.json` publish so the log row survives a failed or retried merge.
304
+ - **Never read inside the loop.** The orchestrator and teammates never gate behavior on this file; it is for the user and follow-up tooling only.
305
+
306
+ **Single-PR runs without `state.json`:** do **not** append `converged.log`; the in-conversation summary plus GitHub ready state are enough.
28
307
 
29
308
  ## Invocation modes
30
309
 
31
- - **`/loop /pr-converge`** (recommended): loops automatically. The /loop skill runs each tick and uses ScheduleWakeup to pace re-entry. Termination on convergence is automatic; the skill omits the next wakeup at the convergence tick.
32
- - **`/pr-converge`** (manual): runs exactly one tick and returns. Useful for ad-hoc state checks or for advancing the loop one step manually. The user re-runs the skill (or wraps it in `/loop`) to continue.
310
+ - **`/pr-converge`** (default): runs **one tick**, then **Step 4** per [Pacing workflows](#pacing-workflows-load-exactly-one) — same loop-until-convergence semantics whether the user typed it once, a `ScheduleWakeup` fires with `prompt: "/pr-converge"`, or AHK sends `continue` (`workflows/schedule-wakeup-loop.md`, `workflows/ahk-auto-continue-loop.md`). Omit the next wakeup only on convergence or another **Stop conditions** branch.
311
+ - **`/loop /pr-converge`**: optional **harness wrapper** when the parent only executes wakeup `prompt`s that are routed through the `/loop` skill; behavior is equivalent to default `/pr-converge` for per-tick work and Step 4. Use `prompt: "/loop /pr-converge"` in `ScheduleWakeup` only when that wrapper is required for the next firing to run.
33
312
 
34
313
  ## State across ticks
35
314
 
36
- Track the following in plain text in the assistant's response so subsequent ticks can re-read it from conversation context:
315
+ **Dual persistence:** When `<TMPDIR>/pr-converge-<session_id>/state.json` exists (multi-PR or file-backed session per §Multi-PR orchestration model),
316
+ the orchestrator and teammates treat **that file** as the source of truth for `phase`, heads, counters, and status — not the conversation
317
+ transcript. When **no** `state.json` is in use (typical single-PR `/pr-converge` in Cursor), track the following **in each assistant turn as plain
318
+ text** so the **next tick that resumes in this transcript** can re-read them from conversation context:
37
319
 
38
320
  - `phase`: `BUGBOT` or `BUGTEAM`. Start in `BUGBOT` on the first tick of a fresh loop.
39
321
  - `bugbot_clean_at`: the HEAD SHA at which bugbot last reported clean, or `null`. Reset to `null` whenever a new commit is pushed.
40
- - `inline_lag_streak`: integer counter, initialized to `0`. Tracks consecutive ticks where bugbot's review body indicates findings against `current_head` but the inline-comments API returns zero matching comments. Reset to `0` on any other branch outcome.
41
- - `tick_count`: integer, initialized to `0`. Increment on every tick to enforce the safety cap.
322
+ - `inline_lag_streak`: integer counter, initialized to `0`. Tracks consecutive ticks where bugbot's review body indicates findings against
323
+ `current_head` but the inline-comments API returns zero matching comments. Reset to `0` on any other branch outcome.
324
+ - `tick_count`: integer, initialized to `0`. Increment on every tick (observability only; no ceiling).
42
325
 
43
- Each tick begins by reading the prior tick's state line from the most recent assistant message and ends by emitting the updated state line.
326
+ Each tick begins by reading the prior tick's state line from the most recent assistant message (when **no** `state.json`) and ends by emitting the
327
+ updated state line; when `state.json` is in use, follow §What the orchestrator does per tick instead.
44
328
 
45
329
  ## Per-tick work
46
330
 
47
331
  ### Step 1: Resolve current HEAD and PR context
48
332
 
49
- Read the prior tick's state line from the most recent assistant message (or initialize all fields if none). **Increment `tick_count` by 1.** This is the increment referenced in the **State across ticks** section; without it the safety cap (Step 3.5, §Safety cap) never fires.
333
+ Read the prior tick's state line from the most recent assistant message (or initialize all fields if none). **Increment `tick_count` by 1** in the **conversation state line** when **no** `state.json` is in use (single-PR-only invocation); when `state.json` exists, **do not** increment here — the orchestrator's per-tick bump in §Orchestrator `state.json` writes is the sole increment for that store.
50
334
 
51
335
  ```bash
52
- gh pr view --json number,url,headRefOid,baseRefName,headRefName,isDraft
336
+ python "${CLAUDE_SKILL_DIR}/scripts/view_pr_context.py"
53
337
  ```
54
338
 
55
- Capture `number` (`<NUMBER>`), `headRefOid` (`current_head`), owner/repo (from `url`), branch name (`<BRANCH>`).
339
+ Output is a JSON object with `number`, `url`, `headRefOid`, `baseRefName`, `headRefName`, `isDraft`. Capture `number` (`<NUMBER>`), `headRefOid` (`current_head`), owner/repo (from `url`), branch name (`<BRANCH>`).
56
340
 
57
341
  ### Step 2: Branch on `phase`
58
342
 
59
343
  #### `phase == BUGBOT`
60
344
 
61
- a. Fetch Cursor Bugbot reviews newest-first and walk backwards until the first clean review:
345
+ a. Fetch Cursor Bugbot reviews newest-first and walk backwards until the first clean review. The script enforces the gh-paginate rule (uses `--paginate --slurp` plus Python JSON handling — see [`scripts/README.md`](scripts/README.md) and [`../../rules/gh-paginate.md`](../../rules/gh-paginate.md)) and classifies each review:
62
346
 
63
347
  ```bash
64
- gh api repos/<OWNER>/<REPO>/pulls/<NUMBER>/reviews \
65
- --jq '[.[] | select(.user.login=="cursor[bot]")] | sort_by(.submitted_at) | reverse'
348
+ python "${CLAUDE_SKILL_DIR}/scripts/fetch_bugbot_reviews.py" \
349
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
66
350
  ```
67
351
 
68
- Track dirty reviews in a temp file as you walk; the Fix protocol reads it back later in this tick:
352
+ Output is a JSON array of `{review_id, commit_id, submitted_at, body, classification}`, newest-first, with `classification` already set to `"dirty"` or `"clean"`. Track dirty entries in a temp file as you walk; the Fix protocol reads it back later in this tick:
69
353
 
70
354
  ```bash
71
355
  dirty_reviews_path=$(mktemp "${TMPDIR:-/tmp}/pr-converge-bugbot.XXXXXX")
@@ -74,163 +358,344 @@ a. Fetch Cursor Bugbot reviews newest-first and walk backwards until the first c
74
358
 
75
359
  Iterate from index 0 (most recent) toward older entries:
76
360
 
77
- - Classify each review's body — **dirty** when it contains `Cursor Bugbot has reviewed your changes and found <N> potential issue`; **clean** otherwise.
78
361
  - For a dirty review, append one JSON line to `$dirty_reviews_path` with `{review_id, commit_id, submitted_at, body}`.
79
362
  - Stop at the first clean review. Older reviews are presumed addressed at that clean checkpoint and are not re-read.
80
363
  - When index 0 is itself clean, `$dirty_reviews_path` stays empty.
81
364
 
82
- Capture `commit_id`, `state`, `submitted_at`, and body of the index-0 review for the decision branches below. When a branch routes to the **Fix protocol**, read every entry from `$dirty_reviews_path` and address all of them — not just index 0.
365
+ Capture `commit_id`, `submitted_at`, body, and `classification` of the index-0 review for the decision branches below. When a branch routes to the **Fix protocol**, read every entry from `$dirty_reviews_path` and address all of them — not just index 0.
366
+
367
+ b. Fetch unaddressed inline comments from `cursor[bot]` for the **newest submitted Bugbot review** on `current_head`. The script enforces the same `--paginate --slurp` pattern as `fetch_bugbot_reviews.py`, resolves that review via the reviews list, then returns only inline rows whose `pull_request_review_id` matches that review (so stale threads from an older Bugbot review on the same SHA are excluded).
83
368
 
84
- b. Fetch unaddressed inline comments from `cursor[bot]` on `current_head`:
85
369
  ```bash
86
- gh api repos/<OWNER>/<REPO>/pulls/<NUMBER>/comments \
87
- --jq "[.[] | select(.user.login==\"cursor[bot]\") | select(.commit_id==\"$current_head\")]"
370
+ python "${CLAUDE_SKILL_DIR}/scripts/fetch_bugbot_inline_comments.py" \
371
+ --owner <OWNER> --repo <REPO> --number <NUMBER> --commit "$current_head"
88
372
  ```
89
373
 
374
+ Output is a JSON array of `{comment_id, commit_id, path, line, body}` for those matching inline comments.
375
+
90
376
  c. Decide (the four branches below cover every input combination — match the first branch whose predicate holds):
91
- - **No bugbot review yet, OR latest bugbot review's `commit_id` differs from `current_head`:** Re-trigger bugbot (Step 3), set `bugbot_clean_at = null`, reset `inline_lag_streak = 0`, schedule next wakeup, return.
92
- - **Latest review's `commit_id == current_head` AND zero unaddressed inline findings AND review body indicates clean:** Set `bugbot_clean_at = current_head`. Reset `inline_lag_streak = 0`. Transition `phase = BUGTEAM`. Continue to bugteam branch in this same tick — back-to-back convergence requires bugteam to run against the same HEAD before the next wakeup is scheduled.
93
- - **Latest review's `commit_id == current_head` with unaddressed inline findings (review body indicates findings):** Apply the **Fix protocol** below to address them. Reset `inline_lag_streak = 0`. The fix protocol pushes a new commit, which sets `current_head` to the new SHA, sets `bugbot_clean_at = null`, replies inline on each thread, and re-triggers bugbot. Schedule next wakeup, return.
94
- - **Latest review's `commit_id == current_head` AND review body indicates findings AND inline-comments API returns zero matching comments for `current_head`:** Treat as transient API propagation lag — bugbot publishes the review body and inline comments through separate API operations and the two writes can briefly desync. Increment `inline_lag_streak`. When `inline_lag_streak >= 3`, escalate as a hard blocker (bugbot review is structurally inconsistent body claims findings while inline anchors stay empty across three consecutive ticks); report and terminate. Otherwise schedule next wakeup at `delaySeconds: 60` (lag is short-lived) and return; the inline comments should appear on the next tick.
377
+ - **No bugbot review yet, OR latest bugbot review's `commit_id` differs from `current_head`:** Re-trigger bugbot (Step 3), set
378
+ `bugbot_clean_at = null`, reset `inline_lag_streak = 0`, schedule next wakeup, return.
379
+ - **Latest review's `commit_id == current_head` AND zero unaddressed inline findings AND review body indicates clean:** Set `bugbot_clean_at
380
+ = current_head`. Reset `inline_lag_streak = 0`. Transition `phase = BUGTEAM`. Continue to BUGTEAM in this same tickback-to-back
381
+ convergence requires the second audit on the same HEAD before the next wakeup is scheduled.
382
+ - **Latest review's `commit_id == current_head` with unaddressed inline findings (review body indicates findings):** Apply the **Fix
383
+ protocol** below. Reset `inline_lag_streak = 0`. When **`state.json`** is in use, the clean-coder teammate pushes, replies inline, writes
384
+ `state.json`, then goes idle; **Step 3** (`trigger_bugbot.py` on the new HEAD) runs **after** via the orchestrator-spawned follow-up agent
385
+ (§Fix result → general-purpose). When **no** `state.json` (typical single-PR Cursor tick), complete implement → push → inline replies → Step 3
386
+ in the same tick per your loaded pacing workflow. Schedule next wakeup, return.
387
+ - **Latest review's `commit_id == current_head` AND review body indicates findings AND inline-comments API returns zero matching comments
388
+ for `current_head`:** Treat as transient API propagation lag. Increment `inline_lag_streak`. When `inline_lag_streak >= 3`, escalate as a hard
389
+ blocker; report and terminate with no loop pacing; stop the AHK auto-typer per `workflows/ahk-auto-continue-loop.md` if that path was active.
390
+ Otherwise complete **Step 4** using the **BUGBOT inline-lag** section of the pacing workflow you loaded ([Pacing workflows](#pacing-workflows-load-exactly-one)); if no workflow file applies, schedule the next wakeup at `delaySeconds: 60`.
391
+
392
+ **Gotcha (Bugbot already clean on `HEAD`, but another `bugbot run` fires):** When the latest Bugbot review on `current_head` already indicates
393
+ **clean / no issues** (the branch that sets `bugbot_clean_at` and transitions to **`phase = BUGTEAM`**), the next action must be the **second
394
+ audit in the same tick** per §Second-audit execution — never a redundant `bugbot run`. If merged findings require commits, continue with **Fix
395
+ protocol** per [Fix protocol](#fix-protocol) (`Task` with `generalPurpose` and the clean-coder **Read** preamble). If **`Task`** cannot be invoked, STOP and notify the user.
95
396
 
96
397
  #### `phase == BUGTEAM`
97
398
 
98
- a. Run the in-house bugteam audit on the current PR by invoking the `Skill` tool in the main session:
399
+ a. Run **bugteam** (second audit) on the current PR.
99
400
 
100
- ```
101
- Skill({skill: "bugteam", args: "https://github.com/<OWNER>/<REPO>/pull/<NUMBER>"})
102
- ```
401
+ - **When `Skill` is invokable** (see [Pacing workflows](#pacing-workflows-load-exactly-one) tool-inventory rules — same session): invoke **bugteam** with the `Skill` tool. Path A vs Path B is selected **inside** bugteam per [bugteam Path routing](../bugteam/SKILL.md#path-routing-mandatory-first-branch); pr-converge does not branch on `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` here.
103
402
 
104
- The main session is the team lead, so `TeamCreate` fires from the orchestrator and `/bugteam` emits its CODE_RULES gate output, teammate spawn lines, and audit progress as expected. The skill audits the current PR against CODE_RULES, posts review threads, and converges or stops at its own internal cap. Wait for it to complete; capture exit and final summary.
403
+ ```
404
+ Skill({skill: "bugteam", args: "https://github.com/<OWNER>/<REPO>/pull/<NUMBER>"})
405
+ ```
406
+
407
+ Wait for completion; capture exit and final summary (convergence vs findings) for Step **(c)**.
105
408
 
106
- b. **Re-resolve current HEAD now** because `/bugteam` may have pushed commits during its run. The `current_head` from Step 1 is potentially stale at this point:
409
+ - **When `Skill` is not invokable** (typical `Task` teammate): that worker executes **bugteam** by reading [`../bugteam/SKILL.md`](../bugteam/SKILL.md) and, if Path B applies, [`../bugteam/reference/workflow-path-b-task-harness.md`](../bugteam/reference/workflow-path-b-task-harness.md) same **`code-quality-agent`** / **`clean-coder`** loop and gates as Path A; only harness steps differ per that workflow file.
410
+
411
+ b. **Re-resolve current HEAD now** because the second audit may have pushed commits during its run. The `current_head` from Step 1 is potentially
412
+ stale at this point:
107
413
  ```bash
108
- new_head=$(gh api repos/<OWNER>/<REPO>/pulls/<NUMBER> --jq '.head.sha')
414
+ new_head=$(python "${CLAUDE_SKILL_DIR}/scripts/resolve_pr_head.py" \
415
+ --owner <OWNER> --repo <REPO> --number <NUMBER>)
109
416
  ```
110
- If `new_head != current_head`, set `current_head = new_head` AND set `bugbot_clean_at = null`. The new commits from bugteam invalidate bugbot's prior clean.
417
+ If `new_head != current_head`, set `current_head = new_head` AND set `bugbot_clean_at = null`. The new commits invalidate bugbot's prior clean.
111
418
 
112
- c. Inspect bugteam's output. Bugteam reports either `convergence (zero findings)` or a list of unfixed findings with file:line.
419
+ c. Inspect the bugteam outcome. It reports either `convergence (zero findings)` or a list of unfixed findings with file:line (same semantics for both bugteam workflows).
113
420
 
114
- d. Decide based on the (post-bugteam) state — order matters; check pushed-during-bugteam FIRST so a convergence report against a stale HEAD never falsely terminates:
115
- - **bugteam pushed during this tick (i.e., `bugbot_clean_at` was just reset to `null` in step b):** Re-trigger bugbot in this same tick (Step 3) so the new HEAD enters bugbot's queue immediately, transition `phase = BUGBOT`, schedule next wakeup, return. The new commit needs a fresh bugbot review before convergence can be claimed.
116
- - **bugteam reports convergence AND `bugbot_clean_at == current_head` (no push during this tick):** This is back-to-back clean. Mark the PR ready for review:
117
- ```bash
118
- gh pr ready <NUMBER> --repo <OWNER>/<REPO>
119
- ```
120
- Report to the user in one sentence: "PR #<NUMBER> converged: bugbot CLEAN at <SHA>, bugteam CLEAN at <SHA>; marked ready for review." **Omit the next ScheduleWakeup call** — this terminates the /loop.
121
- - **bugteam reports convergence BUT `bugbot_clean_at != current_head` (no push during this tick):** Bugteam reached zero findings without committing, yet bugbot still needs re-confirmation against this HEAD. This branch is reachable only when state diverged BETWEEN ticks — for example, the user pushed a manual commit between two wakeups, leaving `current_head` ahead of the SHA bugbot last cleaned. Transition `phase = BUGBOT`, schedule next wakeup, return.
122
- - **bugteam reports findings without committing fixes:** apply the **Fix protocol** below (which always re-triggers bugbot after the push), transition `phase = BUGBOT`, schedule next wakeup, return.
421
+ d. Decide based on the (post-second-audit) state — order matters; check pushed-during-second-audit FIRST so a convergence report against a stale HEAD never falsely terminates:
422
+ - **Second audit pushed during this tick (i.e., `bugbot_clean_at` was just reset to `null` in step b):** Re-trigger bugbot in this same tick (Step 3) so the new HEAD enters bugbot's queue immediately, transition `phase = BUGBOT`, schedule next wakeup, return.
423
+ - **Second audit reports convergence AND `bugbot_clean_at == current_head` (no push during this tick):** This is back-to-back clean — necessary, but not sufficient on its own. Run the **§Convergence gates** below to clear the Copilot-findings, mergeability, and post-convergence Copilot-request gates. Only when all four gates pass do you mark the PR ready and **omit loop pacing** per the **Convergence** section of whichever pacing workflow was active.
424
+ - **Second audit reports convergence BUT `bugbot_clean_at != current_head` (no push during this tick):** Transition `phase = BUGBOT`, schedule next wakeup, return.
425
+ - **Second audit reports findings without committing fixes:** apply the **Fix protocol** below; **Step 3** on the new HEAD runs after fix handoff per §Multi-PR or in-tick for single-PR. Transition `phase = BUGBOT`, schedule next wakeup, return.
426
+
427
+ ### Convergence gates
428
+
429
+ Run **only** when Step 2 BUGTEAM reports `convergence (zero findings)` AND `bugbot_clean_at == current_head` (back-to-back clean) AND no push occurred during the bugteam tick. The gates run in order; the first one that fails determines next-tick behavior. Only after all four gates pass do you mark the PR ready.
430
+
431
+ #### (a) Copilot findings gate
432
+
433
+ Fetch the latest Copilot reviewer (`copilot-pull-request-reviewer[bot]`) review on the PR and any inline comments anchored to the most recent Copilot review on `current_head`:
434
+
435
+ ```bash
436
+ python "${CLAUDE_SKILL_DIR}/scripts/fetch_copilot_reviews.py" \
437
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
438
+
439
+ python "${CLAUDE_SKILL_DIR}/scripts/fetch_copilot_inline_comments.py" \
440
+ --owner <OWNER> --repo <REPO> --number <NUMBER> --commit "$current_head"
441
+ ```
442
+
443
+ Decide (the four branches below cover every input combination — match the first whose predicate holds):
444
+
445
+ - **A Copilot review exists at `current_head` AND its `classification == "dirty"` AND inline comments returned non-empty for the matching `pull_request_review_id`:** Treat as a Fix protocol input (same shape as bugbot dirty). Read every inline finding, apply the **Fix protocol** below (TDD test → production fix → push → reply inline on each thread), reset `bugbot_clean_at = null` AND `copilot_clean_at = null`, transition `phase = BUGBOT`, run **Step 3** (`trigger_bugbot.py`) on the new HEAD, schedule next wakeup, return. The full back-to-back-clean cycle plus all four gates must hold again on the new HEAD before convergence.
446
+ - **A Copilot review exists at `current_head` AND its `classification == "dirty"` AND inline comments are empty for the matching `pull_request_review_id`:** Copilot posted findings only in the review body (`CHANGES_REQUESTED` or `COMMENTED` with non-empty body and no inline threads). Treat the review body as the finding source: parse the body for actionable findings, apply the **Fix protocol** using the body excerpts in place of inline threads (TDD test → production fix → push). Post the reply as a top-level review reply on the Copilot review (not an inline-thread reply, because no inline threads exist) acknowledging the addressed findings and citing the new HEAD SHA. Reset `bugbot_clean_at = null` AND `copilot_clean_at = null`, transition `phase = BUGBOT`, run **Step 3** (`trigger_bugbot.py`) on the new HEAD, schedule next wakeup, return. Convergence still requires the full back-to-back-clean cycle on the new HEAD.
447
+ - **A Copilot review exists at `current_head` AND its `classification == "clean"` (state `APPROVED`):** Set `copilot_clean_at = current_head`. Continue to gate **(b)**.
448
+ - **No Copilot review has been posted on `current_head` yet:** Skip — gate **(c)** below issues the proactive request. Continue to gate **(b)**.
449
+
450
+ #### (b) Mergeability gate
451
+
452
+ Resolve the PR's mergeability state:
453
+
454
+ ```bash
455
+ python "${CLAUDE_SKILL_DIR}/scripts/check_pr_mergeability.py" \
456
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
457
+ ```
458
+
459
+ Output is `{"mergeable", "mergeStateStatus", "headRefOid"}`. Persist `mergeStateStatus` into `merge_state_status` (state line or `state.json`). Decide:
460
+
461
+ - **`mergeStateStatus == "CLEAN"` AND `mergeable == "MERGEABLE"`:** Continue to gate **(c)**.
462
+ - **`mergeStateStatus == "DIRTY"` (or `mergeable == "CONFLICTING"`):** Do **not** mark ready. Invoke the **`rebase`** skill ([`../rebase/SKILL.md`](../rebase/SKILL.md)) and follow its Phase 1–4 protocol against the PR's base ref. After a successful rebase + force-with-lease push, the new HEAD invalidates every prior clean state — reset `bugbot_clean_at = null`, `copilot_clean_at = null`, `merge_state_status = null`, transition `phase = BUGBOT`, run **Step 3** (`trigger_bugbot.py`) on the new HEAD, schedule next wakeup, return. The convergence loop re-runs from scratch on the new HEAD.
463
+ - **`mergeStateStatus` is `BLOCKED`, `BEHIND`, or `UNKNOWN` for non-conflict reasons (e.g., required checks pending, branch behind base without conflicts that GitHub cannot auto-resolve):** This is a **hard blocker** per §Stop conditions — do not invent a fix. Report the specific `mergeStateStatus`, omit loop pacing per the active workflow, stop the AHK auto-typer if that path was in use.
464
+
465
+ #### (c) Post-convergence Copilot review request
466
+
467
+ Once gates (a) and (b) both pass with the strong outcomes (Copilot already clean at `current_head` *or* no Copilot review on `current_head` yet, AND `mergeStateStatus == "CLEAN"`), request a Copilot review:
468
+
469
+ ```bash
470
+ python "${CLAUDE_SKILL_DIR}/scripts/request_copilot_review.py" \
471
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
472
+ ```
473
+
474
+ The reviewer ID literal `copilot-pull-request-reviewer[bot]` (with the `[bot]` suffix) is load-bearing — `Copilot`, `copilot`, and `github-copilot` all silently no-op per [`../copilot-review/SKILL.md`](../copilot-review/SKILL.md). After the request, schedule the next wakeup (one ScheduleWakeup cycle, or AHK tick) and return — the next tick checks Copilot's response.
475
+
476
+ When the next tick fires and `phase == BUGTEAM` with all prior state preserved, re-run gate **(a)** as the first thing. Decide:
477
+
478
+ - **Copilot review at `current_head` is `clean` (state `APPROVED`):** Set `copilot_clean_at = current_head`. Mark the PR ready (`mark_pr_ready.py`), report convergence (see §(d) below for the report format), terminate per **§Stop conditions / Convergence**.
479
+ - **Copilot review at `current_head` is `dirty`:** Still mark the current PR ready (this is the documented "we still allow it to be 'clean'" rule — the four gates are bugbot CLEAN ∧ bugteam CLEAN ∧ `mergeStateStatus == CLEAN` ∧ either Copilot CLEAN at HEAD or a follow-up PR captures Copilot findings). Before terminating, build a markdown findings file from `fetch_copilot_inline_comments.py` output (one checklist item per finding with file:line and excerpted body), then open a follow-up draft PR off `current_head`:
480
+
481
+ ```bash
482
+ python "${CLAUDE_SKILL_DIR}/scripts/open_followup_copilot_pr.py" \
483
+ --owner <OWNER> --repo <REPO> \
484
+ --parent-number <NUMBER> --head "$current_head" \
485
+ --findings-file <PATH_TO_FINDINGS_MD>
486
+ ```
487
+
488
+ The follow-up branch name is `chore/copilot-followup-<NUMBER>-<short_sha>` and the PR title is `chore: address Copilot findings from PR #<NUMBER>`. Queue `/pr-converge` on the new PR for the user to invoke (do **not** inline-spawn another converge loop in the same session). Report **both** PR URLs to the user. The current PR's convergence is final at the original HEAD; the new PR runs its own convergence cycle that itself satisfies all four gates.
489
+
490
+ - **No Copilot review has appeared at `current_head` yet (still propagating):** Schedule one more wakeup cycle (270s when `ScheduleWakeup` is available, AHK cadence otherwise) and re-check on the next tick. After three consecutive empty waits, escalate as a hard blocker per §Stop conditions (Copilot has not produced a review on the requested commit despite the request).
491
+
492
+ #### (d) Mark ready and report
493
+
494
+ Only when all four gates pass — bugbot CLEAN ∧ bugteam CLEAN ∧ `mergeStateStatus == "CLEAN"` ∧ Copilot CLEAN at HEAD (or the post-convergence request returned dirty and the follow-up PR is open) — run:
495
+
496
+ ```bash
497
+ python "${CLAUDE_SKILL_DIR}/scripts/mark_pr_ready.py" \
498
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
499
+ ```
500
+
501
+ When scripts are unavailable, `gh pr ready <NUMBER> --repo <OWNER>/<REPO>` is an equivalent human-visible outcome. When `state.json` is in use, append the convergence row to `<TMPDIR>/pr-converge-<session_id>/converged.log` per §Memory; when not, skip file append. Report: `PR #<NUMBER> converged: bugbot CLEAN at <SHA>, bugteam CLEAN at <SHA>, mergeStateStatus CLEAN, copilot <CLEAN|FOLLOWUP_PR_URL>; marked ready for review`. **Omit loop pacing** per the **Convergence** section of whichever pacing workflow was active.
123
502
 
124
503
  ### Step 3: Re-trigger bugbot
125
504
 
126
- Used in Step 2 BUGBOT branch 1, in Step 2 BUGTEAM branch 1, and in the Fix protocol. Post a literal `bugbot run` PR comment. Write the body via the Write tool to a temp file, then pass it with `--body-file` (per the gh-body-file rule):
505
+ Used in Step 2 BUGBOT branch 1, in Step 2 BUGTEAM branch 1, and in the Fix protocol. Prefer the portable script (temp body file, `gh pr comment --body-file`):
506
+
507
+ ```bash
508
+ python "${CLAUDE_SKILL_DIR}/scripts/trigger_bugbot.py" \
509
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
510
+ ```
511
+
512
+ **Bundled PowerShell alternative** (same gh-body-file contract):
513
+
514
+ ```bash
515
+ POST_BUGBOT_RUN="$HOME/.claude/skills/pr-converge/scripts/post-bugbot-run.ps1"
516
+ pwsh -NoProfile -ExecutionPolicy Bypass -File "$POST_BUGBOT_RUN" "https://github.com/<OWNER>/<REPO>/pull/<NUMBER>"
517
+ ```
518
+
519
+ Shorthand `owner/repo#number`:
127
520
 
128
521
  ```bash
129
- gh pr comment <NUMBER> --repo <OWNER>/<REPO> --body-file <path/to/bugbot_run.md>
522
+ POST_BUGBOT_RUN="$HOME/.claude/skills/pr-converge/scripts/post-bugbot-run.ps1"
523
+ pwsh -NoProfile -ExecutionPolicy Bypass -File "$POST_BUGBOT_RUN" "<OWNER>/<REPO>#<NUMBER>"
130
524
  ```
131
525
 
132
- The body file contains exactly the literal phrase `bugbot run` followed by a newline. Use that phrase exactly — empirically the only re-trigger Cursor Bugbot recognizes; alternative phrasings (`re-review`, `bugbot please`, etc.) silently no-op.
526
+ Explicit repository and number:
527
+
528
+ ```bash
529
+ POST_BUGBOT_RUN="$HOME/.claude/skills/pr-converge/scripts/post-bugbot-run.ps1"
530
+ pwsh -NoProfile -ExecutionPolicy Bypass -File "$POST_BUGBOT_RUN" -Repository "<OWNER>/<REPO>" -Number <NUMBER>
531
+ ```
532
+
533
+ `bugbot run` is empirically the only re-trigger Cursor Bugbot recognizes; alternative phrasings (`re-review`, `bugbot please`, etc.) silently no-op.
534
+
535
+ If you cannot run the scripts above, use the Write tool to a temp file, then `gh pr comment <NUMBER> --repo <OWNER>/<REPO> --body-file <path>` yourself.
536
+ The body file must contain exactly the literal phrase `bugbot run` followed by a newline — empirically the only re-trigger Cursor Bugbot
537
+ recognizes; alternative phrasings (`re-review`, `bugbot please`, etc.) silently no-op.
133
538
 
134
- ### Step 3.5: Enforce the safety cap
539
+ **Gotcha (duplicate `bugbot run` while a review is already queued):** Do not post another `bugbot run` when Bugbot has already picked up the latest trigger. On GitHub, the bugbot review signal is an **eyes** (`:eyes:`) reaction on the **most recent** `bugbot run` PR comment (Bugbot acknowledging the job). When that reaction is present, skip Step 3 for this wait cycle — a second comment spams the PR and can confuse tick logic; wait for the review to finish or for `HEAD` to change before re-triggering per Step 2.
135
540
 
136
- Before scheduling the next wakeup, evaluate `tick_count`. When `tick_count >= 30`, stop and report per the **Stop conditions** safety-cap branch (§Safety cap) **omit Step 4 entirely**. Reaching this many rounds means something structural is wrong with the loop and continuing wastes work. Otherwise proceed to Step 4.
541
+ **Default loop:** After each tick, run **Step 4** whenever pacing still applies meaning convergence has not yet omitted pacing and no **Stop conditions** branch has omitted pacing for this tick. That rule covers the **first** user-typed **`/pr-converge`** the same as later wakeups or `continue` ticks: each invocation completes one tick, then **Step 4** loads the pacing workflow and schedules the next entry when the workflow says to. For **`ScheduleWakeup`**, set **`prompt: "/pr-converge"`** by default (`workflows/schedule-wakeup-loop.md`); set **`prompt: "/loop /pr-converge"`** when the harness requires the `/loop` wrapper for the next firing to execute.
137
542
 
138
- ### Step 4: Schedule the next wakeup (only when invoked under `/loop`)
543
+ When **`ScheduleWakeup` is unavailable**, run **Step 4** on the AHK workflow row; that path keeps **`/pr-converge`** on loop-until-done semantics per `workflows/ahk-auto-continue-loop.md`. When **no** pacing mechanism is active (no `ScheduleWakeup` tool and AHK not started per that file), end the tick with **return** only — there is nothing to schedule. Elsewhere, **schedule next wakeup, return** means run **Step 4** below; when Step 4 schedules nothing, treat that phrase as **return** only.
139
544
 
140
- **Skip this step entirely when the skill was invoked as bare `/pr-converge`** (manual mode). Manual mode runs exactly one tick and returns without scheduling — the user re-runs the skill or wraps it in `/loop` to continue. References elsewhere in this document to "schedule next wakeup, return" mean Step 4 below; under manual mode every such reference becomes "return" only.
545
+ **Gotcha (Bugbot found errors, but a redundant `bugbot run` instead of a fix push):** When the latest Bugbot review on `current_head` still has
546
+ **unaddressed findings** (inline threads and/or a non-clean review body), **do not** post another `bugbot run` on that same SHA as a
547
+ substitute for fixing the code. A second trigger without a new commit cannot resolve the findings — it only duplicates noise and breaks tick
548
+ expectations. Follow the **Fix protocol** end-to-end: spawn **`Task`** with **`subagent_type: "generalPurpose"`** and the clean-coder **Read** preamble from [Fix protocol](#fix-protocol) (never ad-hoc shell or a bare `generalPurpose` prompt for production edits), **commit and push** with mandatory pre-commit and pre-push hook validation (full stop and notify the user if hooks did not
549
+ run or were bypassed), reply inline on each thread, **then** Step 3 `bugbot run` against the new SHA.
141
550
 
142
- Detect manual mode by inspecting the conversation context: when the most recent user message that triggered this run was `/pr-converge` (no `/loop` prefix and no prior `ScheduleWakeup` chain entry that fired with `prompt: "/loop /pr-converge"`), this is manual mode. When the run was triggered by the parent's /loop wakeup chain or the user typed `/loop /pr-converge`, this is loop mode.
551
+ ### Step 4: Loop pacing
143
552
 
144
- In **loop mode**, call `ScheduleWakeup` with:
553
+ **`ScheduleWakeup` field hints** (when not using the workflow files — not recommended; prefer [Pacing workflows](#pacing-workflows-load-exactly-one)):
145
554
 
146
- - `delaySeconds: 270` whenever bugbot was just re-triggered (whether by Step 3 directly, by the Fix protocol's mandatory re-trigger, or by BUGTEAM branch 1's same-tick re-trigger). Bugbot finishes a review in 1–4 minutes, so 270s stays under the 5-minute prompt-cache TTL while giving a margin past bugbot's typical upper bound. The single exception is the BUGBOT inline-lag branch, which uses `delaySeconds: 60` because no re-trigger fired and the only thing being awaited is GitHub's inline-comments API catching up.
555
+ - `delaySeconds: 270` whenever bugbot was just re-triggered (whether by Step 3 directly, by **Step 3** after a fix via the follow-up agent chain, or by BUGTEAM branch 1's same-tick re-trigger). Bugbot finishes a review in 1–4 minutes, so 270s stays under the 5-minute prompt-cache TTL while giving a margin past bugbot's typical upper bound. The single exception is the BUGBOT inline-lag branch, which uses `delaySeconds: 60` because no re-trigger fired and the only thing being awaited is GitHub's inline-comments API catching up.
147
556
  - `reason`: one short sentence on what is being awaited, including the current `phase` and `bugbot_clean_at` SHA when set.
148
- - `prompt: "/loop /pr-converge"` — re-enters this skill via /loop on the next firing.
557
+ - `prompt: "/pr-converge"` — default; re-enters this skill on the next firing with default loop semantics. If the harness requires the `/loop` wrapper for wakeups, `prompt: "/loop /pr-converge"` is equivalent (`workflows/schedule-wakeup-loop.md`).
149
558
 
150
- **On convergence (loop mode):** omit the ScheduleWakeup call entirely. The /loop terminates because no next wakeup was scheduled.
559
+ Throughout Step 2 and the Fix protocol, **schedule next wakeup, return** means: load the correct pacing workflow (see
560
+ [Pacing workflows](#pacing-workflows-load-exactly-one)), then execute **Step 4** exactly as that file specifies (pace the next tick, then
561
+ return).
562
+
563
+ **Entry paths** include `/pr-converge`, `/loop /pr-converge` when the harness uses that wrapper, an AHK `continue` tick, or a `ScheduleWakeup` whose `prompt` is `/pr-converge` or `/loop /pr-converge` per the schedule-wakeup workflow.
564
+
565
+ **On convergence:** apply the **Convergence** section of the **same** pacing workflow file you are using for this session (omit wakeups / stop
566
+ AHK per that file).
151
567
 
152
568
  ## Fix protocol
153
569
 
154
- Used by both phases when findings exist:
570
+ ### Cursor `Task` registry (single-PR / Cursor host)
571
+
572
+ Cursor's **`Task`** tool validates `subagent_type` against a **fixed enum**; **`"clean-coder"` is not a valid value**. When **no** `state.json` is in use
573
+ (typical single-PR Cursor tick), **production edits** use **`Task`** with **`subagent_type: "generalPurpose"`** and the clean-coder contract in the **`prompt`**
574
+ per the **Implement** bullet below - not a separate `clean-coder` spawn.
575
+
576
+ The fix protocol is executed by a **`clean-coder` teammate** when **`state.json`** drives the session (§Multi-PR orchestration model), or by the **`Task` + `generalPurpose`** path in the **main session** when **no** `state.json` is in use (typical single-PR Cursor). The orchestrator **never** performs production edits inline in multi-PR mode. Pre-commit and pre-push hook handling is governed by §Ground rules and the gates below.
577
+
578
+ **Multi-PR (`state.json`) teammate obligations** (in addition to TDD, commit, push):
579
+
580
+ - Replies inline on each addressed finding thread via `reply_to_inline_comment.py` (what changed and the commit identifier), matching §Audit result → fix worker step 4 — **before** writing `state.json` and going idle.
581
+ - Writes `last_action: "fix_pushed"`, `current_head: <new SHA>`, `bugbot_clean_at: null`, `phase: "BUGBOT"`, `status: "awaiting_bugbot"`, and `last_updated` (ISO-8601 UTC) to `state.json` (per §Concurrency).
582
+ - Goes idle. The orchestrator spawns the follow-up `general-purpose` agent for bugbot trigger and monitoring.
583
+
584
+ **The orchestrator does not reply to inline comments, does not trigger bugbot, and does not read repository source files during the fix phase** when the multi-PR model is active.
585
+
586
+ **Single-PR (no `state.json`) — same gates, main session executor:**
155
587
 
156
588
  - Read each referenced file:line.
157
589
  - Write a failing test first when the finding has behavior to test. For pure doc, comment, or naming nits with no behavior, go straight to the fix.
158
- - Implement the fix.
590
+ - **Implement** by invoking **`Task`** with **`subagent_type: "generalPurpose"`**. The **`prompt`** MUST begin by requiring the subagent to **Read** the clean-coder agent markdown **before** editing production files: on macOS/Linux `$HOME/.claude/agents/clean-coder.md`, on Windows `%USERPROFILE%\.claude\agents\clean-coder.md`. The prompt MUST state that file is binding for code generation (naming, TDD when behavior changes, hook-safe single commit, scope limited to the listed findings). Do **not** use ad-hoc shell edits for production code on this path. Do **not** emit a bare `generalPurpose` prompt that omits the clean-coder file step. If **`Task`** cannot be invoked, **full stop** and tell the user – do not substitute another subagent type for production edits.
159
591
  - Stage the affected files and create one new commit on the existing branch:
160
592
  ```bash
161
593
  git add <files> && git commit -m "fix(review): <brief summary>"
162
594
  ```
163
- Honor pre-commit and pre-push hooks; when a hook rejects, read its message, fix the underlying issue, retry. Hook rejections flag real underlying issues worth investigating.
595
+ **Pre-commit gate:** Never pass `--no-verify`, `--no-gpg-sign` (unless the user has explicitly required otherwise), or any flag that skips hooks. After `git commit`, confirm from the **same terminal transcript** that the **pre-commit** hook ran (visible hook output or your configured hook runner's success banner) and exited **0**. If the transcript shows hooks were **skipped**, **bypassed**, or **did not run** when your repo expects them, **full stop** — do not push, do not reply inline, do not trigger Bugbot — and notify the user with what you observed. When a hook **rejects** (non-zero exit), read the message, fix the cause, retry commit until hooks pass.
164
596
  - Push the new commit:
165
597
  ```bash
166
598
  git push origin <BRANCH>
167
599
  ```
168
- Capture the new HEAD SHA. Set `current_head` to it. Set `bugbot_clean_at = null`.
600
+ **Pre-push gate:** Never pass `--no-verify` or equivalent. After `git push`, confirm from the **same terminal transcript** that **pre-push** ran (when your repo defines a pre-push hook) and exited **0**. If push output shows pre-push was **skipped**, **bypassed**, or **absent** when it should have run, **full stop** — do not update `current_head`, do not reply inline, do not trigger Bugbot — and notify the user. Capture the new HEAD SHA only after both gates pass. Set `current_head` to it. Set `bugbot_clean_at = null`.
169
601
  - Reply inline on each addressed comment thread using `--body-file` (per gh-body-file rule):
170
602
  ```bash
171
- gh api -X POST repos/<OWNER>/<REPO>/pulls/<NUMBER>/comments/<comment_id>/replies \
172
- --field body=@<path/to/reply.md>
603
+ python "${CLAUDE_SKILL_DIR}/scripts/reply_to_inline_comment.py" \
604
+ --owner <OWNER> --repo <REPO> --number <NUMBER> \
605
+ --comment-id <COMMENT_ID> --body-file <path/to/reply.md>
173
606
  ```
174
- - **Always re-trigger bugbot (Step 3 above) after pushing a fix**, regardless of which phase originated the findings. Any new commit invalidates bugbot's prior clean by definition, so bugbot must re-review the new HEAD before convergence can be claimed. Re-triggering in the same tick saves a full wakeup cycle compared to deferring the trigger to the next tick.
607
+ - **After pushing a fix, always run Step 3 (`bugbot run`) in the same tick** when you would otherwise wait for Bugbot — regardless of which phase originated the findings. Step 3 is the **mechanism** that restarts Bugbot on the new `HEAD`, but the **meaning** is broader: a new commit **resets the full convergence cycle**. Prior bugbot clean and prior second-audit clean on an older SHA **do not** count toward convergence on the new `HEAD`. You must **again** obtain **bugbot CLEAN** on `current_head`, then **second-audit CLEAN** on that same `HEAD` with **no intervening push** (the same back-to-back rule as Step 2). Re-triggering Bugbot in the same tick after the push saves a full wakeup cycle compared to deferring Step 3 to the next tick.
175
608
 
176
609
  ## Stop conditions
177
610
 
178
- - **Convergence** (back-to-back clean as defined in Step 2 BUGTEAM second branch `bugteam reports convergence AND bugbot_clean_at == current_head` with no push during this tick): mark PR ready for review, report one-sentence summary, omit ScheduleWakeup.
179
- - **Hard blocker:** API auth failure persists across two ticks, a CI regression whose root cause falls outside this PR, a hook rejection investigated through three commits and still unresolved, `inline_lag_streak >= 3`, or `/bugteam` itself reports a stuck state. Report the specific blocker and the diagnosis, then omit ScheduleWakeup.
180
- - **User stops the loop:** user says "stop the converge loop" omit ScheduleWakeup on the next tick.
181
- - **Safety cap:** `tick_count >= 30` (evaluated in Step 3.5) → omit ScheduleWakeup, report the cap was hit. See §Safety cap below for rationale.
182
-
183
- ## Safety cap
184
-
185
- When `tick_count >= 30`, stop and report. That many rounds means something structural is wrong with the loop. (Higher than copilot-review's 20-tick cap because two reviewers run sequentially per round.) The increment lives in Step 1; the evaluation lives in Step 3.5.
611
+ - **Convergence** (back-to-back clean no outstanding Copilot findings on `current_head` `mergeStateStatus == "CLEAN"` with `mergeable == "MERGEABLE"` ∧ post-convergence Copilot request resolved (either `clean` at `current_head`, or `dirty` with a follow-up PR opened per §Convergence gates (c)) — see §Convergence gates for the full ordered check): prefer `mark_pr_ready.py`; when unavailable use `gh pr ready`. When `state.json` is in use, append the convergence row to `<TMPDIR>/pr-converge-<session_id>/converged.log` per §Memory; otherwise skip file append. Report the §Convergence gates (d) summary line, then **omit loop pacing** per **Convergence** in the pacing workflow from the Step 4 table (or omit `ScheduleWakeup` when no workflow file applies). End any ongoing loops once all PRs are converged.
612
+ - **Hard blocker:** API auth failure persists across two ticks, a CI regression whose root cause falls outside this PR, a hook rejection investigated through three commits and still unresolved, `inline_lag_streak >= 3`, **bugteam** (either workflow) reports a stuck state, or the post-convergence Copilot request fails to surface a review on `current_head` after three consecutive wakeup cycles. Report the specific blocker and the diagnosis, then **omit loop pacing** per the active workflow; stop the AHK auto-typer per `workflows/ahk-auto-continue-loop.md` **Stop / safety** if that path was in use.
613
+ - **Hard blocker (`mergeStateStatus` non-CLEAN non-DIRTY):** `mergeStateStatus` is `BLOCKED`, `UNKNOWN`, or `BEHIND` (required checks pending, branch behind base without textual conflicts, or GitHub returns an indeterminate state). Investigate before retrying; the `rebase` skill addresses `DIRTY` (textual merge conflicts) — it does not address these states. Report the specific `mergeStateStatus`, then **omit loop pacing** per the active workflow; stop the AHK auto-typer per `workflows/ahk-auto-continue-loop.md` **Stop / safety** if that path was in use.
614
+ - **User stops the loop:** user says "stop the converge loop"**omit loop pacing** per the active workflow; stop the AHK auto-typer per `workflows/ahk-auto-continue-loop.md` **Stop / safety** if that path was in use.
186
615
 
187
616
  ## Ground rules
188
617
 
189
618
  - **Append commits.** Each tick adds at most one new fix commit. Multiple findings within one tick collapse into a single commit; the next tick handles the next round.
619
+ - **Bugbot findings on the current SHA mean fix-then-push-then-`bugbot run`, not another naked `bugbot run`.** Unaddressed Bugbot errors require the Fix protocol before Step 3; posting `bugbot run` again without a new commit does not clear the review state.
190
620
  - **`bugbot_clean_at` resets on every push.** A new commit invalidates bugbot's prior clean by definition — bugbot must re-review the new HEAD before convergence can be claimed.
191
- - **Back-to-back clean is the ONLY termination criterion.** Convergence requires both reviewers clean against the same HEAD with no intervening fixes; either reviewer clean alone counts as in-progress.
621
+ - **`copilot_clean_at` and `merge_state_status` reset on every push.** The same invalidation rule applies to the Copilot reviewer's prior clean and the prior `gh pr view` mergeability snapshot both must be re-checked on the new HEAD before §Convergence gates can pass.
622
+ - **Convergence requires four gates on the same HEAD.** (1) Back-to-back clean (Bugbot ∧ **bugteam** — team or background-agent workflow — with no intervening fixes), (2) no outstanding Copilot findings on `current_head`, (3) `mergeStateStatus == "CLEAN"` with `mergeable == "MERGEABLE"`, and (4) the post-convergence Copilot request resolved (either Copilot clean at HEAD, or a follow-up PR opened off HEAD that captures the Copilot findings — see §Convergence gates). Any one gate failing leaves the PR in-progress.
623
+ - **Clean Bugbot on `HEAD` means advance to second audit, not another `bugbot run`.** After Bugbot reports clean on the current SHA, set `bugbot_clean_at` and run the BUGTEAM phase per Step 2 — never post `bugbot run` as a substitute.
192
624
  - **The `bugbot run` comment is load-bearing.** Use the literal phrase `bugbot run` exactly — empirically the only re-trigger Cursor Bugbot recognizes; alternative phrasings silently no-op.
193
- - **`gh pr ready` is the convergence action.** Mark the PR ready for review and stop there. Merge, additional reviewers, title, and body remain the user's decisions; the skill's contract ends at "ready for review."
625
+ - **`gh pr ready` / `mark_pr_ready.py` is the convergence action.** Mark the PR ready for review and stop there. Merge, additional reviewers, title, and body remain the user's decisions; the skill's contract ends at "ready for review."
194
626
  - **Honor pre-push and pre-commit hooks.** When a hook rejects the change, read its output, fix the underlying issue (the failing test, the missing constant, the broken import), and retry.
627
+ - **Adapt when reality contradicts on-disk state.** This skill is a state machine, but the spec assumes `state.json` (when used) and `git`/`gh` agree with the live PR. When they diverge — the user pushed manually between ticks, the branch was force-reset, the worktree moved, the PR was closed/merged externally, or `gh` auth dropped mid-tick — **do not execute the spec literally against stale state**. Report the specific drift and escalate as a hard blocker per §Stop conditions; let the user decide whether to reset the loop, refresh credentials, or stop.
195
628
 
196
629
  ## Examples
197
630
 
198
631
  <example>
199
- User: `/loop /pr-converge`
200
- Claude: [reads PR context, runs one tick of bugbot phase, schedules next wakeup at 270s with prompt `/loop /pr-converge`, returns]
632
+ User: `/pr-converge`
633
+ Claude: [PR context + one tick of bugbot/bugteam work; then Step 4 per loaded pacing workflow — default loop until convergence or stop]
201
634
  </example>
202
635
 
203
636
  <example>
204
- User: `/pr-converge`
205
- Claude: [runs one tick manually, reports state, does NOT schedule a wakeup; user re-runs to advance]
637
+ User: `/loop /pr-converge`
638
+ Claude: [same per-tick work and Step 4 as bare `/pr-converge` harness wrapper only when the host routes wakeups through `/loop`]
206
639
  </example>
207
640
 
208
641
  <example>
209
642
  Tick fires in BUGBOT phase, latest bugbot review is against an older commit.
210
- Claude: [posts `bugbot run` comment, sets `bugbot_clean_at = null`, schedules next wakeup at 270s, returns]
643
+ Claude: [posts `bugbot run` comment, sets `bugbot_clean_at = null`, completes Step 4 per `workflows/schedule-wakeup-loop.md` when on that path
644
+ (e.g. 270s wakeup), returns]
211
645
  </example>
212
646
 
213
647
  <example>
214
648
  Tick fires in BUGBOT phase, bugbot has 2 unaddressed findings on HEAD.
215
- Claude: [TDD-fixes both, one commit, pushes, replies inline on both threads, posts `bugbot run`, schedules next wakeup at 270s, returns]
649
+ Claude: [TDD-fixes both, one commit, pushes, replies inline on both threads, posts `bugbot run`, Step 4 per schedule-wakeup workflow at 270s
650
+ when on that path, returns]
216
651
  </example>
217
652
 
218
653
  <example>
219
654
  Tick fires in BUGBOT phase, bugbot is clean against HEAD.
220
- Claude: [sets `bugbot_clean_at = HEAD`, transitions `phase = BUGTEAM`, runs `/bugteam` in the same tick]
655
+ Claude: [sets `bugbot_clean_at = HEAD`, transitions `phase = BUGTEAM`, runs `Skill({skill: "bugteam", ...})` in the same tick — bugteam Path routing picks Path A vs Path B internally]
656
+ </example>
657
+
658
+ <example>
659
+ In BUGTEAM phase, bugteam (team workflow) reports convergence and `bugbot_clean_at == current_head`.
660
+ Claude: [runs `gh pr ready <NUMBER>`, reports "PR converged: bugbot CLEAN at <SHA>, bugteam CLEAN at <SHA>; marked ready for review", applies
661
+ **Convergence** from the active pacing workflow]
662
+ </example>
663
+
664
+ <example>
665
+ In BUGTEAM phase, bugteam pushed a fix commit during its run.
666
+ Claude: [re-resolves HEAD, sets `bugbot_clean_at = null`, posts `bugbot run` in this same tick, transitions `phase = BUGBOT`, Step 4 per
667
+ schedule-wakeup workflow at 270s when on that path]
668
+ </example>
669
+
670
+ <example>
671
+ Tick fires in BUGBOT phase, bugbot review body says "found 3 potential issues" against HEAD but the inline-comments API returns zero matching
672
+ comments for `current_head`.
673
+ Claude: [increments `inline_lag_streak` to 1, Step 4 inline-lag rules from the active pacing workflow (60s `ScheduleWakeup` vs AHK cadence),
674
+ returns; expects inline comments on the next tick]
675
+ </example>
676
+
677
+ <example>
678
+ BUGTEAM tick with no agent teams: `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` is unset; bugteam Path B applies inside `Skill({skill: "bugteam", ...})`.
679
+ Claude: [invokes bugteam; bugteam runs Path B per `bugteam/SKILL.md` + `bugteam/reference/workflow-path-b-task-harness.md`; applies Step 2 §(b)–(d) unchanged against the skill outcome]
680
+ </example>
681
+
682
+ <example>
683
+ Back-to-back clean reached, but `gh pr view` shows `mergeStateStatus: DIRTY` (the base branch advanced and produced merge conflicts).
684
+ Claude: [runs §Convergence gates (b); does NOT mark ready; invokes the `rebase` skill per `../rebase/SKILL.md` Phase 1–4; after force-with-lease push, resets `bugbot_clean_at = null`, `copilot_clean_at = null`, `merge_state_status = null`, transitions `phase = BUGBOT`, posts `bugbot run` against the new HEAD, schedules next wakeup; the convergence loop re-runs from scratch on the new HEAD]
221
685
  </example>
222
686
 
223
687
  <example>
224
- In BUGTEAM phase, /bugteam reports convergence and `bugbot_clean_at == current_head`.
225
- Claude: [runs `gh pr ready <NUMBER>`, reports "PR converged: bugbot CLEAN at <SHA>, bugteam CLEAN at <SHA>; marked ready for review", omits ScheduleWakeup, terminates the /loop]
688
+ Back-to-back clean reached, mergeability is CLEAN, and Copilot already posted a review at `current_head` whose `state == "CHANGES_REQUESTED"` with two unaddressed inline findings.
689
+ Claude: [runs §Convergence gates (a); reads each finding, applies the Fix protocol (TDD test fix push reply inline on both threads), resets `bugbot_clean_at = null` and `copilot_clean_at = null`, transitions `phase = BUGBOT`, posts `bugbot run` on the new HEAD, schedules next wakeup; the back-to-back-clean cycle plus all four gates must hold again on the new HEAD]
226
690
  </example>
227
691
 
228
692
  <example>
229
- In BUGTEAM phase, /bugteam pushed a fix commit during its run.
230
- Claude: [re-resolves HEAD, sets `bugbot_clean_at = null`, posts `bugbot run` in this same tick, transitions `phase = BUGBOT`, schedules next wakeup at 270s]
693
+ Back-to-back clean reached, mergeability is CLEAN, no Copilot review exists yet on `current_head`. Claude requests Copilot via `request_copilot_review.py` and waits one tick.
694
+ Next tick: Copilot review at `current_head` is `state: APPROVED`.
695
+ Claude: [sets `copilot_clean_at = current_head`; runs `mark_pr_ready.py`; reports "PR #N converged: bugbot CLEAN at <SHA>, bugteam CLEAN at <SHA>, mergeStateStatus CLEAN, copilot CLEAN; marked ready for review"; applies **Convergence** from the active pacing workflow]
231
696
  </example>
232
697
 
233
698
  <example>
234
- Tick fires in BUGBOT phase, bugbot review body says "found 3 potential issues" against HEAD but the inline-comments API returns zero matching comments for `current_head`.
235
- Claude: [increments `inline_lag_streak` to 1, schedules next wakeup at 60s, returns; expects inline comments to appear by the next tick]
699
+ Back-to-back clean reached, mergeability is CLEAN, post-convergence Copilot review fired and returned `state: CHANGES_REQUESTED` with inline findings on `current_head`.
700
+ Claude: [still marks the PR ready (the four-gate rule allows convergence when a follow-up captures Copilot findings); builds a markdown findings checklist from `fetch_copilot_inline_comments.py`; runs `open_followup_copilot_pr.py` off `current_head` to create branch `chore/copilot-followup-<NUMBER>-<short_sha>` with title `chore: address Copilot findings from PR #<NUMBER>`; reports both PR URLs to the user; queues `/pr-converge` on the new PR for the user to invoke; current PR's convergence is final]
236
701
  </example>