claude-dev-env 1.21.2 → 1.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/bugteam/SKILL.md +530 -0
- package/skills/bugteam/_claude_permissions_common.py +224 -0
- package/skills/bugteam/grant_project_claude_permissions.py +110 -0
- package/skills/bugteam/revoke_project_claude_permissions.py +136 -0
- package/skills/findbugs/SKILL.md +203 -0
- package/skills/fixbugs/SKILL.md +143 -0
package/package.json
CHANGED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bugteam
|
|
3
|
+
description: >-
|
|
4
|
+
Runs an autonomous audit-and-fix loop on the current branch's PR using a
|
|
5
|
+
Claude Code agent team — bugfind teammate (code-quality-agent, clean-room
|
|
6
|
+
audit) and bugfix teammate (clean-coder, sonnet fix) — until the audit
|
|
7
|
+
returns zero bugs or a 10-loop safety cap is reached. One up-front
|
|
8
|
+
confirmation authorizes the entire cycle. Each audit teammate is spawned
|
|
9
|
+
fresh per loop to prevent anchoring bias. Wraps the cycle with project
|
|
10
|
+
permission grant/revoke. Requires CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
|
|
11
|
+
and Claude Code v2.1.32+. Triggers: '/bugteam', 'run the bug team',
|
|
12
|
+
'auto-fix the PR until clean', 'loop audit and fix'.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Bugteam
|
|
16
|
+
|
|
17
|
+
**Core principle:** A Claude Code **agent team** runs the audit-and-fix loop until convergence. The bugfind teammate audits clean-room (own context window, no chat history); the bugfix teammate addresses each audit's findings; both spawn fresh per loop. A 10-loop hard cap prevents runaway cost. Project permissions are granted at session start and revoked at session end.
|
|
18
|
+
|
|
19
|
+
> **Source:** [Anthropic — Orchestrate teams of Claude Code sessions](https://code.claude.com/docs/en/agent-teams). Direct quote: *"Each teammate has its own context window. When spawned, a teammate loads the same project context as a regular session: CLAUDE.md, MCP servers, and skills. It also receives the spawn prompt from the lead. The lead's conversation history does not carry over."* That isolation is the design's whole point — independent context per teammate enforces the clean-room property automatically.
|
|
20
|
+
|
|
21
|
+
> **Why agent teams, not parallel subagents:** Subagents return their results into the lead's context, which accumulates across loops. Agent team teammates are independent sessions with their own context windows and do not pollute the lead. The lead can shut down + respawn each loop, guaranteeing every audit starts fresh. Per the docs: *"Use subagents when you need quick, focused workers that report back. Use agent teams when teammates need to share findings, challenge each other, and coordinate on their own."* For this skill, the independent-context property is what we need; parallel subagents fail the clean-room requirement.
|
|
22
|
+
|
|
23
|
+
## Contents
|
|
24
|
+
|
|
25
|
+
This file is 400+ lines. The list below is for the LLM reading this skill — partial reads (e.g., `head -100`) miss what comes later, so this section ensures the full scope is visible from the top. (Per Anthropic's [Skill authoring best practices — Structure longer reference files with table of contents](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices#structure-longer-reference-files-with-table-of-contents).)
|
|
26
|
+
|
|
27
|
+
- When this skill applies — refusal cases (4) and trigger conditions
|
|
28
|
+
- The Process — Progress checklist + Steps 0–6
|
|
29
|
+
- Step 0 — Grant project permissions
|
|
30
|
+
- Step 1 — Resolve PR scope
|
|
31
|
+
- Step 2 — Create the agent team
|
|
32
|
+
- Step 2.5 — PR comment lifecycle (loop comment, finding comments, fix replies)
|
|
33
|
+
- Step 3 — The cycle (AUDIT ↔ FIX, decision table, exit conditions)
|
|
34
|
+
- Step 4 — Tear down the team and clean working tree
|
|
35
|
+
- Step 4.5 — Finalize the PR description (via pr-description-writer)
|
|
36
|
+
- Step 5 — Revoke project permissions
|
|
37
|
+
- Step 6 — Print the final report
|
|
38
|
+
- Constraints — invariants the implementer must preserve
|
|
39
|
+
- Examples — five end-to-end scenarios
|
|
40
|
+
- Why this design — rationale for agent-teams + clean-room + grant/revoke
|
|
41
|
+
|
|
42
|
+
## When this skill applies
|
|
43
|
+
|
|
44
|
+
User wants automated convergence on a clean PR without babysitting each step. Typed `/bugteam` once = full authorization for up to 10 audit cycles and the corresponding fix commits.
|
|
45
|
+
|
|
46
|
+
Refusal cases — check in order; first match short-circuits and stops:
|
|
47
|
+
|
|
48
|
+
- **Agent teams not enabled.** Check `claude config get env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` and `~/.claude/settings.json`. If neither sets it to `"1"`, respond: `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 not set. /bugteam requires the agent teams feature. See https://code.claude.com/docs/en/agent-teams#enable-agent-teams.` and stop.
|
|
49
|
+
- **Claude Code version too old.** Run `claude --version`. If older than v2.1.32, respond: `Claude Code v<version> is older than the v2.1.32 minimum for agent teams. Upgrade first.` and stop.
|
|
50
|
+
- **No PR or upstream diff.** Respond exactly: `No PR or upstream diff. /bugteam needs a target.` and stop.
|
|
51
|
+
- **Working tree dirty with uncommitted changes the user did not stage.** Respond: `Uncommitted changes detected. Stash, commit, or revert before /bugteam.` and stop. Reason: the fix teammate will commit the working tree, mixing user-uncommitted work into automated fixes.
|
|
52
|
+
- **Required subagents not installed.** Before Step 0, verify `code-quality-agent` and `clean-coder` subagent types exist in the available agents list. If either is missing, respond: `Required subagent type <name> not installed. /bugteam needs both code-quality-agent and clean-coder available.` and stop.
|
|
53
|
+
|
|
54
|
+
## The Process
|
|
55
|
+
|
|
56
|
+
### Progress checklist (copy at start, tick as you go)
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
[ ] Step 0: project permissions granted
|
|
60
|
+
[ ] Step 1: PR scope resolved
|
|
61
|
+
[ ] Step 2: agent team created + initial loop state set
|
|
62
|
+
[ ] Step 3: cycle complete (converged | cap reached | stuck | error)
|
|
63
|
+
[ ] Step 4: team torn down + working tree clean
|
|
64
|
+
[ ] Step 4.5: PR description rewritten (or skip warning logged)
|
|
65
|
+
[ ] Step 5: project permissions revoked
|
|
66
|
+
[ ] Step 6: final report printed
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Step 0: Grant project permissions (mandatory, runs once)
|
|
70
|
+
|
|
71
|
+
Before spawning any teammates, grant the team session write access to the project's `.claude/**` tree:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python "${CLAUDE_SKILL_DIR}/grant_project_claude_permissions.py"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Note: `${CLAUDE_SKILL_DIR}` is a Claude Code host-managed token, pre-substituted by the runtime before any shell sees it. Unlike `${TMPDIR}` and similar shell parameter expansions, it does not depend on the shell's expansion semantics, so it works identically on Unix and Windows shells.
|
|
78
|
+
|
|
79
|
+
The script reads `Path.cwd()` and writes idempotent allow rules into `~/.claude/settings.json`. Run from the project root. If it fails (non-zero exit), surface the error and stop — do not proceed without the grant.
|
|
80
|
+
|
|
81
|
+
This is the FIRST action of every `/bugteam` invocation, before any team creation, before any agent spawn. The corresponding revoke runs at Step 5 regardless of how the cycle exits.
|
|
82
|
+
|
|
83
|
+
### Step 1: Resolve PR scope (once, persisted across loops)
|
|
84
|
+
|
|
85
|
+
Same resolution path as `/findbugs`:
|
|
86
|
+
|
|
87
|
+
1. `gh pr view --json number,baseRefName,headRefName,url` from the working directory.
|
|
88
|
+
2. Fall back to `git merge-base HEAD origin/<default>` then `git diff <merge-base>...HEAD`.
|
|
89
|
+
3. Neither → refuse per the refusal cases above.
|
|
90
|
+
|
|
91
|
+
Capture: `<owner>/<repo>`, head branch, base branch, PR number, PR URL. This scope persists across every loop — `/bugteam` never re-prompts the user mid-cycle.
|
|
92
|
+
|
|
93
|
+
### Step 2: Create the agent team
|
|
94
|
+
|
|
95
|
+
This session is the **team lead**. Create a team using the agent teams feature. Per the docs: *"After enabling agent teams, tell Claude to create an agent team and describe the task and the team structure you want in natural language. Claude creates the team, spawns teammates, and coordinates work based on your prompt."*
|
|
96
|
+
|
|
97
|
+
Team specification:
|
|
98
|
+
|
|
99
|
+
- **Team name:** `bugteam-pr-<number>-<YYYYMMDDHHMMSS>` (or `bugteam-<sanitized-head-branch>-<YYYYMMDDHHMMSS>` if no PR). The timestamp is captured at team-creation time from the lead session and prevents two concurrent invocations on the same PR from colliding.
|
|
100
|
+
- **Branch-name sanitization (no-PR fallback only):** Before substituting `<head-branch>` into the team_name template, replace every character that is NOT in `[A-Za-z0-9._-]` with `-`. This whitelist covers safe portable filename characters and rejects all OS-reserved or shell-special chars including `/ \ : * ? < > | "` and ASCII control chars (0x00–0x1F). Example: `feat/foo*bar` → `feat-foo-bar`; team_name becomes `bugteam-feat-foo-bar-<YYYYMMDDHHMMSS>`. Apply this sanitization BEFORE the team_name is captured, not after — every downstream use of `team_name` (team creation, scoped temp dir, cleanup) sees the safe form.
|
|
101
|
+
- **Per-team temp directory (resolved once, reused everywhere):** After team_name is captured, resolve a portable absolute path with a Claude-side lookup using Python's `tempfile.gettempdir()`, which honors `TMPDIR`, `TEMP`, and `TMP` in the platform-correct order and falls back to `C:\Users\<user>\AppData\Local\Temp` on Windows or `/tmp` on Unix: `Path(tempfile.gettempdir()) / team_name` (requires `import tempfile`). The `team_name` value already carries the `bugteam-` prefix, so do NOT add it again here. Avoid hand-rolled env var chains. Capture the resolved absolute path as `<team_temp_dir>` and pass that literal path to every shell command that follows. Shell-side parameter expansion (`${TMPDIR:-/tmp}`) is forbidden because cmd.exe and PowerShell do not expand it.
|
|
102
|
+
- **Roles defined up front (spawned per loop, not at team creation):**
|
|
103
|
+
- `bugfind` — uses teammate role `code-quality-agent`, model sonnet
|
|
104
|
+
- `bugfix` — uses teammate role `clean-coder`, model sonnet
|
|
105
|
+
- **Display mode:** inherit user's default (`teammateMode` in `~/.claude.json`); do not override.
|
|
106
|
+
|
|
107
|
+
Reference teammate role definitions by name when spawning. Per the docs: *"When spawning a teammate, you can reference a subagent type from any subagent scope: project, user, plugin, or CLI-defined. This lets you define a role once... and reuse it both as a delegated subagent and as an agent team teammate."*
|
|
108
|
+
|
|
109
|
+
Initialize loop state. The block below mixes lead-internal variables and one shell command (the SHA capture). Read it as instructions, not a literal script:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
loop_count=0
|
|
113
|
+
last_action="fresh"
|
|
114
|
+
last_findings=""
|
|
115
|
+
audit_log=""
|
|
116
|
+
starting_sha="$(git rev-parse HEAD)" # captured once, used in the final report
|
|
117
|
+
team_name="bugteam-pr-<number>-<YYYYMMDDHHMMSS>" # no-PR fallback uses sanitized branch
|
|
118
|
+
team_temp_dir="<resolved-absolute-path>/<team_name>"
|
|
119
|
+
loop_comment_index="" # reset at every AUDIT, see scope note below
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**`loop_comment_index` scope (per-loop, not cross-loop).** This list is reset at the start of every AUDIT action, populated as finding comments are posted during AUDIT, consumed by the matching FIX action when it posts fix replies, and discarded after FIX completes. It does not persist across loops; each loop starts with an empty index and its own fresh set of comment URLs.
|
|
123
|
+
|
|
124
|
+
Each entry: `{loop, finding_id, finding_comment_id, finding_comment_url, used_fallback, fix_status}`. Populated by AUDIT, consumed by FIX.
|
|
125
|
+
|
|
126
|
+
### Step 2.5: PR comment lifecycle (start simple)
|
|
127
|
+
|
|
128
|
+
The team narrates its work to the PR via GitHub comments so a reviewer can scan `/bugteam` activity inline with the code. **Teammates own all PR comment posting** — bugfind posts audit comments, bugfix posts fix replies. The lead never calls `gh pr comment` or `gh api repos/.../comments`. The lead's only PR-write action is the final description rewrite at Step 4.5 (via `pr-description-writer` agent).
|
|
129
|
+
|
|
130
|
+
- **Loop comment** — one top-level PR issue comment per loop. Posted by the bugfind teammate at the start of each loop. Body: short header naming the loop and the action. Example body:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
## /bugteam loop <N>: audit running
|
|
134
|
+
|
|
135
|
+
Clean-room audit on PR diff. Finding comments will appear below
|
|
136
|
+
this line.
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
New loop comment per loop, not one across loops — keeps each loop's section self-contained.
|
|
140
|
+
|
|
141
|
+
- **Finding comments** — inline review comments anchored to file:line in the diff. Posted by the bugfind teammate, one per P0/P1/P2 finding. Body: severity, category, description, and a `From /bugteam audit loop <N>` footer.
|
|
142
|
+
|
|
143
|
+
- **Fix replies** — replies to each finding comment. Posted by the bugfix teammate after the commit lands. Body: `Fixed in <commit_sha>` if addressed, or `Could not address this loop: <one-line reason>` if not.
|
|
144
|
+
|
|
145
|
+
This is the **simplest** comment shape that links findings and fixes inline. Do not add cross-loop threading, comment editing, thread resolution, batched reviews, or comment summarization in this version. Build out from observed behavior later.
|
|
146
|
+
|
|
147
|
+
CLI shapes (teammate runs these):
|
|
148
|
+
|
|
149
|
+
- Loop comment: `gh pr comment <number> -R <owner>/<repo> --body-file <tmp>` → returns the comment URL on stdout. (GitHub API name: issue comment.)
|
|
150
|
+
- Finding comment: `gh api repos/<owner>/<repo>/pulls/<number>/comments -X POST -f body=@<tmp> -f commit_id=<head_sha_at_post_time> -f path=<file> -F line=<line> -f side=RIGHT` → returns JSON; capture `id` and `html_url`. (GitHub API name: pull-request review comment.)
|
|
151
|
+
- Fix reply: `gh api repos/<owner>/<repo>/pulls/<number>/comments/<finding_comment_id>/replies -X POST -f body=@<tmp>` → returns JSON.
|
|
152
|
+
|
|
153
|
+
`<head_sha_at_post_time>` = the SHA at the moment the finding comment is posted (run `git rev-parse HEAD` in the teammate's working dir immediately before the POST). Each loop's audit anchors its finding comments to the head SHA at audit time, which is the SHA before this loop's fix lands.
|
|
154
|
+
|
|
155
|
+
Use `--body-file` everywhere, not `--body` — the existing `gh-body-backtick-guard` hook blocks inline bodies that contain backticks, and bug descriptions almost always contain code excerpts.
|
|
156
|
+
|
|
157
|
+
**Finding-comment failure fallback (teammate handles).** If the finding-comment POST fails (rate limit, line not in the diff, malformed payload, network), the bugfind teammate falls back to a top-level issue comment with the file:line in the body text and prefixes the body with `**Inline failed for <file>:<line>** — finding follows below.` The teammate logs the fallback in its outcome XML so the lead's final report can count fallbacks. Cycle continues; no single-comment failure aborts the loop.
|
|
158
|
+
|
|
159
|
+
### Step 3: The cycle
|
|
160
|
+
|
|
161
|
+
Repeat until an exit condition fires:
|
|
162
|
+
|
|
163
|
+
1. Increment `loop_count`. If `loop_count > 10`, exit reason = `cap reached`.
|
|
164
|
+
2. Decide the next action:
|
|
165
|
+
- `last_action in {"fresh", "fixed"}` → run **AUDIT**
|
|
166
|
+
- `last_action == "audited"` and `last_findings.total > 0` → run **FIX**
|
|
167
|
+
- `last_action == "audited"` and `last_findings.total == 0` → exit reason = `converged`
|
|
168
|
+
- `last_action == "fixed"` and `git rev-parse HEAD` did not change since pre-FIX → exit reason = `stuck` (see FIX action for detection)
|
|
169
|
+
3. Execute the chosen action (see action specs below).
|
|
170
|
+
4. Update `last_action`, `last_findings`, and append to `audit_log`.
|
|
171
|
+
5. Print a one-line progress marker so the user can watch convergence:
|
|
172
|
+
- After audit: `Loop <N> audit: <P0>P0 / <P1>P1 / <P2>P2`
|
|
173
|
+
- After fix: `Loop <N> fix: commit <sha7> (<files_changed> files, +<add>/-<del>)`
|
|
174
|
+
6. Loop.
|
|
175
|
+
|
|
176
|
+
### AUDIT action (clean-room teammate, fresh per loop)
|
|
177
|
+
|
|
178
|
+
Capture a fresh PR diff for this loop into the per-team scoped directory so concurrent `/bugteam` runs do not collide. Use the literal `<team_temp_dir>` resolved once in Step 2 — do NOT rewrite the path with shell expansion:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
mkdir -p "<team_temp_dir>"
|
|
182
|
+
gh pr diff <number> -R <owner>/<repo> > "<team_temp_dir>/loop-<N>.patch"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`<team_temp_dir>` is the absolute path captured in Step 2 (already includes the sanitized team_name and timestamp suffix, and `team_name` itself is already prefixed with `bugteam-`). Claude resolves the portable temp root once via `Path(tempfile.gettempdir()) / team_name` (requires `import tempfile`) and passes the literal absolute path to every shell command. `tempfile.gettempdir()` honors `TMPDIR`, `TEMP`, and `TMP` in the platform-correct order and falls back to `C:\Users\<user>\AppData\Local\Temp` on Windows or `/tmp` on Unix, so this works identically on macOS, Linux, Windows cmd.exe, and PowerShell because the shell never has to interpret `${TMPDIR:-/tmp}` or `%TEMP%`.
|
|
186
|
+
|
|
187
|
+
Spawn a NEW `bugfind` teammate for this loop using the `code-quality-agent` subagent type. The teammate is fresh: no prior loop's findings, no chat history, no inherited audit context. Per the docs: *"The lead's conversation history does not carry over."* — and we further guarantee independence by spawning a new teammate per loop rather than reusing one.
|
|
188
|
+
|
|
189
|
+
The teammate's spawn prompt is the full XML below — copy it verbatim with the placeholders substituted. **Forbid all conversation references** in the spawn prompt. No "as we discussed," "the earlier issue," "fix from the prior loop," "you previously identified." Each loop's audit teammate has no idea other loops happened.
|
|
190
|
+
|
|
191
|
+
```xml
|
|
192
|
+
<context>
|
|
193
|
+
<repo>owner/repo</repo>
|
|
194
|
+
<branch>head ref</branch>
|
|
195
|
+
<base_branch>base ref</base_branch>
|
|
196
|
+
<pr_url>full URL</pr_url>
|
|
197
|
+
<loop>N</loop>
|
|
198
|
+
</context>
|
|
199
|
+
|
|
200
|
+
<scope>
|
|
201
|
+
<diff_path>Absolute path to the loop-N patch file under team_temp_dir from Step 2 (same path as gh pr diff redirect in AUDIT)</diff_path>
|
|
202
|
+
<scope_rule>Audit only lines added or modified in the diff. Pre-existing code on untouched lines is out of scope.</scope_rule>
|
|
203
|
+
</scope>
|
|
204
|
+
|
|
205
|
+
<bug_categories>
|
|
206
|
+
Investigate each category explicitly. For each, return either at least
|
|
207
|
+
one finding OR a verified-clean entry with the evidence used to clear it:
|
|
208
|
+
A. API contract verification (signatures, return types, async/await correctness)
|
|
209
|
+
B. Selector / query / engine compatibility
|
|
210
|
+
C. Resource cleanup and lifecycle (file handles, connections, processes, locks)
|
|
211
|
+
D. Variable scoping, ordering, and unbound references
|
|
212
|
+
E. Dead code and unused imports
|
|
213
|
+
F. Silent failures (catch-all excepts, unconditional success returns, missing error propagation)
|
|
214
|
+
G. Off-by-one, bounds, and integer overflow
|
|
215
|
+
H. Security boundaries (injection, path traversal, auth bypass, secret leakage)
|
|
216
|
+
I. Concurrency hazards (race conditions, missing awaits, shared mutable state)
|
|
217
|
+
J. Magic values and configuration drift
|
|
218
|
+
</bug_categories>
|
|
219
|
+
|
|
220
|
+
<constraints>
|
|
221
|
+
- Read-only on source code: the audit does not modify any source file.
|
|
222
|
+
- Cite file:line for every finding.
|
|
223
|
+
- When the diff alone does not provide enough context to confirm a bug,
|
|
224
|
+
list it under "Open questions" rather than assert it.
|
|
225
|
+
</constraints>
|
|
226
|
+
|
|
227
|
+
<comment_posting>
|
|
228
|
+
1. Post the loop comment for this loop FIRST, before auditing. Use
|
|
229
|
+
the Step 2.5 loop-comment CLI shape with this body:
|
|
230
|
+
|
|
231
|
+
## /bugteam loop N: audit running
|
|
232
|
+
|
|
233
|
+
Clean-room audit on PR diff. Finding comments will appear below
|
|
234
|
+
this line.
|
|
235
|
+
|
|
236
|
+
2. Audit the diff against the 10 categories above.
|
|
237
|
+
3. For each finding, post a finding comment via the Step 2.5
|
|
238
|
+
finding-comment CLI shape. Body:
|
|
239
|
+
|
|
240
|
+
**[severity] one-line title**
|
|
241
|
+
Category: <letter> (<category name>)
|
|
242
|
+
<2-3 sentence description with concrete trace>
|
|
243
|
+
|
|
244
|
+
_From /bugteam audit loop N._
|
|
245
|
+
|
|
246
|
+
On POST failure (rate limit, line not in diff, malformed payload,
|
|
247
|
+
network), fall back to a top-level issue comment per Step 2.5.
|
|
248
|
+
4. Assign each finding a stable finding_id of exactly the form
|
|
249
|
+
`loopN-K` where K is 1-based within this loop.
|
|
250
|
+
5. Use --body-file (never --body) to avoid the gh-body-backtick-guard hook.
|
|
251
|
+
</comment_posting>
|
|
252
|
+
|
|
253
|
+
<output_format>
|
|
254
|
+
Write the outcome XML below to .bugteam-loop-N.outcomes.xml in the
|
|
255
|
+
working directory. Return only that path on stdout. The schema:
|
|
256
|
+
</output_format>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Outcome XML schema (bugfind writes this):
|
|
260
|
+
|
|
261
|
+
```xml
|
|
262
|
+
<bugteam_audit loop="<N>" loop_comment_url="<url>">
|
|
263
|
+
<finding
|
|
264
|
+
finding_id="loop<N>-<index>"
|
|
265
|
+
severity="P0|P1|P2"
|
|
266
|
+
category="<letter>"
|
|
267
|
+
file="<path>"
|
|
268
|
+
line="<int>"
|
|
269
|
+
finding_comment_id="<gh comment id, or empty if fallback>"
|
|
270
|
+
finding_comment_url="<url, inline OR fallback issue comment URL>"
|
|
271
|
+
used_fallback="true|false"
|
|
272
|
+
>
|
|
273
|
+
<title>one-line title</title>
|
|
274
|
+
<description>2-3 sentence description with concrete trace</description>
|
|
275
|
+
</finding>
|
|
276
|
+
<verified_clean>
|
|
277
|
+
<category letter="<letter>" name="<name>" evidence="brief evidence + cleared conclusion"/>
|
|
278
|
+
</verified_clean>
|
|
279
|
+
</bugteam_audit>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
After the teammate writes the XML and returns, the lead reads `.bugteam-loop-<N>.outcomes.xml`, parses it, and populates `loop_comment_index` from `<finding>` elements. Then **shut down the bugfind teammate**: `Ask the bugfind teammate to shut down`. Per the docs: *"The lead sends a shutdown request. The teammate can approve, exiting gracefully, or reject with an explanation."* If the teammate rejects shutdown, force-shut by failing the team and starting Step 5 cleanup with exit reason = `error: bugfind teammate refused shutdown`.
|
|
283
|
+
|
|
284
|
+
`last_action = "audited"`. `last_findings = parsed`. Append `(loop=N, action="audit", counts={P0,P1,P2}, sha=current_HEAD, loop_comment_url=<url>, finding_count=<n>, fallback_count=<n>)` to `audit_log`.
|
|
285
|
+
|
|
286
|
+
### FIX action (fresh teammate, only sees latest audit)
|
|
287
|
+
|
|
288
|
+
Spawn a NEW `bugfix` teammate for this loop using the `clean-coder` teammate role, model sonnet. The teammate sees ONLY the most recent audit's findings — no prior-loop findings, no prior-loop fix history, no chat history.
|
|
289
|
+
|
|
290
|
+
The teammate receives the **finding comment URL and id for each finding** (from `loop_comment_index`) and **owns the reply posting**. After committing fixes, the teammate posts one reply per finding: `Fixed in <commit_sha>` for addressed findings, `Could not address this loop: <one-line reason>` for skipped or failed findings. Same one-identity model as bugfind: teammate posts, lead does not.
|
|
291
|
+
|
|
292
|
+
After all replies are posted, the teammate writes its own outcome XML (see schema below), returns, and the lead **shuts down the bugfix teammate** the same way as the bugfind shutdown.
|
|
293
|
+
|
|
294
|
+
Prompt skeleton:
|
|
295
|
+
|
|
296
|
+
```xml
|
|
297
|
+
<context>
|
|
298
|
+
<repo>owner/repo</repo>
|
|
299
|
+
<branch>head</branch>
|
|
300
|
+
<base_branch>base</base_branch>
|
|
301
|
+
<pr_url>url</pr_url>
|
|
302
|
+
<loop>N</loop>
|
|
303
|
+
</context>
|
|
304
|
+
|
|
305
|
+
<bugs_to_fix>
|
|
306
|
+
[for each P0/P1/P2 finding from last_findings:]
|
|
307
|
+
<bug
|
|
308
|
+
finding_id="loop<N>-<index>"
|
|
309
|
+
severity="P0|P1|P2"
|
|
310
|
+
file="<path>"
|
|
311
|
+
line="<int>"
|
|
312
|
+
category="<letter>"
|
|
313
|
+
finding_comment_id="<id>"
|
|
314
|
+
finding_comment_url="<url>"
|
|
315
|
+
>
|
|
316
|
+
<description>...</description>
|
|
317
|
+
</bug>
|
|
318
|
+
</bugs_to_fix>
|
|
319
|
+
|
|
320
|
+
<execution>
|
|
321
|
+
1. Read each referenced file before editing.
|
|
322
|
+
2. Apply each fix you can address.
|
|
323
|
+
3. Run `python -m py_compile` (or language-equivalent) on every modified file.
|
|
324
|
+
4. git add by explicit path, then git commit with a message summarizing the bugs fixed.
|
|
325
|
+
- If the commit fails because a git hook (pre-commit, commit-msg, etc.) blocked it,
|
|
326
|
+
capture the hook's stderr, write status=hook_blocked for every finding in this loop
|
|
327
|
+
(the commit was atomic; if it failed, no finding was applied), populate hook_output
|
|
328
|
+
on each outcome, and return WITHOUT retrying. The lead will treat this loop as no-progress.
|
|
329
|
+
5. git push (NEVER --force, NEVER --force-with-lease).
|
|
330
|
+
6. For each bug, post a fix reply to its finding_comment_id via the
|
|
331
|
+
Step 2.5 reply CLI shape:
|
|
332
|
+
- "Fixed in <commit_sha>" if the bug was addressed by your commit
|
|
333
|
+
- "Could not address this loop: <one-line reason>" if you skipped or failed it
|
|
334
|
+
- "Hook blocked the fix commit: <one-line summary>" if the commit was hook-blocked
|
|
335
|
+
Use --body-file (the existing gh-body-backtick-guard hook blocks --body).
|
|
336
|
+
7. Write `.bugteam-loop-<N>.outcomes.xml` (schema below) and return its path.
|
|
337
|
+
</execution>
|
|
338
|
+
|
|
339
|
+
<outcome_xml_schema>
|
|
340
|
+
<bugteam_fix loop="<N>" commit_sha="<sha or empty if no commit>">
|
|
341
|
+
<outcome
|
|
342
|
+
finding_id="loop<N>-<index>"
|
|
343
|
+
status="fixed|could_not_address|hook_blocked"
|
|
344
|
+
commit_sha="<sha if fixed, empty otherwise>"
|
|
345
|
+
reply_comment_id="<id of the reply posted>"
|
|
346
|
+
reply_comment_url="<url of the reply posted>"
|
|
347
|
+
>
|
|
348
|
+
<reason>only present when status=could_not_address; one-line reason text</reason>
|
|
349
|
+
<hook_output>only present when status=hook_blocked; verbatim stderr from the blocked hook</hook_output>
|
|
350
|
+
</outcome>
|
|
351
|
+
</bugteam_fix>
|
|
352
|
+
</outcome_xml_schema>
|
|
353
|
+
|
|
354
|
+
<constraints>
|
|
355
|
+
- Modify only files referenced in bugs_to_fix.
|
|
356
|
+
- One commit on the existing branch, then push.
|
|
357
|
+
- Do NOT rebase, amend, --force, --force-with-lease, or change the PR base.
|
|
358
|
+
- Do NOT skip git hooks.
|
|
359
|
+
- git add by explicit path; never `git add .` or `git add -A`.
|
|
360
|
+
- Preserve existing comments on lines you do not modify.
|
|
361
|
+
- Type hints on every signature you touch.
|
|
362
|
+
</constraints>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Verify the fix actually committed and pushed:
|
|
366
|
+
|
|
367
|
+
- `git rev-parse HEAD` after fix should differ from before
|
|
368
|
+
- The new HEAD should be present on `origin/<branch>` (`git fetch origin <branch> && git rev-parse origin/<branch>` matches HEAD)
|
|
369
|
+
|
|
370
|
+
If `git rev-parse HEAD` did not change, exit reason = `stuck — bugfix teammate could not address findings`. The fix teammate ran but produced no commit; further loops will not converge.
|
|
371
|
+
|
|
372
|
+
`last_action = "fixed"`. Append `(loop=N, action="fix", commit_sha=new_HEAD, files_changed, lines_added, lines_removed)` to `audit_log`.
|
|
373
|
+
|
|
374
|
+
### Step 4: Tear down the team and clean working tree
|
|
375
|
+
|
|
376
|
+
When the cycle exits (any reason):
|
|
377
|
+
|
|
378
|
+
1. **Clean up the team as the lead.** Per the docs: *"When you're done, ask the lead to clean up: 'Clean up the team'. This removes the shared team resources. When the lead runs cleanup, it checks for active teammates and fails if any are still running, so shut them down first."* The lead is THIS session — call cleanup directly. If any teammate is still alive (e.g., from an aborted shutdown), shut it down first.
|
|
379
|
+
2. Delete the per-team scoped temp directory using Python: `shutil.rmtree(team_temp_dir, ignore_errors=True)` (requires `import shutil`). This works on every platform without OS-detection branching. Pass the literal absolute path Claude resolved at Step 2 — do NOT defer to the shell, and never use shell `${TMPDIR:-/tmp}` or `%TEMP%` expansion at this step either.
|
|
380
|
+
|
|
381
|
+
### Step 4.5: Finalize the PR description (mandatory)
|
|
382
|
+
|
|
383
|
+
After teardown and before permission revoke, the lead rewrites the PR body to reflect the PR's **final cumulative state** — the change the PR delivers, not the bugteam process. This is the **only** PR-write the lead performs (audit and fix comments belong to the teammates).
|
|
384
|
+
|
|
385
|
+
The lead delegates the body authoring to the `pr-description-writer` agent so the global mandatory-pr-description-writer hook accepts the subsequent `gh pr edit`. The lead does NOT compose the body inline.
|
|
386
|
+
|
|
387
|
+
`pr-description-writer` is provided by the global git-workflow rule in `claude-code-config` (as the `pr-description-writer` agent type). If that agent is not available in the current environment, fall back to spawning a `general-purpose` agent with the same brief — the global hook treats agent-authored bodies the same regardless of the specific agent type. If neither agent is available, log a warning in the final report and skip Step 4.5; the original PR body remains.
|
|
388
|
+
|
|
389
|
+
Steps:
|
|
390
|
+
|
|
391
|
+
1. Capture the cumulative diff: `gh pr diff <number> -R <owner>/<repo> > .bugteam-final.diff`.
|
|
392
|
+
2. Capture the original body: `gh pr view <number> -R <owner>/<repo> --json body --jq .body > .bugteam-original-body.md`.
|
|
393
|
+
3. Invoke the `pr-description-writer` agent (or `general-purpose` fallback) with this brief:
|
|
394
|
+
- **Inputs:** the diff path, the original body path, the head branch name, the base branch name.
|
|
395
|
+
- **Constraint:** describe what the PR delivers based on the cumulative diff. Do NOT mention `/bugteam`, audit loops, fix commits, finding counts, or any process metadata. Those belong in the finding comments, not the description. The description is for the merge audience.
|
|
396
|
+
- **Preservation rule:** if the original body contains sections that look manually curated (linked issues, screenshots, a populated test plan, "Risk Assessment" sections), preserve those verbatim and only rewrite the prose narrative around them.
|
|
397
|
+
- **Output:** the new body markdown.
|
|
398
|
+
4. Write the agent's returned body to `.bugteam-final-body.md`.
|
|
399
|
+
5. Apply: `gh pr edit <number> -R <owner>/<repo> --body-file .bugteam-final-body.md`.
|
|
400
|
+
6. Delete `.bugteam-final.diff`, `.bugteam-original-body.md`, and `.bugteam-final-body.md`.
|
|
401
|
+
|
|
402
|
+
If Step 4.5 fails for any reason (agent error, hook block, network), surface the failure in the final report and continue to Step 5. The original PR body remains; the rest of the cycle's work (commits, comments, replies) is unaffected.
|
|
403
|
+
|
|
404
|
+
### Step 5: Revoke project permissions (mandatory, runs always)
|
|
405
|
+
|
|
406
|
+
After team cleanup completes — including on error, cap-reached, or stuck exits — run:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
python "${CLAUDE_SKILL_DIR}/revoke_project_claude_permissions.py"
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
This removes the allow rules and additionalDirectories entry that Step 0 added. Revoke is non-negotiable: leaving the grant in place means future sessions inherit elevated permissions on this project's `.claude/**` tree without the user opting in. Run this even if Step 4 cleanup partially failed; surface the cleanup error separately in the final report.
|
|
413
|
+
|
|
414
|
+
### Step 6: Print the final report
|
|
415
|
+
|
|
416
|
+
```
|
|
417
|
+
/bugteam exit: <converged | cap reached | stuck | error>
|
|
418
|
+
Loops: <loop_count>
|
|
419
|
+
Starting commit: <starting_sha7>
|
|
420
|
+
Final commit: <current_HEAD_sha7>
|
|
421
|
+
Net change: <total_files> files, +<total_add>/-<total_del>
|
|
422
|
+
|
|
423
|
+
Loop log:
|
|
424
|
+
1 audit: 3P0 2P1 0P2
|
|
425
|
+
1 fix: commit a1b2c3d (4 files, +12/-3)
|
|
426
|
+
2 audit: 1P0 0P1 0P2
|
|
427
|
+
2 fix: commit e4f5g6h (1 file, +2/-1)
|
|
428
|
+
3 audit: 0P0 0P1 0P2 → converged
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
If exit = `cap reached`, name the remaining bug count and recommend `/findbugs` for human triage. If exit = `stuck`, name which findings the fix agent could not resolve. If exit = `error`, surface the error and the loop number.
|
|
432
|
+
|
|
433
|
+
## Constraints
|
|
434
|
+
|
|
435
|
+
- **Agent teams required, not parallel subagents.** The skill MUST use Claude Code's agent teams feature (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`). Spawning `code-quality-agent` and `clean-coder` as parallel subagents from the lead's context = fail; the clean-room property requires independent teammate sessions.
|
|
436
|
+
- **Grant before any spawn, revoke before any return.** Step 0 grants project `.claude/**` permissions; Step 5 revokes. Both are mandatory. Revoke runs on every exit path including error, cap-reached, and stuck.
|
|
437
|
+
- **Fresh teammate per loop.** Both bugfind and bugfix are spawned new each loop and shut down after their action. Reusing a teammate across loops accumulates context inside that teammate's window — defeats clean-room.
|
|
438
|
+
- **One up-front confirmation = whole cycle.** No mid-loop AskUserQuestion. The `/bugteam` invocation IS the authorization.
|
|
439
|
+
- **10-loop hard cap.** Counted as audits performed. Worst case = 10 audits + 10 fixes = 20 teammate spawns + 20 shutdowns.
|
|
440
|
+
- **Clean-room audits, every loop.** Never pass conversation context, prior findings, prior commits, or prior loop history into a bugfind teammate's spawn prompt.
|
|
441
|
+
- **Targeted fixes.** Each fix teammate sees ONLY the most recent audit's findings. Prior loops are invisible to the fix teammate.
|
|
442
|
+
- **Sonnet for both teammates.** Predictable cost, fits-purpose for code work.
|
|
443
|
+
- **No clean-room exception for fix.** The fix teammate legitimately needs the findings; that is not anchoring bias, that is the input contract.
|
|
444
|
+
- **One commit per fix action.** Loops produce one commit per loop, not one per bug.
|
|
445
|
+
- **No `--force`, no `--amend`, no rebase, no base change** at any point.
|
|
446
|
+
- **Lead-only cleanup.** Per the docs: *"Always use the lead to clean up. Teammates should not run cleanup because their team context may not resolve correctly, potentially leaving resources in an inconsistent state."* This session is the lead; teammates never call cleanup.
|
|
447
|
+
- **Cleanup the per-team scoped temp directory on exit.** The resolved `<team_temp_dir>` (absolute literal captured in Step 2) is deleted entirely so no loop patches leak between runs.
|
|
448
|
+
- **Cleanup all `.bugteam-*` files on exit.** `.bugteam-loop-*.patch`, `.bugteam-loop-*.outcomes.xml`, `.bugteam-final.diff`, `.bugteam-original-body.md`, `.bugteam-final-body.md`. Working directory ends clean.
|
|
449
|
+
- **Teammates own audit/fix comment posting.** Bugfind posts the loop comment and finding comments (with issue-comment fallback). Bugfix posts the fix replies after committing. The lead never calls `gh pr comment` or `gh api repos/.../comments` for these.
|
|
450
|
+
- **Lead owns the final PR description rewrite only** (Step 4.5), and only via the `pr-description-writer` agent. The lead does not compose the description inline.
|
|
451
|
+
- **Loop comment per loop, fresh finding comments per loop.** No cross-loop comment threading, no comment editing in place, no thread resolution in this version. Each loop's section on the PR is self-contained.
|
|
452
|
+
- **PR description rewrite on every exit.** Step 4.5 runs on `converged`, `cap reached`, and `stuck`. On `error`, the rewrite is best-effort; if it fails, surface the error in the final report and continue to revoke.
|
|
453
|
+
- **Outcome XML, not JSON.** Both teammates write structured outcome data (findings or fix outcomes) to `.bugteam-loop-<N>.outcomes.xml`. The lead reads these files between actions. XML chosen for parser robustness against multi-line, special-character, and quoted reason fields.
|
|
454
|
+
|
|
455
|
+
## Examples
|
|
456
|
+
|
|
457
|
+
<example>
|
|
458
|
+
User: `/bugteam`
|
|
459
|
+
Claude: [resolves PR #42, runs loop]
|
|
460
|
+
|
|
461
|
+
`Loop 1 audit: 1P0 / 2P1 / 0P2`
|
|
462
|
+
`Loop 1 fix: commit a1b2c3d (3 files, +18/-7)`
|
|
463
|
+
`Loop 2 audit: 0P0 / 1P1 / 0P2`
|
|
464
|
+
`Loop 2 fix: commit e4f5g6h (1 file, +5/-2)`
|
|
465
|
+
`Loop 3 audit: 0P0 / 0P1 / 0P2 → converged`
|
|
466
|
+
|
|
467
|
+
`/bugteam exit: converged`
|
|
468
|
+
`Loops: 3`
|
|
469
|
+
`Starting commit: 9d8c7b6`
|
|
470
|
+
`Final commit: e4f5g6h`
|
|
471
|
+
`Net change: 4 files, +23/-9`
|
|
472
|
+
</example>
|
|
473
|
+
|
|
474
|
+
<example>
|
|
475
|
+
User: `/bugteam`
|
|
476
|
+
Claude: [runs 10 loops without convergence]
|
|
477
|
+
|
|
478
|
+
`Loop 10 audit: 0P0 / 1P1 / 2P2`
|
|
479
|
+
|
|
480
|
+
`/bugteam exit: cap reached`
|
|
481
|
+
`Loops: 10`
|
|
482
|
+
`Remaining: 0P0 / 1P1 / 2P2 — run /findbugs for human triage`
|
|
483
|
+
</example>
|
|
484
|
+
|
|
485
|
+
<example>
|
|
486
|
+
User: `/bugteam`
|
|
487
|
+
Claude: [loop 4 fix produces no commit]
|
|
488
|
+
|
|
489
|
+
`Loop 4 fix: clean-coder reported no changes (could not address remaining bugs)`
|
|
490
|
+
`/bugteam exit: stuck`
|
|
491
|
+
`Unresolved findings (3): src/cache.py:88 (P0 race condition); ...`
|
|
492
|
+
</example>
|
|
493
|
+
|
|
494
|
+
<example>
|
|
495
|
+
User: `/bugteam` (mixed-outcome path: some findings fixed, others skipped)
|
|
496
|
+
Claude: [resolves PR #99, runs loop with partial-fix outcomes]
|
|
497
|
+
|
|
498
|
+
`Loop 1 audit: 1P0 / 3P1 / 0P2`
|
|
499
|
+
`Loop 1 fix: commit a1b2c3d (2 files, +8/-3) — 2 fixed, 2 could_not_address`
|
|
500
|
+
`Loop 2 audit: 0P0 / 2P1 / 0P2`
|
|
501
|
+
`Loop 2 fix: 0 fixed, 2 could_not_address (no commit)`
|
|
502
|
+
|
|
503
|
+
`/bugteam exit: stuck`
|
|
504
|
+
`Loops: 2`
|
|
505
|
+
`Unresolved findings (2): src/auth.py:45 (P1: file is generated, cannot edit); src/legacy.py:200 (P1: rewrite scope exceeds the bug)`
|
|
506
|
+
|
|
507
|
+
The bugfix teammate writes one outcome per finding to `.bugteam-loop-2.outcomes.xml`. Findings with `status=could_not_address` carry their `<reason>` text, and the teammate posts a matching reply to each finding comment so the reviewer sees why each bug stayed open.
|
|
508
|
+
</example>
|
|
509
|
+
|
|
510
|
+
<example>
|
|
511
|
+
User: `/bugteam` (no PR or upstream diff)
|
|
512
|
+
Claude: `No PR or upstream diff. /bugteam needs a target.`
|
|
513
|
+
</example>
|
|
514
|
+
|
|
515
|
+
<example>
|
|
516
|
+
User: `/bugteam` (uncommitted changes in working tree)
|
|
517
|
+
Claude: `Uncommitted changes detected. Stash, commit, or revert before /bugteam.`
|
|
518
|
+
</example>
|
|
519
|
+
|
|
520
|
+
## Why this design
|
|
521
|
+
|
|
522
|
+
The three sibling skills compose, but `/bugteam` solves a problem they cannot solve in sequence:
|
|
523
|
+
|
|
524
|
+
- `/findbugs` audits once and stops.
|
|
525
|
+
- `/fixbugs` fixes the findings of one audit and stops.
|
|
526
|
+
- A human-driven `/findbugs` → `/fixbugs` → `/findbugs` → `/fixbugs` cycle works but requires the user to drive it.
|
|
527
|
+
|
|
528
|
+
`/bugteam` automates that cycle. The clean-room property is preserved by spawning a fresh audit agent each loop with no inherited context — every audit is independent of the prior loop's verdict. The 10-loop cap is the safety: pathological cases (audit agent oscillating, fix agent regressing) cannot run away.
|
|
529
|
+
|
|
530
|
+
The single up-front confirmation is the explicit trade — `/bugteam` is more autonomous than `/findbugs`+`/fixbugs` chained manually. The user accepts that autonomy by typing the command. Stop conditions and the loop log give the user full visibility on exit.
|