claude-dev-env 1.32.0 → 1.34.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.
@@ -5,17 +5,29 @@ import subprocess
5
5
  import sys
6
6
  from pathlib import Path
7
7
 
8
+ sys.modules.pop("config", None)
9
+ if str(Path(__file__).resolve().parent) not in sys.path:
10
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
11
+
12
+ from config.bugteam_fix_hookspath_constants import (
13
+ ALL_CANONICAL_HOOKS_DIRECTORY_COMPONENTS,
14
+ ALL_HOME_ENV_VAR_NAMES,
15
+ HOOKS_PATH_SUFFIX,
16
+ PREFLIGHT_NO_PYTEST_FLAG,
17
+ PREFLIGHT_REPO_ROOT_FLAG,
18
+ )
19
+
8
20
 
9
21
  def _expected_hooks_path_suffix() -> str:
10
- return "hooks/git-hooks"
22
+ return HOOKS_PATH_SUFFIX
11
23
 
12
24
 
13
- def _canonical_hooks_directory_components() -> tuple[str, ...]:
14
- return (".claude", "hooks", "git-hooks")
25
+ def _canonical_hooks_directory_components() -> tuple[str, str, str]:
26
+ return ALL_CANONICAL_HOOKS_DIRECTORY_COMPONENTS
15
27
 
16
28
 
17
- def _home_env_var_names() -> tuple[str, ...]:
18
- return ("HOME", "USERPROFILE")
29
+ def _home_env_var_names() -> tuple[str, str]:
30
+ return ALL_HOME_ENV_VAR_NAMES
19
31
 
20
32
 
21
33
  def resolve_canonical_hooks_directory(
@@ -82,7 +94,7 @@ def read_global_core_hooks_path(
82
94
  def unset_local_core_hooks_path(
83
95
  repository_root: Path,
84
96
  environment_overrides: dict[str, str] | None,
85
- ) -> None:
97
+ ) -> int:
86
98
  git_command = [
87
99
  "git",
88
100
  "-C",
@@ -92,13 +104,14 @@ def unset_local_core_hooks_path(
92
104
  "--unset-all",
93
105
  "core.hooksPath",
94
106
  ]
95
- subprocess.run(
107
+ completed_process = subprocess.run(
96
108
  git_command,
97
109
  capture_output=True,
98
110
  text=True,
99
111
  check=False,
100
112
  env=environment_overrides,
101
113
  )
114
+ return completed_process.returncode
102
115
 
103
116
 
104
117
  def set_global_core_hooks_path(
@@ -144,8 +157,8 @@ def rerun_preflight(
144
157
  rerun_command = [
145
158
  sys.executable,
146
159
  str(preflight_script_path),
147
- "--no-pytest",
148
- "--repo-root",
160
+ PREFLIGHT_NO_PYTEST_FLAG,
161
+ PREFLIGHT_REPO_ROOT_FLAG,
149
162
  str(repository_root),
150
163
  ]
151
164
  completed_process = subprocess.run(
@@ -156,7 +169,7 @@ def rerun_preflight(
156
169
  return completed_process.returncode
157
170
 
158
171
 
159
- def parse_arguments(argv: list[str]) -> argparse.Namespace:
172
+ def parse_arguments(argv: list[str] | None) -> argparse.Namespace:
160
173
  parser = argparse.ArgumentParser(
161
174
  description=(
162
175
  "Auto-fix core.hooksPath when bugteam preflight detects a stale override. "
@@ -178,7 +191,7 @@ def main(
178
191
  *,
179
192
  environment_overrides: dict[str, str] | None = None,
180
193
  ) -> int:
181
- arguments = parse_arguments(sys.argv[1:] if argv is None else argv)
194
+ arguments = parse_arguments(argv)
182
195
  start_directory = Path.cwd()
183
196
  repository_root = (
184
197
  arguments.repo_root.resolve()
@@ -206,7 +219,16 @@ def main(
206
219
  for each_value in local_hooks_path_values
207
220
  )
208
221
  if has_non_canonical_local_override:
209
- unset_local_core_hooks_path(repository_root, environment_overrides)
222
+ unset_local_returncode = unset_local_core_hooks_path(
223
+ repository_root, environment_overrides
224
+ )
225
+ if unset_local_returncode != 0:
226
+ print(
227
+ "bugteam_fix_hookspath: failed to unset local core.hooksPath on "
228
+ f"{repository_root} (git exit {unset_local_returncode}).",
229
+ file=sys.stderr,
230
+ )
231
+ return 1
210
232
  print(
211
233
  "bugteam_fix_hookspath: removed stale local core.hooksPath override on "
212
234
  f"{repository_root}",
File without changes
@@ -0,0 +1,17 @@
1
+ """Configuration constants for bugteam_fix_hookspath auto-remediation script."""
2
+
3
+ from __future__ import annotations
4
+
5
+ HOOKS_PATH_SUFFIX: str = "hooks/git-hooks"
6
+
7
+ ALL_CANONICAL_HOOKS_DIRECTORY_COMPONENTS: tuple[str, str, str] = (
8
+ ".claude",
9
+ "hooks",
10
+ "git-hooks",
11
+ )
12
+
13
+ ALL_HOME_ENV_VAR_NAMES: tuple[str, str] = ("HOME", "USERPROFILE")
14
+
15
+ PREFLIGHT_NO_PYTEST_FLAG: str = "--no-pytest"
16
+
17
+ PREFLIGHT_REPO_ROOT_FLAG: str = "--repo-root"
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: caveman
3
+ description: "Inline counterpart to the caveman agent — trim noise from `$ARGUMENTS` (or the previous assistant message) and reply with the trimmed text only, no report, no Agent spawn. Triggers: /caveman, caveman this, trim this, make it terse, caveman voice."
4
+ argument-hint: "[text to trim, or omit and the model uses the previous assistant message]"
5
+ ---
6
+
7
+ # Caveman
8
+
9
+ Inline counterpart to the `caveman` agent at `packages/claude-dev-env/agents/caveman.md`. Use this skill when the trim target is conversational text in the current turn rather than a separate file or multi-section artifact. The model performs the trim itself in the next reply — no `Agent` invocation, no structured report, just the trimmed text.
10
+
11
+ ## Instructions
12
+
13
+ 1. **Resolve the source.** If `$ARGUMENTS` is non-empty, that text is the trim source. Otherwise the source is the previous assistant message in the current conversation.
14
+
15
+ 2. **Trim the same noise categories the agent trims:**
16
+
17
+ | Noise type | Example |
18
+ |---|---|
19
+ | Preamble / recap | "As discussed above, this skill will..." |
20
+ | Hedging | "This might, in some cases, potentially..." |
21
+ | Filler transitions | "Now, moving on to..." / "It's worth noting that..." |
22
+ | Restatement | the same point made twice in different words |
23
+ | Empty future-proofing | parameters, sections, or fields with no current consumer |
24
+ | Dead examples | examples that duplicate another example without adding coverage |
25
+ | Pleasantries | "Hope this helps." / "Feel free to..." |
26
+ | Vague qualifiers | "various", "several", "a number of" — replace with the actual count or cut |
27
+
28
+ 3. **Preserve verbatim:** code, commands, paths, URLs, errors, JSON, schemas, frontmatter, counts, version strings, identifiers, safety/destructive-op language, and anything the user explicitly flagged as keep-as-is.
29
+
30
+ 4. **Output only the trimmed text.** No `trimmed / removed / preserved-verbatim / flagged` report. No commentary about what was cut. No preamble like "Here is the trimmed version:". The trimmed text is the entire reply.
31
+
32
+ ## Escape hatch
33
+
34
+ If trimming would drop a safety warning, collapse a deliberate distinction, or you are unsure whether a span is load-bearing, leave that span in place verbatim. Do not flag, narrate, or ask about the preserved span — instruction 4 still applies, so the trimmed text (with the preserved span included) remains the entire reply. Terse is for noise, not for substance.
35
+
36
+ ## When NOT to use
37
+
38
+ Use the `caveman` agent (via `Task` / `Agent` tool with `subagent_type: caveman`) when the input is a file path, a multi-section artifact requiring the structured `trimmed / removed / preserved-verbatim / flagged` report, or any delegated workflow. This skill is for inline conversational text only.
@@ -0,0 +1,218 @@
1
+ ---
2
+ name: pr-converge
3
+ description: >-
4
+ Spawns a background subagent that drives the current PR to convergence by
5
+ alternating Cursor Bugbot and the in-house bugteam audit. The subagent
6
+ fetches each reviewer's findings, applies TDD fixes, pushes one commit per
7
+ tick, replies inline, and re-triggers the reviewer. Termination requires a
8
+ back-to-back clean cycle — bugbot CLEAN immediately followed by bugteam
9
+ CLEAN with no intervening fixes — at which point the subagent marks the PR
10
+ ready for review and terminates. Triggers: '/pr-converge', 'drive PR to
11
+ convergence', 'loop bugbot and bugteam', 'babysit bugbot and bugteam',
12
+ 'until both are clean', 'converge this PR'.
13
+ ---
14
+
15
+ # PR Converge
16
+
17
+ Delegates the bugbot ↔ bugteam convergence loop to a background subagent so the main session stays free. The subagent owns its own cadence and self-terminates on convergence or a hard blocker.
18
+
19
+ ## When this skill applies
20
+
21
+ The user is on a PR branch and wants both reviewers — Cursor's Bugbot AND the in-house `/bugteam` audit — to keep re-reviewing after each push, with findings auto-addressed between ticks. The PR stays in draft until convergence; on convergence the subagent flips it to ready for review.
22
+
23
+ ## The Process
24
+
25
+ ### Step 1: Gather PR context
26
+
27
+ From the current repo:
28
+
29
+ ```bash
30
+ gh pr view --json number,url,headRefOid,baseRefName,headRefName,isDraft
31
+ ```
32
+
33
+ Capture `number`, `headRefOid`, owner/repo (from `url`), branch name, and current draft state. Pass these to the subagent so it can use them directly.
34
+
35
+ ### Step 2: Spawn the background subagent
36
+
37
+ Invoke the `Agent` tool with:
38
+
39
+ - `subagent_type: "general-purpose"`
40
+ - `run_in_background: true`
41
+ - `description: "PR convergence loop for #<N>"`
42
+ - `prompt`: the full instructions in **Step 3 (Subagent prompt template)**, with placeholders filled in from Step 1.
43
+
44
+ Record the returned agent ID. Report to the user in one or two lines:
45
+
46
+ - The subagent is running in the background and will alternate bugbot ↔ bugteam.
47
+ - It self-terminates on back-to-back clean and flips the PR ready for review.
48
+ - To stop it early, the user says "stop the converge loop" and you call `TaskStop <agent_id>`.
49
+
50
+ The skill's job in the main session ends once the subagent is spawned and reported.
51
+
52
+ ### Step 3: Subagent prompt template
53
+
54
+ Pass this verbatim to the subagent (substituting the bracketed values):
55
+
56
+ > You are driving PR **#[NUMBER]** at **[OWNER]/[REPO]** (branch `[BRANCH]`, current HEAD `[HEAD_SHA]`) to convergence by alternating Cursor Bugbot and the in-house bugteam audit. Your job: keep the loop running until BOTH reviewers return CLEAN against the same HEAD with no intervening fixes, then mark the PR ready for review and stop.
57
+ >
58
+ > **State you maintain across ticks** (keep it in your own working memory; you re-enter these instructions verbatim each wakeup):
59
+ >
60
+ > - `phase`: `BUGBOT` or `BUGTEAM`. Start in `BUGBOT`.
61
+ > - `bugbot_clean_at`: the HEAD SHA at which bugbot last reported clean, or `null`. Reset to `null` whenever you push a new commit.
62
+ > - `inline_lag_streak`: integer counter, initialized to `0`. Tracks consecutive ticks where bugbot's review body indicates findings against `current_head` but the inline-comments API returns zero matching comments. Reset to `0` on any other branch outcome.
63
+ >
64
+ > **Per-tick work** (do this now, then on each wakeup):
65
+ >
66
+ > 1. Resolve current HEAD: `gh api repos/[OWNER]/[REPO]/pulls/[NUMBER] --jq '.head.sha'`. Capture it as `current_head`.
67
+ >
68
+ > 2. Branch on `phase`:
69
+ >
70
+ > **If `phase == BUGBOT`:**
71
+ >
72
+ > a. Fetch the latest Cursor Bugbot review:
73
+ > ```bash
74
+ > gh api repos/[OWNER]/[REPO]/pulls/[NUMBER]/reviews \
75
+ > --jq '[.[] | select(.user.login=="cursor[bot]")] | sort_by(.submitted_at) | last'
76
+ > ```
77
+ > Capture `commit_id`, `state`, `submitted_at`, and the body. Bugbot's body contains either `Cursor Bugbot has reviewed your changes and found <N> potential issue` (findings exist) or text indicating no issues found.
78
+ >
79
+ > b. Fetch unaddressed inline comments from `cursor[bot]` on `current_head`:
80
+ > ```bash
81
+ > gh api repos/[OWNER]/[REPO]/pulls/[NUMBER]/comments \
82
+ > --jq "[.[] | select(.user.login==\"cursor[bot]\") | select(.commit_id==\"$current_head\")]"
83
+ > ```
84
+ >
85
+ > c. Decide (the four branches below cover every input combination — match the first branch whose predicate holds):
86
+ > - **No bugbot review yet, OR latest bugbot review's `commit_id` differs from `current_head`:** Re-trigger bugbot (step 3), set `bugbot_clean_at = null`, reset `inline_lag_streak = 0`, schedule next wakeup, return.
87
+ > - **Latest review's `commit_id == current_head` AND zero unaddressed inline findings AND review body indicates clean:** Set `bugbot_clean_at = current_head`. Reset `inline_lag_streak = 0`. Transition `phase = BUGTEAM`. Continue to bugteam branch in this same tick — back-to-back convergence requires bugteam to run against the same HEAD before the next wakeup is scheduled.
88
+ > - **Latest review's `commit_id == current_head` with unaddressed inline findings (review body indicates findings):** Apply the **Fix protocol** below to address them. Reset `inline_lag_streak = 0`. The fix protocol pushes a new commit, which sets `current_head` to the new SHA, sets `bugbot_clean_at = null`, replies inline on each thread, and re-triggers bugbot. Schedule next wakeup, return.
89
+ > - **Latest review's `commit_id == current_head` AND review body indicates findings AND inline-comments API returns zero matching comments for `current_head`:** Treat as transient API propagation lag — bugbot publishes the review body and inline comments through separate API operations and the two writes can briefly desync. Increment `inline_lag_streak`. When `inline_lag_streak >= 3`, escalate as a hard blocker (bugbot review is structurally inconsistent — body claims findings while inline anchors stay empty across three consecutive ticks); report and terminate. Otherwise schedule next wakeup at `delaySeconds: 60` (lag is short-lived) and return; the inline comments should appear on the next tick.
90
+ >
91
+ > **If `phase == BUGTEAM`:**
92
+ >
93
+ > a. Run the in-house bugteam audit on the current PR:
94
+ > ```bash
95
+ > claude -p "/bugteam" --max-turns 200
96
+ > ```
97
+ > (The `/bugteam` skill audits the current PR against CODE_RULES, posts review threads, and converges or stops at its own internal cap. Wait for it to complete; capture exit and final summary.)
98
+ >
99
+ > b. **Re-resolve current HEAD now** because `/bugteam` may have pushed commits during its run. The `current_head` from step 1 is potentially stale at this point:
100
+ > ```bash
101
+ > new_head=$(gh api repos/[OWNER]/[REPO]/pulls/[NUMBER] --jq '.head.sha')
102
+ > ```
103
+ > If `new_head != current_head`, set `current_head = new_head` AND set `bugbot_clean_at = null`. The new commits from bugteam invalidate bugbot's prior clean.
104
+ >
105
+ > c. Inspect bugteam's output. Bugteam reports either `convergence (zero findings)` or a list of unfixed findings with file:line.
106
+ >
107
+ > d. Decide based on the (post-bugteam) state — order matters; check pushed-during-bugteam FIRST so a convergence report against a stale HEAD never falsely terminates:
108
+ > - **bugteam pushed during this tick (i.e., `bugbot_clean_at` was just reset to `null` in step b):** Re-trigger bugbot in this same tick (step 3) so the new HEAD enters bugbot's queue immediately, transition `phase = BUGBOT`, schedule next wakeup, return. The new commit needs a fresh bugbot review before convergence can be claimed.
109
+ > - **bugteam reports convergence AND `bugbot_clean_at == current_head` (no push during this tick):** This is back-to-back clean. Mark the PR ready for review:
110
+ > ```bash
111
+ > gh pr ready [NUMBER] --repo [OWNER]/[REPO]
112
+ > ```
113
+ > Report to the parent in one sentence: "PR #[NUMBER] converged: bugbot CLEAN at [SHA], bugteam CLEAN at [SHA]; marked ready for review." Terminate; this is the final tick — skip scheduling the next wakeup.
114
+ > - **bugteam reports convergence BUT `bugbot_clean_at != current_head` (no push during this tick):** Bugteam reached zero findings without committing, yet bugbot still needs re-confirmation against this HEAD. This branch is reachable only when state diverged BETWEEN ticks — for example, the user pushed a manual commit between two wakeups, leaving `current_head` ahead of the SHA bugbot last cleaned. Transition `phase = BUGBOT`, schedule next wakeup, return.
115
+ > - **bugteam reports findings without committing fixes:** apply the **Fix protocol** below (which always re-triggers bugbot after the push), transition `phase = BUGBOT`, schedule next wakeup, return.
116
+ >
117
+ > 3. Re-trigger bugbot (used in step 2.c first branch, in step 2.d BUGTEAM branch 1, and in the Fix protocol). Post a literal `bugbot run` PR comment:
118
+ > ```bash
119
+ > printf 'bugbot run\n' > /tmp/bugbot-run.md
120
+ > gh pr comment [NUMBER] --repo [OWNER]/[REPO] --body-file /tmp/bugbot-run.md
121
+ > rm /tmp/bugbot-run.md
122
+ > ```
123
+ > Use the literal phrase `bugbot run` exactly — Cursor Bugbot's documented re-trigger phrase, empirically the only one that fires a fresh review.
124
+ >
125
+ > 4. Schedule the next wakeup with `ScheduleWakeup` using a single rule: `delaySeconds: 300` whenever bugbot was just re-triggered (whether by step 3 directly, by the Fix protocol's mandatory re-trigger, or by BUGTEAM branch 1's same-tick re-trigger). Bugbot finishes a review in 1–4 minutes, so 300s gives a one-minute safety margin past the upper bound. The single exception is the BUGBOT inline-lag branch, which uses `delaySeconds: 60` because no re-trigger fired and the only thing being awaited is GitHub's inline-comments API catching up. Set `reason` to one short sentence on what is being awaited, including the current `phase` and `bugbot_clean_at` SHA when set; set `prompt` to the literal sentinel `<<autonomous-loop-dynamic>>` so the next firing re-enters these instructions.
126
+ >
127
+ > **Fix protocol** (used by both phases when findings exist):
128
+ >
129
+ > - Read each referenced file:line.
130
+ > - Write a failing test first when the finding has behavior to test. For pure doc, comment, or naming nits with no behavior, go straight to the fix.
131
+ > - Implement the fix.
132
+ > - Stage the affected files and create one new commit on the existing branch:
133
+ > ```bash
134
+ > git add <files> && git commit -m "fix(review): <brief summary>"
135
+ > ```
136
+ > Honor pre-commit and pre-push hooks; when a hook rejects, read its message, fix the underlying issue, retry. Hook rejections flag real underlying issues worth investigating.
137
+ > - Push the new commit:
138
+ > ```bash
139
+ > git push origin [BRANCH]
140
+ > ```
141
+ > Capture the new HEAD SHA. Set `current_head` to it. Set `bugbot_clean_at = null`.
142
+ > - Reply inline on each addressed comment thread:
143
+ > ```bash
144
+ > gh api -X POST repos/[OWNER]/[REPO]/pulls/[NUMBER]/comments/<comment_id>/replies \
145
+ > -f body="Addressed in <new_sha>. <one-line description of the fix>."
146
+ > ```
147
+ > Use `--body-file` when the body contains backticks (per repo policy on `gh` body content).
148
+ > - **Always re-trigger bugbot (step 3 above) after pushing a fix**, regardless of which phase originated the findings. Any new commit invalidates bugbot's prior clean by definition, so bugbot must re-review the new HEAD before convergence can be claimed. Re-triggering in the same tick saves a full wakeup cycle compared to deferring the trigger to the next tick — the fix protocol's last step before scheduling the wakeup is always `printf 'bugbot run\n' > /tmp/bugbot-run.md && gh pr comment ... --body-file /tmp/bugbot-run.md && rm /tmp/bugbot-run.md`.
149
+ >
150
+ > **Stop conditions:**
151
+ >
152
+ > - **Convergence** (back-to-back clean as defined in step 2.d BUGTEAM second branch — `bugteam reports convergence AND bugbot_clean_at == current_head` with no push during this tick): mark PR ready for review, report one-sentence summary to parent, terminate.
153
+ > - **Hard blocker:** API auth failure persists across two ticks, a CI regression whose root cause falls outside this PR, a hook rejection investigated through three commits and still unresolved, `inline_lag_streak >= 3`, or `/bugteam` itself reports a stuck state. Report the specific blocker and your diagnosis to the parent, then terminate; skip scheduling the next wakeup.
154
+ > - **Parent sends `TaskStop`:** terminate immediately.
155
+ >
156
+ > **Safety cap:** when 30 ticks elapse before convergence, stop and report. That many rounds means something structural is wrong with the loop. (Higher than copilot-review's 20-tick cap because two reviewers run sequentially per round.)
157
+
158
+ ### Step 4: Report back to the user
159
+
160
+ After spawning, tell the user in one or two lines: subagent ID, PR URL, that it will alternate bugbot and bugteam and notify on convergence or blocker. Nothing else.
161
+
162
+ ## Stopping the subagent
163
+
164
+ - Convergence → subagent stops itself, marks PR ready for review.
165
+ - Blocker → subagent reports and stops.
166
+ - User says stop → `TaskStop <agent_id>`.
167
+ - User asks what loops are running → `TaskList`.
168
+
169
+ ## Ground rules (for the subagent)
170
+
171
+ - **Append commits.** Each tick adds at most one new fix commit. Multiple findings within one tick collapse into a single commit; the next tick handles the next round.
172
+ - **`bugbot_clean_at` resets on every push.** A new commit invalidates bugbot's prior clean by definition — bugbot must re-review the new HEAD before convergence can be claimed.
173
+ - **Back-to-back clean is the ONLY termination criterion.** Convergence requires both reviewers clean against the same HEAD with no intervening fixes; either reviewer clean alone counts as in-progress.
174
+ - **The `bugbot run` comment is load-bearing.** Use the literal phrase `bugbot run` exactly — empirically the only re-trigger Cursor Bugbot recognizes; alternative phrasings (`re-review`, `bugbot please`, etc.) silently no-op.
175
+ - **`gh pr ready` is the convergence action.** Mark the PR ready for review and stop there. Merge, additional reviewers, title, and body remain the user's decisions; the subagent's contract ends at "ready for review."
176
+ - **Honor pre-push and pre-commit hooks.** When a hook rejects the change, read its output, fix the underlying issue (the failing test, the missing constant, the broken import), and retry.
177
+
178
+ ## Examples
179
+
180
+ <example>
181
+ User: `/pr-converge`
182
+ Claude: [reads PR context, spawns background subagent with the Step 3 template, reports "subagent X driving PR #280 to convergence; will notify on back-to-back clean"]
183
+ </example>
184
+
185
+ <example>
186
+ User: "drive this PR to convergence — bugbot and bugteam until both are clean"
187
+ Claude: [same as above]
188
+ </example>
189
+
190
+ <example>
191
+ Subagent tick fires in BUGBOT phase, latest bugbot review is against an older commit.
192
+ Subagent: [posts `bugbot run` comment, sets `bugbot_clean_at = null`, schedules next wakeup at 300s, returns]
193
+ </example>
194
+
195
+ <example>
196
+ Subagent tick fires in BUGBOT phase, bugbot has 2 unaddressed findings on HEAD.
197
+ Subagent: [TDD-fixes both, one commit, pushes, replies inline on both threads, posts `bugbot run`, schedules next wakeup at 300s, returns]
198
+ </example>
199
+
200
+ <example>
201
+ Subagent tick fires in BUGBOT phase, bugbot is clean against HEAD.
202
+ Subagent: [sets `bugbot_clean_at = HEAD`, transitions `phase = BUGTEAM`, runs `/bugteam` in the same tick]
203
+ </example>
204
+
205
+ <example>
206
+ Subagent in BUGTEAM phase, /bugteam reports convergence and `bugbot_clean_at == current_head`.
207
+ Subagent: [runs `gh pr ready [NUMBER]`, reports "PR converged: bugbot CLEAN at <SHA>, bugteam CLEAN at <SHA>; marked ready for review", terminates]
208
+ </example>
209
+
210
+ <example>
211
+ Subagent in BUGTEAM phase, /bugteam pushed a fix commit during its run.
212
+ Subagent: [re-resolves HEAD, sets `bugbot_clean_at = null`, posts `bugbot run` in this same tick, transitions `phase = BUGBOT`, schedules next wakeup at 300s]
213
+ </example>
214
+
215
+ <example>
216
+ Subagent tick fires in BUGBOT phase, bugbot review body says "found 3 potential issues" against HEAD but the inline-comments API returns zero matching comments for `current_head`.
217
+ Subagent: [increments `inline_lag_streak` to 1, schedules next wakeup at 60s, returns; expects inline comments to appear by the next tick]
218
+ </example>
@@ -11,16 +11,23 @@ The default failure mode is shipping a rebase that compiled but doesn't run. Thi
11
11
 
12
12
  ## When to rebase vs. merge
13
13
 
14
+ > **Decision thresholds**
15
+ >
16
+ > | Threshold | Value |
17
+ > |---|---|
18
+ > | Maximum commits for solo rebase | 5 |
19
+ > | Divergence age that triggers merge-instead | 2 weeks |
20
+
14
21
  **Default to rebase** when:
15
22
 
16
23
  - Solo branch (you are the only author of the commits being rebased).
17
- - 1–5 commits ahead of base.
24
+ - 1–5 commits ahead of base (see Decision thresholds above).
18
25
  - Stacked PR whose base just merged via squash.
19
26
 
20
27
  **Default to merge** when:
21
28
 
22
29
  - Branch has multiple authors (force-push would clobber their state).
23
- - More than ~5 commits or more than ~2 weeks of divergence (rebase complexity grows non-linearly).
30
+ - More than 5 commits or more than 2 weeks of divergence (see Decision thresholds above; rebase complexity grows non-linearly).
24
31
  - The user said "merge", not "rebase".
25
32
  - Open PR with approving reviews already on the current SHA (force-push invalidates them).
26
33
 
@@ -74,10 +81,10 @@ When in doubt, ask. Both work; the choice affects history shape, not correctness
74
81
  6. **Audit auto-merged files.** Files that git merged without conflict markers are not automatically correct. After each commit applies, run:
75
82
 
76
83
  ```
77
- git diff --name-only --diff-filter=M HEAD@{1}
84
+ git diff --name-only --diff-filter=M ORIG_HEAD
78
85
  ```
79
86
 
80
- For each modified file with no conflict markers, eyeball the changes — auto-merge can produce duplicate blocks (when both sides added similar content) or silently drop content (when both sides removed adjacent lines). Pay extra attention to `config/`, `constants.*`, and `__init__.py` files where additions often sit near each other.
87
+ Use `ORIG_HEAD`, which git sets at rebase start; the reflog index `HEAD@{1}` shifts as each rebase step runs and is unreliable mid-rebase. For each modified file with no conflict markers, eyeball the changes — auto-merge can produce duplicate blocks (when both sides added similar content) or silently drop content (when both sides removed adjacent lines). Pay extra attention to `config/`, `constants.*`, and `__init__.py` files where additions often sit near each other.
81
88
 
82
89
  7. **At every conflict, take both sides' intent seriously.** Read both, then decide based on the post-rebase logical state. Do not reflex-pick HEAD or `origin/main`. Document the resolution reasoning in the commit message if it is non-obvious.
83
90
 
@@ -88,10 +95,10 @@ When in doubt, ask. Both work; the choice affects history shape, not correctness
88
95
  8. **Real import check.** For Python:
89
96
 
90
97
  ```
91
- python -c "import <every_top_level_module_in_changed_packages>"
98
+ python -m compileall -q <package>
92
99
  ```
93
100
 
94
- This catches the most common rebase failure: an import that survived the rebase pointing at a name the rebased commits removed or renamed.
101
+ Follow immediately with the test-collection step below. `compileall` catches import-time failures across every module file; combined with `--collect-only` it surfaces `NameError`, `AttributeError`, and `ImportError` cases that a syntax-only check misses.
95
102
 
96
103
  9. **Test collection.** `pytest --collect-only -q` on the changed packages catches NameError, AttributeError, and ImportError surfaces beyond plain imports.
97
104
 
@@ -102,7 +109,7 @@ When in doubt, ask. Both work; the choice affects history shape, not correctness
102
109
  - **Preferred:** `mcp__serena__find_referencing_symbols` (symbol-aware; ignores false matches in comments and string literals).
103
110
  - **Fallback:** `mcp__zoekt__search` for cross-repo or large trees.
104
111
  - **Then:** the `Grep` tool (e.g., `Grep(pattern="<symbol>", type="py")`) for fast in-repo scans.
105
- - **Last resort:** `grep -rn "<symbol>" --include='*.py' --include='*.ts' --include='*.json' .`
112
+ - **Last resort:** `grep -rn "<symbol>" .` (let ripgrep defaults and `.gitignore` handle scoping)
106
113
 
107
114
  Any reference outside the rebased commits' own changes is a stale reference. Either update it (with user authorization) or surface it and refuse to push.
108
115
 
@@ -117,7 +124,7 @@ When in doubt, ask. Both work; the choice affects history shape, not correctness
117
124
  - Ask for explicit authorization.
118
125
  - If denied: leave the rebase result locally, report merge-instead as the alternative, stop.
119
126
 
120
- 14. **Refuse to force-push** `main`, `master`, `release/*`, `production`, or any branch with more than one author in `git log --format='%ae' origin/<branch> | sort -u`. Surface the refusal; do not ask for authorization on these.
127
+ 14. **Refuse to force-push** `main`, `master`, `release/*`, `production`, or any branch with more than one unique author. Count unique authors in a cross-platform way: `git log --format='%ae' origin/<branch> | python -c "import sys; print(len(set(sys.stdin)))"`. This form works on both Windows (PowerShell) and Unix without shell pipeline extensions. Surface the refusal; do not ask for authorization on these.
121
128
 
122
129
  15. **Always `--force-with-lease=<branch>:<sha>`**, never bare `--force`. Pin the lease to the SHA you started from so concurrent pushes are detected as a lease mismatch instead of clobbered.
123
130