claude-dev-env 1.36.2 → 1.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/_shared/pr-loop/scripts/config/preflight_constants.py +29 -8
  2. package/_shared/pr-loop/scripts/preflight.py +242 -20
  3. package/_shared/pr-loop/scripts/tests/test_preflight.py +362 -25
  4. package/_shared/pr-loop/scripts/tests/test_preflight_constants.py +9 -14
  5. package/hooks/blocking/code_rules_enforcer.py +269 -23
  6. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +157 -1
  7. package/hooks/config/test_unused_module_import_constants.py +48 -0
  8. package/hooks/config/unused_module_import_constants.py +41 -0
  9. package/package.json +1 -1
  10. package/rules/gh-paginate.md +4 -50
  11. package/rules/no-historical-clutter.md +36 -0
  12. package/skills/bg-agent/SKILL.md +69 -0
  13. package/skills/bugteam/CONSTRAINTS.md +10 -19
  14. package/skills/bugteam/PROMPTS.md +21 -14
  15. package/skills/bugteam/SKILL.md +122 -208
  16. package/skills/bugteam/SKILL_EVALS.md +75 -114
  17. package/skills/bugteam/reference/README.md +2 -4
  18. package/skills/bugteam/reference/audit-and-teammates.md +21 -48
  19. package/skills/bugteam/reference/audit-contract.md +7 -7
  20. package/skills/bugteam/reference/design-rationale.md +3 -8
  21. package/skills/bugteam/reference/team-setup.md +11 -19
  22. package/skills/bugteam/reference/teardown-publish-permissions.md +2 -14
  23. package/skills/bugteam/scripts/config/__init__.py +0 -0
  24. package/skills/bugteam/scripts/config/reflow_skill_md_constants.py +12 -0
  25. package/skills/bugteam/scripts/reflow_skill_md.py +51 -47
  26. package/skills/bugteam/sources.md +1 -25
  27. package/skills/bugteam/test_skill_additions.py +4 -13
  28. package/skills/fresh-branch/SKILL.md +71 -0
  29. package/skills/gotcha/SKILL.md +73 -0
  30. package/skills/monitor-open-prs/SKILL.md +4 -37
  31. package/skills/monitor-open-prs/test_skill_contract.py +0 -5
  32. package/skills/pr-converge/SKILL.md +60 -1298
  33. package/skills/pr-converge/reference/convergence-gates.md +122 -0
  34. package/skills/pr-converge/reference/examples.md +76 -0
  35. package/skills/pr-converge/reference/fix-protocol.md +56 -0
  36. package/skills/pr-converge/reference/ground-rules.md +13 -0
  37. package/skills/pr-converge/reference/multi-pr-orchestration.md +204 -0
  38. package/skills/pr-converge/reference/per-tick.md +204 -0
  39. package/skills/pr-converge/reference/state-schema.md +19 -0
  40. package/skills/pr-converge/reference/stop-conditions.md +26 -0
  41. package/skills/pr-converge/scripts/README.md +36 -9
  42. package/skills/pr-converge/scripts/check_pr_mergeability.py +1 -2
  43. package/skills/pr-converge/scripts/config/pr_converge_constants.py +74 -5
  44. package/skills/pr-converge/scripts/config/reflow_skill_md_constants.py +13 -0
  45. package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +0 -24
  46. package/skills/pr-converge/scripts/cursor-agents-continue.ahk +22 -2
  47. package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +19 -59
  48. package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +15 -61
  49. package/skills/pr-converge/scripts/fetch_claude_inline_comments.py +70 -0
  50. package/skills/pr-converge/scripts/fetch_claude_reviews.py +61 -0
  51. package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +19 -61
  52. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +14 -74
  53. package/skills/pr-converge/scripts/reflow_skill_md.py +71 -50
  54. package/skills/pr-converge/scripts/reviewer_fetch_core.py +153 -0
  55. package/skills/pr-converge/scripts/reviewer_specs.py +98 -0
  56. package/skills/pr-converge/scripts/test_cursor_agents_continue.py +65 -0
  57. package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +107 -6
  58. package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +85 -6
  59. package/skills/pr-converge/scripts/test_fetch_claude_inline_comments.py +485 -0
  60. package/skills/pr-converge/scripts/test_fetch_claude_reviews.py +368 -0
  61. package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +74 -6
  62. package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +94 -8
  63. package/skills/pr-converge/scripts/test_reflow_skill_md.py +162 -0
  64. package/skills/pr-converge/scripts/test_reviewer_fetch_core.py +448 -0
  65. package/skills/pr-converge/scripts/test_reviewer_specs.py +107 -0
  66. package/skills/pr-converge/scripts/test_view_pr_context.py +44 -0
  67. package/skills/pr-converge/scripts/view_pr_context.py +35 -4
  68. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +24 -22
  69. package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +0 -113
  70. package/skills/bugteam/reference/workflow-path-b-task-harness.md +0 -48
  71. package/skills/bugteam/test_team_lifecycle.py +0 -103
  72. package/skills/monitor-open-prs/test_team_lifecycle.py +0 -46
  73. package/skills/pr-converge/scripts/open_followup_copilot_pr.py +0 -136
  74. package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +0 -236
  75. package/skills/pr-converge/test_team_lifecycle.py +0 -56
  76. package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +0 -108
@@ -0,0 +1,122 @@
1
+ # Convergence gates
2
+
3
+ Run **only** when Step 2 BUGTEAM reports `convergence (zero findings)` AND
4
+ `bugbot_clean_at == current_head` AND no push during bugteam tick. Gates run
5
+ in order; first failure determines next-tick behavior. Mark PR ready only
6
+ when all four pass.
7
+
8
+ ## (a) Copilot findings gate
9
+
10
+ Fetch latest Copilot reviewer (`copilot-pull-request-reviewer[bot]`) review
11
+ plus inline comments anchored to most recent Copilot review on
12
+ `current_head`:
13
+
14
+ ```bash
15
+ python "${CLAUDE_SKILL_DIR}/scripts/fetch_copilot_reviews.py" \
16
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
17
+
18
+ python "${CLAUDE_SKILL_DIR}/scripts/fetch_copilot_inline_comments.py" \
19
+ --owner <OWNER> --repo <REPO> --number <NUMBER> --commit "$current_head"
20
+ ```
21
+
22
+ Decide (four branches; match first whose predicate holds):
23
+
24
+ - **`classification == "dirty"` with non-empty inline comments matching
25
+ `pull_request_review_id`:** Fix protocol input (same shape as bugbot
26
+ dirty). Spawn Agent (subagent_type: clean-coder) to implement → push → reply inline on each thread via
27
+ `reply_to_inline_comment.py` → Step 3 in same tick (see
28
+ [Single-PR fix workflow](fix-protocol.md#single-pr-fix-workflow) for
29
+ full contract).
30
+ Reset `bugbot_clean_at = null` AND `copilot_clean_at = null`, `phase =
31
+ BUGBOT`, schedule next wakeup, return. Full back-to-back-clean cycle
32
+ plus all four gates must hold again on new HEAD.
33
+ - **`classification == "dirty"` with empty inline comments matching
34
+ `pull_request_review_id`:** Copilot posted findings only in review body
35
+ (`CHANGES_REQUESTED` or `COMMENTED` with non-empty body, no inline
36
+ threads). Parse body for actionable findings. Spawn Agent (subagent_type: clean-coder) to implement → push → post
37
+ top-level review reply citing new HEAD SHA → Step 3 in same tick (see
38
+ [Single-PR fix workflow](fix-protocol.md#single-pr-fix-workflow) for
39
+ full contract).
40
+ Reset
41
+ `bugbot_clean_at = null` AND
42
+ `copilot_clean_at = null`, `phase = BUGBOT`, Step 3 on new HEAD,
43
+ schedule next wakeup, return. Convergence requires full
44
+ back-to-back-clean on new HEAD.
45
+ - **`classification == "clean"` (state `APPROVED`):** Set
46
+ `copilot_clean_at = current_head`. Continue to gate (b).
47
+ - **No Copilot review on `current_head` yet:** Skip — gate (c) issues
48
+ proactive request. Continue to gate (b).
49
+
50
+ ## (b) Mergeability gate
51
+
52
+ Resolve PR's mergeability state:
53
+
54
+ ```bash
55
+ python "${CLAUDE_SKILL_DIR}/scripts/check_pr_mergeability.py" \
56
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
57
+ ```
58
+
59
+ Persist `mergeStateStatus` into `merge_state_status`. Decide:
60
+
61
+ - **`mergeStateStatus == "CLEAN"` AND `mergeable == "MERGEABLE"`:**
62
+ Continue to gate (c).
63
+ - **`mergeStateStatus == "DIRTY"` (or `mergeable == "CONFLICTING"`):** Do
64
+ **not** mark ready. Invoke **`rebase`** skill
65
+ ([`../../rebase/SKILL.md`](../../rebase/SKILL.md)) Phase 1–4 against PR's
66
+ base ref. After rebase + force-with-lease push, new HEAD invalidates
67
+ every prior clean state — reset `bugbot_clean_at = null`,
68
+ `copilot_clean_at = null`, `merge_state_status = null`, `phase = BUGBOT`,
69
+ Step 3 on new HEAD, schedule next wakeup, return. Loop re-runs from
70
+ scratch on new HEAD.
71
+ - **`mergeStateStatus` is `BLOCKED`, `BEHIND`, or `UNKNOWN` for
72
+ non-conflict reasons** (required checks pending, branch behind base
73
+ without conflicts GitHub cannot auto-resolve): **hard blocker** per
74
+ [stop-conditions.md](stop-conditions.md) — do not invent a fix. Report specific
75
+ `mergeStateStatus`, omit loop pacing.
76
+
77
+ ## (c) Post-convergence Copilot review request
78
+
79
+ Once gates (a) and (b) both pass (Copilot clean at `current_head` *or* no
80
+ Copilot review yet, AND `mergeStateStatus == "CLEAN"`), request Copilot
81
+ review:
82
+
83
+ ```bash
84
+ python "${CLAUDE_SKILL_DIR}/scripts/request_copilot_review.py" \
85
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
86
+ ```
87
+
88
+ After request, schedule next wakeup and return — next tick checks response.
89
+
90
+ Next tick with `phase == BUGTEAM` and prior state preserved → re-run gate
91
+ (a) first. Decide:
92
+
93
+ - **Copilot review `clean` (state `APPROVED`):** Set `copilot_clean_at =
94
+ current_head`. Mark PR ready (`mark_pr_ready.py`), report convergence
95
+ per §(d), terminate per [stop-conditions.md](stop-conditions.md) / Convergence.
96
+ - **Copilot review `dirty`:** Treat identically to gate (a) dirty path —
97
+ spawn Agent (subagent_type: clean-coder) to fix in same PR, restart convergence from BUGBOT. Follow [Single-PR fix workflow](fix-protocol.md#single-pr-fix-workflow).
98
+ For body-only findings with empty inline, spawn Agent (subagent_type: clean-coder) to implement, then post top-level review reply
99
+ citing new HEAD SHA. Reset `bugbot_clean_at = null` AND
100
+ `copilot_clean_at = null`, `phase = BUGBOT`, schedule next wakeup,
101
+ return. Full back-to-back-clean cycle plus all four gates must hold
102
+ again on new HEAD.
103
+ - **No Copilot review at `current_head` yet (still propagating):**
104
+ Schedule one more wakeup (270s), re-check next tick. After three consecutive empty waits,
105
+ escalate as hard blocker per [stop-conditions.md](stop-conditions.md).
106
+
107
+ ## (d) Mark ready and report
108
+
109
+ Only when all four gates pass — bugbot CLEAN ∧ bugteam CLEAN ∧
110
+ `mergeStateStatus == "CLEAN"` ∧ Copilot CLEAN at HEAD — run:
111
+
112
+ ```bash
113
+ python "${CLAUDE_SKILL_DIR}/scripts/mark_pr_ready.py" \
114
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
115
+ ```
116
+
117
+ When scripts unavailable, `gh pr ready <NUMBER> --repo <OWNER>/<REPO>` is
118
+ equivalent. With `state.json`, append convergence row to
119
+ `<TMPDIR>/pr-converge-<session_id>/converged.log` per `multi-pr-orchestration.md` §Memory; else skip.
120
+ Report: `PR #<NUMBER> converged: bugbot CLEAN at <SHA>, bugteam CLEAN at
121
+ <SHA>, mergeStateStatus CLEAN, copilot CLEAN at <SHA>; marked ready for
122
+ review`. **Omit loop pacing** per **Convergence** of active pacing workflow.
@@ -0,0 +1,76 @@
1
+ # Examples
2
+
3
+ Worked examples for `pr-converge`. Read on demand when a tick's
4
+ classification is novel or ambiguous against the in-skill rules. Cross-refs
5
+ into `SKILL.md` use `§Section name` notation.
6
+
7
+ <example> User: `/pr-converge` Claude: [PR context + one tick bugbot/bugteam
8
+ work; Step 4 per `workflows/schedule-wakeup-loop.md` — default loop until
9
+ convergence or stop]
10
+ </example>
11
+
12
+ <example> BUGBOT tick, latest bugbot review against older commit. Claude:
13
+ [posts `bugbot run`, sets `bugbot_clean_at = null`, Step 4 per
14
+ `workflows/schedule-wakeup-loop.md` (e.g. 270s wakeup), returns]
15
+ </example>
16
+
17
+ <example> BUGBOT tick, bugbot has 2 unaddressed findings on HEAD. Claude:
18
+ [TDD-fixes both, one commit, pushes, replies inline on both threads, posts
19
+ `bugbot run`, Step 4 at 270s, returns]
20
+ </example>
21
+
22
+ <example> BUGBOT tick, bugbot clean against HEAD. Claude: [sets
23
+ `bugbot_clean_at = HEAD`, `phase = BUGTEAM`, runs `Skill({skill: "bugteam",
24
+ ...})` in same tick]
25
+ </example>
26
+
27
+ <example> BUGTEAM phase, bugteam reports convergence and `bugbot_clean_at
28
+ == current_head`. Claude: [runs `gh pr ready <NUMBER>`, reports "PR
29
+ converged: bugbot CLEAN at <SHA>, bugteam CLEAN at <SHA>; marked ready for
30
+ review", applies **Convergence** from `workflows/schedule-wakeup-loop.md`]
31
+ </example>
32
+
33
+ <example> BUGTEAM phase, bugteam pushed fix commit during run. Claude:
34
+ [re-resolves HEAD, sets `bugbot_clean_at = null`, posts `bugbot run` in
35
+ same tick, `phase = BUGBOT`, Step 4 at 270s]
36
+ </example>
37
+
38
+ <example> BUGBOT tick, review body says "found 3 potential issues" against
39
+ HEAD but inline API returns zero matching for `current_head`. Claude:
40
+ [increments `inline_lag_streak` to 1, Step 4 inline-lag rules (90s
41
+ `ScheduleWakeup`), returns]
42
+ </example>
43
+
44
+ <example> Back-to-back clean reached, but `mergeStateStatus: DIRTY` (base
45
+ advanced, merge conflicts). Claude: [runs §Convergence gates (b); does NOT
46
+ mark ready; invokes `rebase` skill per `../../rebase/SKILL.md` Phase 1–4;
47
+ after force-with-lease push, resets `bugbot_clean_at = null`,
48
+ `copilot_clean_at = null`, `merge_state_status = null`, `phase = BUGBOT`,
49
+ posts `bugbot run` on new HEAD, schedules next wakeup]
50
+ </example>
51
+
52
+ <example> Back-to-back clean, mergeability CLEAN, Copilot review at
53
+ `current_head` `state == "CHANGES_REQUESTED"` with two unaddressed inline
54
+ findings. Claude: [runs §Convergence gates (a); applies Fix protocol (TDD
55
+ test → fix → push → reply inline both threads), resets `bugbot_clean_at`
56
+ and `copilot_clean_at` null, `phase = BUGBOT`, posts `bugbot run` on new
57
+ HEAD, schedules next wakeup]
58
+ </example>
59
+
60
+ <example> Back-to-back clean, mergeability CLEAN, no Copilot review on
61
+ `current_head`. Claude requests Copilot via `request_copilot_review.py`,
62
+ waits one tick. Next tick: Copilot review `state: APPROVED`. Claude: [sets
63
+ `copilot_clean_at = current_head`; runs `mark_pr_ready.py`; reports "PR
64
+ #N converged: bugbot CLEAN at <SHA>, bugteam CLEAN at <SHA>,
65
+ mergeStateStatus CLEAN, copilot CLEAN; marked ready for review"]
66
+ </example>
67
+
68
+ <example> Back-to-back clean, mergeability CLEAN, post-convergence Copilot
69
+ review returned `state: CHANGES_REQUESTED` with inline findings on
70
+ `current_head`. Claude: [does NOT mark PR ready — gate (4) failed;
71
+ applies Fix protocol on every confirmed Copilot finding (TDD test → fix →
72
+ push → reply inline on each thread); resets `bugbot_clean_at = null` and
73
+ `copilot_clean_at = null`; `phase = BUGBOT`; posts `bugbot run` on new
74
+ HEAD; schedules next wakeup. Full back-to-back-clean cycle plus all four
75
+ gates must hold again on new HEAD.]
76
+ </example>
@@ -0,0 +1,56 @@
1
+ # Fix protocol
2
+
3
+ Single-PR (no `state.json`): production edits run in main session via
4
+ `Agent` (`subagent_type: "clean-coder"`). Multi-PR (`state.json`):
5
+ clean-coder teammate; orchestrator never edits inline. Hook handling
6
+ per [ground-rules.md](ground-rules.md).
7
+
8
+ **Multi-PR (`state.json`) teammate obligations** (plus TDD, commit, push):
9
+
10
+ - Replies inline on each addressed finding via
11
+ `reply_to_inline_comment.py` (what changed + commit identifier),
12
+ matching §Audit result → fix worker step 4 — **before** writing
13
+ `state.json` and going idle.
14
+ - Writes `last_action: "fix_pushed"`, `current_head: <new SHA>`,
15
+ `bugbot_clean_at: null`, `phase: "BUGBOT"`, `status: "awaiting_bugbot"`,
16
+ `last_updated` (ISO-8601 UTC) to `state.json` (per §Concurrency).
17
+ - Goes idle. Orchestrator spawns follow-up `general-purpose` agent for
18
+ bugbot trigger and monitoring.
19
+
20
+ Orchestrator does not reply inline, trigger bugbot, or read repo source
21
+ files during fix phase in multi-PR mode.
22
+
23
+ ### Single-PR fix workflow
24
+
25
+ **Single-PR (no `state.json`) — same gates, main session executor:**
26
+
27
+ - Read each referenced file:line.
28
+ - Write failing test first when finding has behavior to test. Pure doc /
29
+ comment / naming nits with no behavior → straight to fix.
30
+ - **Implement** via `Agent` (`subagent_type: "clean-coder"`).
31
+ Full-stop if `Agent` is unavailable.
32
+ - Stage affected files and create one new commit on existing branch:
33
+ ```bash
34
+ git add <files> && git commit -m "fix(review): <brief summary>"
35
+ ```
36
+ **Pre-commit gate:** honor hooks; full-stop on bypass.
37
+ - Push the new commit:
38
+ ```bash
39
+ git push origin <BRANCH>
40
+ ```
41
+ **Pre-push gate:** honor hooks; full-stop on bypass. Capture new HEAD
42
+ only after both gates pass; set `current_head`, `bugbot_clean_at = null`.
43
+ - Reply inline on each addressed comment thread using `--body-file` (per
44
+ gh-body-file rule):
45
+ ```bash
46
+ python "${CLAUDE_SKILL_DIR}/scripts/reply_to_inline_comment.py" \
47
+ --owner <OWNER> --repo <REPO> --number <NUMBER> \
48
+ --comment-id <COMMENT_ID> --body-file <path/to/reply.md>
49
+ ```
50
+ - **After pushing a fix, always run Step 3 (`bugbot run`) in the same
51
+ tick** regardless of phase. New commit **resets full convergence cycle**:
52
+ prior bugbot clean and prior bugteam clean on older SHA do **not**
53
+ count toward convergence on new `HEAD`. Must re-obtain bugbot CLEAN on
54
+ `current_head`, then bugteam CLEAN on same `HEAD` with no
55
+ intervening push. Re-triggering in same tick saves a wakeup cycle vs
56
+ deferring Step 3.
@@ -0,0 +1,13 @@
1
+ # Ground rules
2
+
3
+ - **Append commits.** Each tick adds at most one fix commit.
4
+ - **Bugbot findings on current SHA mean fix-then-push-then-`bugbot run`,
5
+ not another naked `bugbot run`.**
6
+ - **All `*_clean_at` and `merge_state_status` reset on every push.**
7
+ - **`bugbot run` comment is load-bearing.** Literal phrase exactly —
8
+ empirically the only re-trigger Cursor Bugbot recognizes.
9
+ - **Honor pre-push and pre-commit hooks.** Read output, fix the cause,
10
+ retry. Full-stop on bypass.
11
+ - **Adapt when reality contradicts on-disk state.** If `state.json`,
12
+ `git`, or `gh` disagree with live PR, escalate as hard blocker per
13
+ [stop-conditions.md](stop-conditions.md).
@@ -0,0 +1,204 @@
1
+ # Multi-PR orchestration model
2
+
3
+ Loaded by `pr-converge` only when `state.json` exists at
4
+ `<TMPDIR>/pr-converge-<session_id>/state.json`. Single-PR runs ignore.
5
+
6
+ ## Core rule: orchestrator is traffic controller only
7
+
8
+ Orchestrator (main session) **never** reads repo source files, writes code,
9
+ audits findings, or does per-PR codebase work inline. Reads `state.json` for
10
+ traffic state, writes only narrow fields per §Orchestrator `state.json`
11
+ writes, receives teammate handoffs, spawns next worker. Every audit/fix unit
12
+ runs inside dedicated teammate.
13
+
14
+ [Workflow-style skill](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices#use-workflows-for-complex-tasks):
15
+ orchestrator splits multi-PR problem into parallel per-PR subworkflows
16
+ owned by short-lived teammates. Orchestrator job: keep state file
17
+ consistent, spawn next agent.
18
+
19
+ ## Per-PR state file
20
+
21
+ Create once at session start. Each teammate writes result before going idle.
22
+
23
+ **Path:** `<TMPDIR>/pr-converge-<session_id>/state.json`. **Session ID:**
24
+ `YYYYMMDDHHMMSS` captured once when loop starts.
25
+
26
+ **Directory lifecycle:** Keep `<TMPDIR>/pr-converge-<session_id>/` until every
27
+ `prs[...]` is `converged` or `blocked`, or user stops. Then delete folder.
28
+ `mark_pr_ready.py` / `gh pr ready` on GitHub is canonical record. See
29
+ [Memory](#memory) for optional append-only log.
30
+
31
+ **Barebones schema:**
32
+
33
+ ```json
34
+ {
35
+ "session_id": "20260502050000",
36
+ "prs": {
37
+ "289": {
38
+ "owner": "jl-cmd",
39
+ "repo": "claude-code-config",
40
+ "branch": "feat/shared-pr-loop-extraction",
41
+ "phase": "BUGBOT",
42
+ "current_head": "f9a7d49e",
43
+ "bugbot_clean_at": null,
44
+ "inline_lag_streak": 0,
45
+ "tick_count": 5,
46
+ "last_action": "bugbot_triggered",
47
+ "status": "in_progress",
48
+ "last_updated": "2026-05-02T10:00:00Z"
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ **`status` values:** `fresh` | `in_progress` | `awaiting_bugbot` |
55
+ `awaiting_bugteam` | `converged` | `blocked`
56
+
57
+ **Write rule:** Subagents read current file, merge **only** their PR's entry
58
+ under `prs`, write back. Writes keyed on `pr_number`; other PRs untouched.
59
+
60
+ **Concurrency (mandatory):** Naive read–modify–write loses updates when
61
+ multiple subagents finish in same wall-clock window (10+ idle notifications
62
+ together). Every subagent write **must** use serialized access plus atomic
63
+ publish:
64
+
65
+ 1. **Acquire** exclusive lock at sibling path `state.json.lock` via atomic
66
+ create-only primitive (`mkdir` on Unix; on Windows `New-Item` / `md`
67
+ guarded so only one creator succeeds, or host file lock API). On
68
+ contention, sleep with jitter and retry. Cap retries and escalate per
69
+ **Stop conditions** if lock never clears (stuck subagent).
70
+ 2. **Read** `state.json`, merge `prs[<pr_number>]` only, write full merged
71
+ JSON to `state.json.tmp`.
72
+ 3. **Replace** `state.json` atomically from `state.json.tmp` (`os.replace` /
73
+ same-volume rename so readers never see half-written file).
74
+ 4. **Release** lock (`rmdir` / `Remove-Item`).
75
+
76
+ **Orchestrator `state.json` writes (traffic metadata only):** Subagents
77
+ own audit/fix payloads. Orchestrator **must not** merge finding bodies,
78
+ file contents, or subagent-owned fields except two exceptions. Uses same
79
+ §Concurrency lock.
80
+
81
+ 1. **Per-tick `tick_count` bump (mandatory):** At start of each tick, one
82
+ locked read–merge–publish: every `prs[<pr_number>]` whose `status` is
83
+ not `converged` or `blocked` → increment `tick_count` by 1 (init `0`),
84
+ refresh `last_updated`. Observability only — no ceiling; loop ends on
85
+ convergence or **Stop conditions**.
86
+ 2. **`phase` when only orchestrator decides:** Orchestrator applies a
87
+ Step 2 phase transition (including BUGTEAM §(d) `phase = BUGBOT`
88
+ without immediate subagent write) and no subagent merge occurs that
89
+ tick → orchestrator performs one locked merge setting only
90
+ `prs[<pr_number>].phase` and `last_updated`.
91
+
92
+ Orchestrator reads file at start of every tick for cross-PR state, not
93
+ conversation context.
94
+
95
+ ## Subagent spawning rules
96
+
97
+ Multiple PRs returning simultaneously (10+ idle notifications) → spawn
98
+ one agent per PR in single parallel message. Never process any PR inline.
99
+
100
+ ### Audit result → fix worker per PR
101
+
102
+ Bugfind subagent completes (findings or clean):
103
+
104
+ - **PRs with findings:** spawn one fix worker per PR via
105
+ `Agent(subagent_type="clean-coder", run_in_background=true)`. Worker:
106
+ 1. Reads outcomes XML.
107
+ 2. Applies TDD fixes (test first, then production).
108
+ 3. Commits, pushes one fix commit.
109
+ 4. Replies inline on each addressed finding via
110
+ `reply_to_inline_comment.py`.
111
+ 5. Writes `state.json` (per §Concurrency): `last_action: "fix_pushed"`,
112
+ `current_head: <new SHA>`, `bugbot_clean_at: null`, `phase:
113
+ "BUGBOT"`, `status: "awaiting_bugbot"`, `last_updated` ISO-8601 UTC.
114
+ 6. Goes idle.
115
+
116
+ - **PRs with zero findings:** spawn one `general-purpose` subagent per PR via
117
+ `Agent(subagent_type="general-purpose", run_in_background=true)`. Subagent:
118
+ 1. `bugbot_clean_at == current_head` (back-to-back clean): run
119
+ `mark_pr_ready.py`, append convergence row to
120
+ `<TMPDIR>/pr-converge-<session_id>/converged.log` per §Memory, then
121
+ write `state.json` (per §Concurrency) with `status: "converged"`,
122
+ `last_action: "converged"` (or `marked_ready`), `phase: "BUGBOT"`,
123
+ `last_updated` ISO-8601 UTC — **before** going idle. Skipping leaves
124
+ orchestrator with stale `awaiting_bugteam` / `in_progress` row, risks
125
+ duplicate work.
126
+ 2. Else: update `state.json` (per §Concurrency) with `last_action:
127
+ "audit_clean"`, `status: "awaiting_bugbot"`, `phase: "BUGBOT"`, then
128
+ trigger bugbot via `trigger_bugbot.py`.
129
+ 3. Goes idle.
130
+
131
+ ### Fix result → general-purpose per PR
132
+
133
+ When bugfix (clean-coder) subagent completes after push:
134
+
135
+ - Spawn one `general-purpose` subagent per PR via
136
+ `Agent(subagent_type="general-purpose", run_in_background=true)`. Subagent:
137
+ 1. Reads `state.json` for its PR.
138
+ 2. Triggers bugbot via `trigger_bugbot.py`.
139
+ 3. Polls `fetch_bugbot_reviews.py` every 60s (up to 10 polls) until review
140
+ anchored to `current_head` appears.
141
+ 4. **Poll / classify loop** (repeat from 4a whenever 4c retries):
142
+ - **4a.** Fetch inline comments via `fetch_bugbot_inline_comments.py`.
143
+ - **4b.** Classify — three outcomes (same as `SKILL.md` Step 2 BUGBOT):
144
+ - **`clean`:** review body clean, zero unaddressed inline findings.
145
+ - **`dirty`:** ≥1 unaddressed inline finding for `current_head`
146
+ (actionable for Fix protocol / `clean-coder`).
147
+ - **`inline_lag`:** review body shows findings, inline API returns
148
+ zero matching for `current_head` (transient desync — `SKILL.md`
149
+ Step 2 BUGBOT fourth bullet).
150
+ - **4c. `inline_lag`:** locked merge: increment `inline_lag_streak`
151
+ (missing → `0` first); set `last_action: "inline_lag_wait"`,
152
+ `phase: "BUGBOT"`, `last_updated`; keep `status` consistent (e.g.
153
+ `awaiting_bugbot`). `inline_lag_streak >= 3` → **hard blocker** per
154
+ `SKILL.md` §Stop conditions (structurally inconsistent review);
155
+ report and go idle **without** classifying as `dirty`. Else sleep
156
+ 90s and repeat from 4a (re-fetch inline only).
157
+ - **4d. `clean`:** exit. Locked merge: `bugbot_clean_at =
158
+ current_head`, reset `inline_lag_streak`, update `last_action`,
159
+ `status`, `phase: BUGTEAM`.
160
+ - **4e. `dirty`:** exit. Locked merge: reset `inline_lag_streak`,
161
+ record findings count, update `last_action`, `status`, `phase:
162
+ BUGBOT`.
163
+ 5. Reports one-line outcome to orchestrator.
164
+
165
+ - Orchestrator reads updated `state.json`, spawns next agent:
166
+ - `clean` → `general-purpose` runs BUGTEAM phase (bugteam via `Skill`
167
+ when available, else inline by reading bugteam `SKILL.md`).
168
+ - Exited on `dirty` (4e) with actionable inline threads → spawn same
169
+ fix worker as "audit result with findings". Do **not** spawn
170
+ `clean-coder` when monitor only saw `inline_lag` (4c retries) without
171
+ reaching 4e — that path retries or escalates via `inline_lag_streak ≥
172
+ 3` hard blocker, not fix pass.
173
+
174
+ ## What orchestrator does per tick
175
+
176
+ 1. Per-tick `tick_count` bump for every non-terminal PR under `prs`.
177
+ 2. Read `state.json`.
178
+ 3. Each PR with new subagent results → spawn next agent per rules, all
179
+ in one parallel message.
180
+ 4. Re-read `state.json` if needed for scheduling.
181
+ 5. Call `ScheduleWakeup` with appropriate delay.
182
+ 6. Nothing else.
183
+
184
+ ## Memory
185
+
186
+ Run directory `<TMPDIR>/pr-converge-<session_id>/` holds `state.json` and
187
+ optional `converged.log`. Keep from first create until every PR under `prs`
188
+ is `converged` or `blocked`, or **Stop conditions** ends loop. Safe to
189
+ delete folder after — `mark_pr_ready.py` / `gh pr ready` on GitHub is
190
+ canonical record. Folder skill, not a plugin package; do **not** rely
191
+ on `${CLAUDE_PLUGIN_DATA}`. OS/disk cleanup of `<TMPDIR>` (reboot, policy)
192
+ can remove files mid-run — environmental risk.
193
+
194
+ **`converged.log` (multi-PR only — requires `state.json`):**
195
+
196
+ - **Path:** sibling of `state.json`.
197
+ - **Format:** one tab-separated row per converged PR: ISO8601 UTC,
198
+ owner/repo#number, bugbot SHA, bugteam SHA.
199
+ - **Append site:** agent running `mark_pr_ready.py`. Append **before**
200
+ locked `state.json` publish so log row survives failed merge.
201
+ - **Never read inside loop.** User / follow-up tooling only.
202
+
203
+ Single-PR runs without `state.json`: do **not** append `converged.log`;
204
+ in-conversation summary plus GitHub ready state suffice.
@@ -0,0 +1,204 @@
1
+ # Per-tick work
2
+
3
+ Use on **draft PR**. Cursor Bugbot and `/bugteam` re-run after each push. Fix
4
+ findings between rounds until back-to-back clean on same `HEAD`, then mark
5
+ PR ready for review.
6
+
7
+ Run every tick in parent harness session. Pacing lives in
8
+ [`../workflows/schedule-wakeup-loop.md`](../workflows/schedule-wakeup-loop.md) (read before Step 4); see [Pacing
9
+ workflow](#pacing-workflow).
10
+
11
+ Every BUGTEAM tick runs **bugteam** — never hand-rolled substitute. Fix
12
+ protocol per [fix-protocol.md](fix-protocol.md). Pacing stays in main session via
13
+ `ScheduleWakeup` (pre-flight aborts when absent).
14
+
15
+ ## Invocation modes
16
+
17
+ - **`/pr-converge`** runs one tick, then Step 4 schedules the next via
18
+ `ScheduleWakeup`. Omit the next wakeup only on convergence or **Stop
19
+ conditions**.
20
+
21
+ ## Pacing workflow
22
+
23
+ Read [`../workflows/schedule-wakeup-loop.md`](../workflows/schedule-wakeup-loop.md)
24
+ (installed copy under `$HOME/.claude/skills/pr-converge/workflows/`) before
25
+ Step 4. The pre-flight gate guarantees `ScheduleWakeup` is invokable; the
26
+ workflow file specifies delays, prompts, convergence cleanup, and
27
+ inline-lag handling.
28
+
29
+ - **`/pr-converge`** (default): loops until convergence. After each tick
30
+ (unless converged or stopped), run **Step 4**.
31
+
32
+ ## Step 1: Resolve current HEAD and PR context
33
+
34
+ Read prior tick's state line from most recent assistant message (or
35
+ initialize fields if none). Increment `tick_count` by 1 in conversation
36
+ state line when **no** `state.json` (single-PR only). With `state.json`, do
37
+ **not** increment here — orchestrator's per-tick bump is sole increment.
38
+
39
+ ```bash
40
+ python "${CLAUDE_SKILL_DIR}/scripts/view_pr_context.py" --owner <OWNER> --repo <REPO> --number <NUMBER>
41
+ ```
42
+
43
+ If owner/repo/number are not yet known, extract them from the PR URL or run without flags in a repo checkout.
44
+
45
+ Capture `number`, `headRefOid` (= `current_head`), owner/repo, branch.
46
+
47
+ ## Step 2: Branch on `phase`
48
+
49
+ ### `phase == BUGBOT`
50
+
51
+ a. Fetch Cursor Bugbot reviews newest-first, walk back until first clean:
52
+
53
+ ```bash
54
+ python "${CLAUDE_SKILL_DIR}/scripts/fetch_bugbot_reviews.py" \
55
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
56
+ ```
57
+
58
+ Track dirty entries in a temp file; Fix protocol reads it back later
59
+ this tick.
60
+
61
+ Iterate from index 0 (most recent) toward older:
62
+
63
+ - Dirty review → append JSON line with `{review_id, commit_id,
64
+ submitted_at, body}`.
65
+ - Stop at first clean. Older reviews presumed addressed at that
66
+ checkpoint.
67
+ - Index 0 clean → `$dirty_reviews_path` stays empty.
68
+
69
+ Capture `commit_id`, `submitted_at`, body, `classification` of index-0
70
+ review for decisions below. When branch routes to **Fix protocol**, address
71
+ **every** entry in `$dirty_reviews_path` — not just index 0.
72
+
73
+ b. Fetch unaddressed inline comments from `cursor[bot]` for newest Bugbot
74
+ review on `current_head`. Script uses same `--paginate --slurp` pattern,
75
+ resolves review via reviews list, returns only inline rows whose
76
+ `pull_request_review_id` matches that review (excludes stale threads from
77
+ older reviews on same SHA).
78
+
79
+ ```bash
80
+ python "${CLAUDE_SKILL_DIR}/scripts/fetch_bugbot_inline_comments.py" \
81
+ --owner <OWNER> --repo <REPO> --number <NUMBER> --commit "$current_head"
82
+ ```
83
+
84
+ c. Decide (four branches; match first whose predicate holds):
85
+ - **No bugbot review yet, OR latest review's `commit_id` ≠
86
+ `current_head`:** Re-trigger bugbot (Step 3), set `bugbot_clean_at =
87
+ null`, reset `inline_lag_streak = 0`, schedule next wakeup, return.
88
+ - **`commit_id == current_head` AND zero unaddressed inline AND review
89
+ body clean:** Set `bugbot_clean_at = current_head`, reset
90
+ `inline_lag_streak = 0`, `phase = BUGTEAM`. Continue BUGTEAM in same
91
+ tick — back-to-back convergence requires bugteam on same HEAD
92
+ before next wakeup.
93
+ - **`commit_id == current_head` with unaddressed inline findings:**
94
+ Apply **Fix protocol**. Reset `inline_lag_streak = 0`. With
95
+ `state.json`: clean-coder teammate pushes, replies inline, writes
96
+ `state.json`, goes idle; Step 3 on new HEAD runs after via
97
+ orchestrator-spawned follow-up agent (§Fix result → general-purpose).
98
+ No `state.json` (single-PR): spawn Agent (subagent_type: clean-coder) to implement → push → reply inline on each thread
99
+ via `reply_to_inline_comment.py` → Step 3 in same tick (see
100
+ [Single-PR fix workflow](fix-protocol.md#single-pr-fix-workflow) for
101
+ full contract).
102
+ Schedule next wakeup, return.
103
+ - **`commit_id == current_head` AND review body findings AND inline
104
+ API zero matching for `current_head`:** Transient API lag. Increment
105
+ `inline_lag_streak`. `>= 3` → hard blocker; report and terminate with
106
+ no loop pacing. Else Step 4 uses the BUGBOT inline-lag section of
107
+ `../workflows/schedule-wakeup-loop.md` (`delaySeconds: 90`).
108
+
109
+ ### `phase == BUGTEAM`
110
+
111
+ a. Run **bugteam** on current PR.
112
+
113
+ - **`Skill` invokable**: invoke bugteam
114
+ with `Skill`.
115
+
116
+ ```
117
+ Skill({skill: "bugteam", args:
118
+ "https://github.com/<OWNER>/<REPO>/pull/<NUMBER>"})
119
+ ```
120
+
121
+ - **`Skill` not invokable** (typical delegated teammate): worker executes
122
+ bugteam by reading [`../../bugteam/SKILL.md`](../../bugteam/SKILL.md). Same
123
+ loop and gates; only harness steps differ.
124
+
125
+ b. **Re-resolve current HEAD** — bugteam may have pushed commits during
126
+ its run. `current_head` from Step 1 is potentially stale:
127
+ ```bash
128
+ new_head=$(python "${CLAUDE_SKILL_DIR}/scripts/resolve_pr_head.py" \
129
+ --owner <OWNER> --repo <REPO> --number <NUMBER>)
130
+ ```
131
+ If `new_head != current_head`, set `current_head = new_head` AND
132
+ `bugbot_clean_at = null`. New commits invalidate bugbot's prior clean.
133
+
134
+ c. Inspect bugteam outcome. Reports `convergence (zero findings)` or list
135
+ of unfixed findings with file:line.
136
+
137
+ d. Decide based on post-bugteam state — order matters. Check
138
+ pushed-during-bugteam FIRST so convergence report against stale HEAD
139
+ never falsely terminates:
140
+ - **Audit pushed this tick (`bugbot_clean_at` reset in step b):**
141
+ Re-trigger bugbot same tick (Step 3) so new HEAD enters queue, `phase
142
+ = BUGBOT`, schedule next wakeup, return.
143
+ - **Convergence AND `bugbot_clean_at == current_head` (no push):**
144
+ Back-to-back clean — necessary, not sufficient. Run **[convergence-gates.md](convergence-gates.md)** to clear Copilot-findings, mergeability, post-convergence
145
+ Copilot-request. Only when all four gates pass mark PR ready and
146
+ **omit loop pacing** per **Convergence** of active pacing workflow.
147
+ - **Convergence BUT `bugbot_clean_at != current_head` (no push):**
148
+ `phase = BUGBOT`, schedule next wakeup, return.
149
+ - **Findings without committed fixes:** spawn Agent (subagent_type: clean-coder) to implement fixes and push, then reply inline via `reply_to_inline_comment.py`, following [Single-PR fix workflow](fix-protocol.md#single-pr-fix-workflow).
150
+ `phase = BUGBOT`, schedule next wakeup, return.
151
+
152
+ ## Step 3: Re-trigger bugbot
153
+
154
+ Prefer portable script (temp body file, `gh pr comment --body-file`):
155
+
156
+ ```bash
157
+ python "${CLAUDE_SKILL_DIR}/scripts/trigger_bugbot.py" \
158
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
159
+ ```
160
+
161
+ **Bundled PowerShell alternative** (same gh-body-file contract):
162
+
163
+ ```bash
164
+ POST_BUGBOT_RUN="$HOME/.claude/skills/pr-converge/scripts/post-bugbot-run.ps1"
165
+ pwsh -NoProfile -ExecutionPolicy Bypass -File "$POST_BUGBOT_RUN" \
166
+ "https://github.com/<OWNER>/<REPO>/pull/<NUMBER>"
167
+ ```
168
+
169
+ `bugbot run` is empirically the only re-trigger Cursor Bugbot recognizes;
170
+ alternative phrasings (`re-review`, `bugbot please`, etc.) silently no-op.
171
+
172
+ **Gotcha (duplicate `bugbot run` while review queued):** Skip Step 3 when
173
+ the latest `bugbot run` PR comment has an `:eyes:` or `:+1:` reaction; wait
174
+ for review or HEAD change before re-triggering.
175
+
176
+ ## Step 4: Loop pacing
177
+
178
+ **`ScheduleWakeup` field hints** (prefer [Pacing
179
+ workflow](#pacing-workflow)):
180
+
181
+ - `delaySeconds: 270` after bugbot re-trigger. Bugbot finishes in 1–4
182
+ min; 270s stays under 5-min prompt-cache TTL with margin. Exception:
183
+ BUGBOT inline-lag branch uses `delaySeconds: 90` (no re-trigger;
184
+ awaiting GitHub inline API).
185
+ - `reason`: short sentence on what is awaited, including `phase` and
186
+ `bugbot_clean_at` SHA.
187
+ - `prompt: "/pr-converge"`.
188
+
189
+ **On convergence:** apply **Convergence** section of
190
+ `../workflows/schedule-wakeup-loop.md` (omit wakeups).
191
+
192
+ ## Bugteam execution
193
+
194
+ **Second audit** (BUGTEAM phase) is **always** **bugteam** skill: preflight,
195
+ CODE_RULES gate, **`code-quality-agent`** / **`clean-coder`** loop, audit
196
+ rubric, outcome shape, Step 2 BUGTEAM §(b)–(d) contract — all in
197
+ [`../../bugteam/SKILL.md`](../../bugteam/SKILL.md) plus `PROMPTS.md` / `EXAMPLES.md` /
198
+ `CONSTRAINTS.md`. Do not re-spec.
199
+
200
+ **pr-converge rule:** Prefer **`Skill({skill: "bugteam", args: "<PR URL or
201
+ args>"})`** wherever registry exposes `Skill`. When `Skill` not invokable
202
+ (typical delegated teammate), worker runs **bugteam** by loading
203
+ `../../bugteam/SKILL.md` from the same checkout. If bugteam cannot run, cancel the
204
+ convergence loop fully and report the issue to the user.