claude-dev-env 1.36.1 → 1.37.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 (101) hide show
  1. package/_shared/pr-loop/audit-contract.md +159 -0
  2. package/_shared/pr-loop/code-rules-gate.md +64 -0
  3. package/_shared/pr-loop/fix-protocol.md +37 -0
  4. package/_shared/pr-loop/gh-payloads.md +85 -0
  5. package/_shared/pr-loop/scripts/README.md +20 -0
  6. package/_shared/pr-loop/scripts/_claude_permissions_common.py +234 -0
  7. package/_shared/pr-loop/scripts/code_rules_gate.py +975 -0
  8. package/_shared/pr-loop/scripts/config/__init__.py +0 -0
  9. package/_shared/pr-loop/scripts/config/claude_permissions_constants.py +36 -0
  10. package/_shared/pr-loop/scripts/config/claude_settings_keys_constants.py +11 -0
  11. package/_shared/pr-loop/scripts/config/code_rules_gate_constants.py +56 -0
  12. package/_shared/pr-loop/scripts/config/fix_hookspath_constants.py +25 -0
  13. package/_shared/pr-loop/scripts/config/gh_util_constants.py +31 -0
  14. package/_shared/pr-loop/scripts/config/preflight_constants.py +68 -0
  15. package/_shared/pr-loop/scripts/fix_hookspath.py +260 -0
  16. package/_shared/pr-loop/scripts/gh_util.py +193 -0
  17. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +130 -0
  18. package/_shared/pr-loop/scripts/preflight.py +449 -0
  19. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +156 -0
  20. package/_shared/pr-loop/scripts/tests/conftest.py +51 -0
  21. package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +135 -0
  22. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +169 -0
  23. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +58 -0
  24. package/_shared/pr-loop/scripts/tests/test_claude_settings_keys_constants.py +50 -0
  25. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +917 -0
  26. package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +102 -0
  27. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +374 -0
  28. package/_shared/pr-loop/scripts/tests/test_fix_hookspath_constants.py +47 -0
  29. package/_shared/pr-loop/scripts/tests/test_gh_util.py +257 -0
  30. package/_shared/pr-loop/scripts/tests/test_gh_util_constants.py +61 -0
  31. package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +49 -0
  32. package/_shared/pr-loop/scripts/tests/test_preflight.py +670 -0
  33. package/_shared/pr-loop/scripts/tests/test_preflight_constants.py +77 -0
  34. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +49 -0
  35. package/_shared/pr-loop/state-schema.md +81 -0
  36. package/hooks/blocking/code_rules_enforcer.py +269 -23
  37. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +157 -1
  38. package/hooks/config/test_unused_module_import_constants.py +48 -0
  39. package/hooks/config/unused_module_import_constants.py +41 -0
  40. package/package.json +2 -1
  41. package/skills/bg-agent/SKILL.md +69 -0
  42. package/skills/bugteam/CONSTRAINTS.md +10 -19
  43. package/skills/bugteam/PROMPTS.md +3 -3
  44. package/skills/bugteam/SKILL.md +103 -202
  45. package/skills/bugteam/SKILL_EVALS.md +75 -114
  46. package/skills/bugteam/reference/README.md +2 -4
  47. package/skills/bugteam/reference/design-rationale.md +3 -8
  48. package/skills/bugteam/reference/team-setup.md +11 -19
  49. package/skills/bugteam/reference/teardown-publish-permissions.md +2 -14
  50. package/skills/bugteam/scripts/config/__init__.py +0 -0
  51. package/skills/bugteam/scripts/config/reflow_skill_md_constants.py +12 -0
  52. package/skills/bugteam/scripts/reflow_skill_md.py +51 -47
  53. package/skills/bugteam/sources.md +1 -25
  54. package/skills/bugteam/test_skill_additions.py +4 -13
  55. package/skills/fresh-branch/SKILL.md +71 -0
  56. package/skills/gotcha/SKILL.md +73 -0
  57. package/skills/monitor-open-prs/SKILL.md +4 -37
  58. package/skills/monitor-open-prs/test_skill_contract.py +0 -5
  59. package/skills/pr-converge/SKILL.md +60 -1298
  60. package/skills/pr-converge/reference/convergence-gates.md +118 -0
  61. package/skills/pr-converge/reference/examples.md +76 -0
  62. package/skills/pr-converge/reference/fix-protocol.md +54 -0
  63. package/skills/pr-converge/reference/ground-rules.md +13 -0
  64. package/skills/pr-converge/reference/multi-pr-orchestration.md +204 -0
  65. package/skills/pr-converge/reference/per-tick.md +201 -0
  66. package/skills/pr-converge/reference/state-schema.md +19 -0
  67. package/skills/pr-converge/reference/stop-conditions.md +26 -0
  68. package/skills/pr-converge/scripts/README.md +36 -9
  69. package/skills/pr-converge/scripts/check_pr_mergeability.py +1 -2
  70. package/skills/pr-converge/scripts/config/pr_converge_constants.py +58 -5
  71. package/skills/pr-converge/scripts/config/reflow_skill_md_constants.py +13 -0
  72. package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +0 -24
  73. package/skills/pr-converge/scripts/cursor-agents-continue.ahk +22 -2
  74. package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +19 -59
  75. package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +15 -61
  76. package/skills/pr-converge/scripts/fetch_claude_inline_comments.py +70 -0
  77. package/skills/pr-converge/scripts/fetch_claude_reviews.py +61 -0
  78. package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +19 -61
  79. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +14 -74
  80. package/skills/pr-converge/scripts/reflow_skill_md.py +71 -50
  81. package/skills/pr-converge/scripts/reviewer_fetch_core.py +153 -0
  82. package/skills/pr-converge/scripts/reviewer_specs.py +98 -0
  83. package/skills/pr-converge/scripts/test_cursor_agents_continue.py +65 -0
  84. package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +107 -6
  85. package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +85 -6
  86. package/skills/pr-converge/scripts/test_fetch_claude_inline_comments.py +485 -0
  87. package/skills/pr-converge/scripts/test_fetch_claude_reviews.py +368 -0
  88. package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +74 -6
  89. package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +94 -8
  90. package/skills/pr-converge/scripts/test_reflow_skill_md.py +162 -0
  91. package/skills/pr-converge/scripts/test_reviewer_fetch_core.py +448 -0
  92. package/skills/pr-converge/scripts/test_reviewer_specs.py +107 -0
  93. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +24 -22
  94. package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +0 -113
  95. package/skills/bugteam/reference/workflow-path-b-task-harness.md +0 -48
  96. package/skills/bugteam/test_team_lifecycle.py +0 -103
  97. package/skills/monitor-open-prs/test_team_lifecycle.py +0 -46
  98. package/skills/pr-converge/scripts/open_followup_copilot_pr.py +0 -136
  99. package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +0 -236
  100. package/skills/pr-converge/test_team_lifecycle.py +0 -56
  101. package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +0 -108
@@ -0,0 +1,118 @@
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). Apply Fix protocol on every inline finding (TDD test →
27
+ production fix → push → reply inline on each thread), reset
28
+ `bugbot_clean_at = null` AND `copilot_clean_at = null`, `phase = BUGBOT`,
29
+ Step 3 on new HEAD, schedule next wakeup, return. Full
30
+ back-to-back-clean cycle plus all four gates must hold again on new HEAD.
31
+ - **`classification == "dirty"` with empty inline comments matching
32
+ `pull_request_review_id`:** Copilot posted findings only in review body
33
+ (`CHANGES_REQUESTED` or `COMMENTED` with non-empty body, no inline
34
+ threads). Parse body for actionable findings, apply Fix protocol using
35
+ body excerpts (TDD test → production fix → push). Post top-level review
36
+ reply acknowledging fixes and citing new HEAD SHA. Reset
37
+ `bugbot_clean_at = null` AND `copilot_clean_at = null`, `phase =
38
+ BUGBOT`, Step 3 on new HEAD, schedule next wakeup, return. Convergence
39
+ requires full back-to-back-clean on new HEAD.
40
+ - **`classification == "clean"` (state `APPROVED`):** Set
41
+ `copilot_clean_at = current_head`. Continue to gate (b).
42
+ - **No Copilot review on `current_head` yet:** Skip — gate (c) issues
43
+ proactive request. Continue to gate (b).
44
+
45
+ ## (b) Mergeability gate
46
+
47
+ Resolve PR's mergeability state:
48
+
49
+ ```bash
50
+ python "${CLAUDE_SKILL_DIR}/scripts/check_pr_mergeability.py" \
51
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
52
+ ```
53
+
54
+ Persist `mergeStateStatus` into `merge_state_status`. Decide:
55
+
56
+ - **`mergeStateStatus == "CLEAN"` AND `mergeable == "MERGEABLE"`:**
57
+ Continue to gate (c).
58
+ - **`mergeStateStatus == "DIRTY"` (or `mergeable == "CONFLICTING"`):** Do
59
+ **not** mark ready. Invoke **`rebase`** skill
60
+ ([`../../rebase/SKILL.md`](../../rebase/SKILL.md)) Phase 1–4 against PR's
61
+ base ref. After rebase + force-with-lease push, new HEAD invalidates
62
+ every prior clean state — reset `bugbot_clean_at = null`,
63
+ `copilot_clean_at = null`, `merge_state_status = null`, `phase = BUGBOT`,
64
+ Step 3 on new HEAD, schedule next wakeup, return. Loop re-runs from
65
+ scratch on new HEAD.
66
+ - **`mergeStateStatus` is `BLOCKED`, `BEHIND`, or `UNKNOWN` for
67
+ non-conflict reasons** (required checks pending, branch behind base
68
+ without conflicts GitHub cannot auto-resolve): **hard blocker** per
69
+ [stop-conditions.md](stop-conditions.md) — do not invent a fix. Report specific
70
+ `mergeStateStatus`, omit loop pacing.
71
+
72
+ ## (c) Post-convergence Copilot review request
73
+
74
+ Once gates (a) and (b) both pass (Copilot clean at `current_head` *or* no
75
+ Copilot review yet, AND `mergeStateStatus == "CLEAN"`), request Copilot
76
+ review:
77
+
78
+ ```bash
79
+ python "${CLAUDE_SKILL_DIR}/scripts/request_copilot_review.py" \
80
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
81
+ ```
82
+
83
+ After request, schedule next wakeup and return — next tick checks response.
84
+
85
+ Next tick with `phase == BUGTEAM` and prior state preserved → re-run gate
86
+ (a) first. Decide:
87
+
88
+ - **Copilot review `clean` (state `APPROVED`):** Set `copilot_clean_at =
89
+ current_head`. Mark PR ready (`mark_pr_ready.py`), report convergence
90
+ per §(d), terminate per [stop-conditions.md](stop-conditions.md) / Convergence.
91
+ - **Copilot review `dirty`:** Treat identically to gate (a) dirty path —
92
+ fix in same PR, restart convergence from BUGBOT. Apply Fix protocol on
93
+ every confirmed Copilot finding (TDD test → production fix → push →
94
+ reply inline on each thread; for body-only findings with empty inline,
95
+ parse body excerpts and post top-level review reply citing new HEAD).
96
+ Reset `bugbot_clean_at = null` AND `copilot_clean_at = null`, `phase =
97
+ BUGBOT`, Step 3 on new HEAD, schedule next wakeup, return. Full
98
+ back-to-back-clean cycle plus all four gates must hold again on new HEAD.
99
+ - **No Copilot review at `current_head` yet (still propagating):**
100
+ Schedule one more wakeup (270s), re-check next tick. After three consecutive empty waits,
101
+ escalate as hard blocker per [stop-conditions.md](stop-conditions.md).
102
+
103
+ ## (d) Mark ready and report
104
+
105
+ Only when all four gates pass — bugbot CLEAN ∧ bugteam CLEAN ∧
106
+ `mergeStateStatus == "CLEAN"` ∧ Copilot CLEAN at HEAD — run:
107
+
108
+ ```bash
109
+ python "${CLAUDE_SKILL_DIR}/scripts/mark_pr_ready.py" \
110
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
111
+ ```
112
+
113
+ When scripts unavailable, `gh pr ready <NUMBER> --repo <OWNER>/<REPO>` is
114
+ equivalent. With `state.json`, append convergence row to
115
+ `<TMPDIR>/pr-converge-<session_id>/converged.log` per `multi-pr-orchestration.md` §Memory; else skip.
116
+ Report: `PR #<NUMBER> converged: bugbot CLEAN at <SHA>, bugteam CLEAN at
117
+ <SHA>, mergeStateStatus CLEAN, copilot CLEAN at <SHA>; marked ready for
118
+ 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,54 @@
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 (no `state.json`) — same gates, main session executor:**
24
+
25
+ - Read each referenced file:line.
26
+ - Write failing test first when finding has behavior to test. Pure doc /
27
+ comment / naming nits with no behavior → straight to fix.
28
+ - **Implement** via `Agent` (`subagent_type: "clean-coder"`).
29
+ Full-stop if `Agent` is unavailable.
30
+ - Stage affected files and create one new commit on existing branch:
31
+ ```bash
32
+ git add <files> && git commit -m "fix(review): <brief summary>"
33
+ ```
34
+ **Pre-commit gate:** honor hooks; full-stop on bypass.
35
+ - Push the new commit:
36
+ ```bash
37
+ git push origin <BRANCH>
38
+ ```
39
+ **Pre-push gate:** honor hooks; full-stop on bypass. Capture new HEAD
40
+ only after both gates pass; set `current_head`, `bugbot_clean_at = null`.
41
+ - Reply inline on each addressed comment thread using `--body-file` (per
42
+ gh-body-file rule):
43
+ ```bash
44
+ python "${CLAUDE_SKILL_DIR}/scripts/reply_to_inline_comment.py" \
45
+ --owner <OWNER> --repo <REPO> --number <NUMBER> \
46
+ --comment-id <COMMENT_ID> --body-file <path/to/reply.md>
47
+ ```
48
+ - **After pushing a fix, always run Step 3 (`bugbot run`) in the same
49
+ tick** regardless of phase. New commit **resets full convergence cycle**:
50
+ prior bugbot clean and prior bugteam clean on older SHA do **not**
51
+ count toward convergence on new `HEAD`. Must re-obtain bugbot CLEAN on
52
+ `current_head`, then bugteam CLEAN on same `HEAD` with no
53
+ intervening push. Re-triggering in same tick saves a wakeup cycle vs
54
+ 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,201 @@
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"
41
+ ```
42
+
43
+ Capture `number`, `headRefOid` (= `current_head`), owner/repo, branch.
44
+
45
+ ## Step 2: Branch on `phase`
46
+
47
+ ### `phase == BUGBOT`
48
+
49
+ a. Fetch Cursor Bugbot reviews newest-first, walk back until first clean:
50
+
51
+ ```bash
52
+ python "${CLAUDE_SKILL_DIR}/scripts/fetch_bugbot_reviews.py" \
53
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
54
+ ```
55
+
56
+ Track dirty entries in a temp file; Fix protocol reads it back later
57
+ this tick.
58
+
59
+ Iterate from index 0 (most recent) toward older:
60
+
61
+ - Dirty review → append JSON line with `{review_id, commit_id,
62
+ submitted_at, body}`.
63
+ - Stop at first clean. Older reviews presumed addressed at that
64
+ checkpoint.
65
+ - Index 0 clean → `$dirty_reviews_path` stays empty.
66
+
67
+ Capture `commit_id`, `submitted_at`, body, `classification` of index-0
68
+ review for decisions below. When branch routes to **Fix protocol**, address
69
+ **every** entry in `$dirty_reviews_path` — not just index 0.
70
+
71
+ b. Fetch unaddressed inline comments from `cursor[bot]` for newest Bugbot
72
+ review on `current_head`. Script uses same `--paginate --slurp` pattern,
73
+ resolves review via reviews list, returns only inline rows whose
74
+ `pull_request_review_id` matches that review (excludes stale threads from
75
+ older reviews on same SHA).
76
+
77
+ ```bash
78
+ python "${CLAUDE_SKILL_DIR}/scripts/fetch_bugbot_inline_comments.py" \
79
+ --owner <OWNER> --repo <REPO> --number <NUMBER> --commit "$current_head"
80
+ ```
81
+
82
+ c. Decide (four branches; match first whose predicate holds):
83
+ - **No bugbot review yet, OR latest review's `commit_id` ≠
84
+ `current_head`:** Re-trigger bugbot (Step 3), set `bugbot_clean_at =
85
+ null`, reset `inline_lag_streak = 0`, schedule next wakeup, return.
86
+ - **`commit_id == current_head` AND zero unaddressed inline AND review
87
+ body clean:** Set `bugbot_clean_at = current_head`, reset
88
+ `inline_lag_streak = 0`, `phase = BUGTEAM`. Continue BUGTEAM in same
89
+ tick — back-to-back convergence requires bugteam on same HEAD
90
+ before next wakeup.
91
+ - **`commit_id == current_head` with unaddressed inline findings:**
92
+ Apply **Fix protocol**. Reset `inline_lag_streak = 0`. With
93
+ `state.json`: clean-coder teammate pushes, replies inline, writes
94
+ `state.json`, goes idle; Step 3 on new HEAD runs after via
95
+ orchestrator-spawned follow-up agent (§Fix result → general-purpose).
96
+ No `state.json` (single-PR): implement → push → inline replies
97
+ → Step 3 in same tick per loaded pacing workflow. Schedule next
98
+ wakeup, return.
99
+ - **`commit_id == current_head` AND review body findings AND inline
100
+ API zero matching for `current_head`:** Transient API lag. Increment
101
+ `inline_lag_streak`. `>= 3` → hard blocker; report and terminate with
102
+ no loop pacing. Else Step 4 uses the BUGBOT inline-lag section of
103
+ `../workflows/schedule-wakeup-loop.md` (`delaySeconds: 90`).
104
+
105
+ ### `phase == BUGTEAM`
106
+
107
+ a. Run **bugteam** on current PR.
108
+
109
+ - **`Skill` invokable**: invoke bugteam
110
+ with `Skill`.
111
+
112
+ ```
113
+ Skill({skill: "bugteam", args:
114
+ "https://github.com/<OWNER>/<REPO>/pull/<NUMBER>"})
115
+ ```
116
+
117
+ - **`Skill` not invokable** (typical delegated teammate): worker executes
118
+ bugteam by reading [`../../bugteam/SKILL.md`](../../bugteam/SKILL.md). Same
119
+ loop and gates; only harness steps differ.
120
+
121
+ b. **Re-resolve current HEAD** — bugteam may have pushed commits during
122
+ its run. `current_head` from Step 1 is potentially stale:
123
+ ```bash
124
+ new_head=$(python "${CLAUDE_SKILL_DIR}/scripts/resolve_pr_head.py" \
125
+ --owner <OWNER> --repo <REPO> --number <NUMBER>)
126
+ ```
127
+ If `new_head != current_head`, set `current_head = new_head` AND
128
+ `bugbot_clean_at = null`. New commits invalidate bugbot's prior clean.
129
+
130
+ c. Inspect bugteam outcome. Reports `convergence (zero findings)` or list
131
+ of unfixed findings with file:line.
132
+
133
+ d. Decide based on post-bugteam state — order matters. Check
134
+ pushed-during-bugteam FIRST so convergence report against stale HEAD
135
+ never falsely terminates:
136
+ - **Audit pushed this tick (`bugbot_clean_at` reset in step b):**
137
+ Re-trigger bugbot same tick (Step 3) so new HEAD enters queue, `phase
138
+ = BUGBOT`, schedule next wakeup, return.
139
+ - **Convergence AND `bugbot_clean_at == current_head` (no push):**
140
+ Back-to-back clean — necessary, not sufficient. Run **[convergence-gates.md](convergence-gates.md)** to clear Copilot-findings, mergeability, post-convergence
141
+ Copilot-request. Only when all four gates pass mark PR ready and
142
+ **omit loop pacing** per **Convergence** of active pacing workflow.
143
+ - **Convergence BUT `bugbot_clean_at != current_head` (no push):**
144
+ `phase = BUGBOT`, schedule next wakeup, return.
145
+ - **Findings without committed fixes:** apply **[fix-protocol.md](fix-protocol.md)**; Step 3
146
+ on new HEAD runs after fix handoff per `multi-pr-orchestration.md` or in-tick for
147
+ single-PR. `phase = BUGBOT`, schedule next wakeup, return.
148
+
149
+ ## Step 3: Re-trigger bugbot
150
+
151
+ Prefer portable script (temp body file, `gh pr comment --body-file`):
152
+
153
+ ```bash
154
+ python "${CLAUDE_SKILL_DIR}/scripts/trigger_bugbot.py" \
155
+ --owner <OWNER> --repo <REPO> --number <NUMBER>
156
+ ```
157
+
158
+ **Bundled PowerShell alternative** (same gh-body-file contract):
159
+
160
+ ```bash
161
+ POST_BUGBOT_RUN="$HOME/.claude/skills/pr-converge/scripts/post-bugbot-run.ps1"
162
+ pwsh -NoProfile -ExecutionPolicy Bypass -File "$POST_BUGBOT_RUN" \
163
+ "https://github.com/<OWNER>/<REPO>/pull/<NUMBER>"
164
+ ```
165
+
166
+ `bugbot run` is empirically the only re-trigger Cursor Bugbot recognizes;
167
+ alternative phrasings (`re-review`, `bugbot please`, etc.) silently no-op.
168
+
169
+ **Gotcha (duplicate `bugbot run` while review queued):** Skip Step 3 when
170
+ the latest `bugbot run` PR comment has an `:eyes:` or `:+1:` reaction; wait
171
+ for review or HEAD change before re-triggering.
172
+
173
+ ## Step 4: Loop pacing
174
+
175
+ **`ScheduleWakeup` field hints** (prefer [Pacing
176
+ workflow](#pacing-workflow)):
177
+
178
+ - `delaySeconds: 270` after bugbot re-trigger. Bugbot finishes in 1–4
179
+ min; 270s stays under 5-min prompt-cache TTL with margin. Exception:
180
+ BUGBOT inline-lag branch uses `delaySeconds: 90` (no re-trigger;
181
+ awaiting GitHub inline API).
182
+ - `reason`: short sentence on what is awaited, including `phase` and
183
+ `bugbot_clean_at` SHA.
184
+ - `prompt: "/pr-converge"`.
185
+
186
+ **On convergence:** apply **Convergence** section of
187
+ `../workflows/schedule-wakeup-loop.md` (omit wakeups).
188
+
189
+ ## Bugteam execution
190
+
191
+ **Second audit** (BUGTEAM phase) is **always** **bugteam** skill: preflight,
192
+ CODE_RULES gate, **`code-quality-agent`** / **`clean-coder`** loop, audit
193
+ rubric, outcome shape, Step 2 BUGTEAM §(b)–(d) contract — all in
194
+ [`../../bugteam/SKILL.md`](../../bugteam/SKILL.md) plus `PROMPTS.md` / `EXAMPLES.md` /
195
+ `CONSTRAINTS.md`. Do not re-spec.
196
+
197
+ **pr-converge rule:** Prefer **`Skill({skill: "bugteam", args: "<PR URL or
198
+ args>"})`** wherever registry exposes `Skill`. When `Skill` not invokable
199
+ (typical delegated teammate), worker runs **bugteam** by loading
200
+ `../../bugteam/SKILL.md` from the same checkout. If bugteam cannot run, cancel the
201
+ convergence loop fully and report the issue to the user.