Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions .agents/skills/babysit/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
name: babysit
description: Drive a PR to a clean review (Greptile 5/5, zero open threads) — ships if needed, triggers Greptile/Cursor Bugbot, fixes real findings, replies to and resolves every thread, and loops until clean
---

# Babysit PRs

Owns a PR end-to-end through review: ship it, wait for the automatic review round, and if it
isn't already clean, drive fix → reply → resolve → re-review cycles until Greptile reports 5/5
and there are zero open comment threads. Designed to be run under `/loop` (no fixed interval —
let it self-pace on review latency) so it survives across multiple wakeups in the same session.

## When to use

- The user says "babysit this PR", "keep working the reviews until it's clean", or similar
- As the natural follow-up to `/ship` when the user wants the review loop automated rather than
manually re-triggering reviews and answering comments themselves

## Inputs

Needs a PR number. If none is given and there's no open PR for the current branch, run `/ship`
first (which includes the `origin/staging` sync check — see `.agents/skills/ship/SKILL.md`) to
create one.

## Definition of "clean"

Both must hold:
1. The latest Greptile summary comment reports **Confidence Score: 5/5**
2. `reviewThreads` (GraphQL, see below) has **zero threads with `isResolved: false`**

Do not stop early on "no new comments this round" alone — a thread can be open from an earlier
round. Always check both conditions freshly after every push.

## Loop

1. **Check current state** before doing anything:
```bash
gh pr view <n> --json comments -q '[.comments[] | select(.author.login=="greptile-apps")] | last | .body'
gh api graphql -f query='
query { repository(owner: "<owner>", name: "<repo>") { pullRequest(number: <n>) {
reviewThreads(first: 50) { pageInfo { hasNextPage endCursor } nodes { id isResolved path line
comments(first: 5) { nodes { id databaseId author { login } body } } } } } } }'
Comment thread
waleedlatif1 marked this conversation as resolved.
```
`[.comments[]] | last | .body`, not `... | .body | tail -1` — the latter pipes every matching
comment's full multi-line body through the pipeline and keeps only the final *line* of that
combined output (usually the "Reviews (n): Last reviewed commit..." footer), not the last
*comment*, so it silently misses the actual "Confidence Score: X/5" line.
`reviewThreads(first: 50)` is a single page — check `pageInfo.hasNextPage`. If `true`, don't
stop yet: re-run the same query with `after: "<endCursor>"` and keep paging until
`hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but
stopping on a partial page would silently miss unresolved ones past the cutoff.
If Greptile is 5/5 and every thread across all pages has `isResolved: true`, stop — report the
outcome (see "Reporting" below) and skip the rest of this list.

2. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run
automatically on PR open — confirm via `gh pr checks <n>` (look for `Cursor Bugbot` /
`Greptile Review`) and wait for that first round before doing anything else.

3. **If a review round has landed and it isn't clean**: for every thread where
`isResolved: false`, triage the finding on its own merits — this is the part that requires
judgment, not a mechanical loop:
- **Real bug**: fix it in the cleanest way available. Match the codebase's existing
conventions for that kind of problem before inventing a new one (e.g. an SSRF-prone
user-supplied-host fetch should use whatever `validateUrlWithDNS`/`secureFetchWithPinnedIP`
pattern the rest of the codebase already uses for that exact situation — grep for a sibling
integration solving the same problem first). Never patch around a finding with a
workaround, a broad try/catch, or a suppression comment — fix the actual cause.
- **False positive**: don't change code. Reply with the specific reason it doesn't apply
(cite the type definition, the established pattern it matches, or the doc it follows) so
the reviewer bot and a human skimming later both understand why it was left as-is.
- **Already fixed by an earlier finding in the same round**: note that and resolve without a
duplicate code change.

4. **Reply to every thread individually** before resolving it — never resolve silently:
```bash
gh api repos/<owner>/<repo>/pulls/<n>/comments/<databaseId>/replies -f body="<what was done and why>"
```
Then resolve via GraphQL (needs the thread `id` from step 1, not the comment id):
```bash
gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: "<threadId>"}) { thread { isResolved } } }'
```

5. **Before pushing, re-run the full sync check from `/ship` step 2** — not just the log command,
the whole check-and-recover flow (stash WIP if needed, rebase, verify the rebase didn't just
cleanly replay stray commits, cherry-pick rebuild if it did or if it conflicted). A babysit
loop spanning a long session is exactly the scenario where a branch can drift, and pushing
review fixes on top of undetected drift is how an oversized PR happens even after the branch
was fixed once. Then run the repo's pre-ship checks the same way `/ship` does before
committing — not just lint/typecheck/boundary-validation, but also the conditional `/cleanup`
(if this round's fix touched UI code) and `/db-migrate` (if it touched schema/migrations)
gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip
either gate just as easily as the original commit did.

6. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 5's
sync check rewrote history, which includes a plain `git rebase origin/staging` that completed
with no conflicts, not only the cherry-pick rebuild path; both rewrite commits already
published to the remote, so a plain `git push` can be rejected either way — then run `/ship`
step 9's post-push verify — not just before the first push, every push in the loop:
```bash
git fetch origin staging && git log --oneline --reverse origin/staging..HEAD
gh pr view <n> --json commits -q '.commits[].messageHeadline'
```
`--reverse` matches `git log`'s newest-first default to the PR commit list's oldest-first
order — without it a positional comparison can spuriously fail on any multi-commit branch.
These two lists must describe the same commits. A review loop runs many pushes across many
rounds; checking sync only before the push (step 5) and never after is how a bad push or a
PR whose commit history quietly went stale between rounds goes unnoticed.

7. **Re-trigger review** by posting `@greptile` and `@cursor review` as **two separate PR
comments** — never combine them into one comment, each bot only responds to its own mention:
```bash
gh pr comment <n> --body "@greptile"
gh pr comment <n> --body "@cursor review"
```

8. **Wait for the new round**, then go back to step 1. Pace the wait with `ScheduleWakeup` using
a fallback delay of ~250–300s (Greptile/Cursor typically take 1–3 minutes) — never busy-poll
in a sleep loop. Pass the same `/loop babysit PR <n>` prompt on each wakeup so the loop
resumes correctly.

9. **Stop conditions**: clean state reached (see above), or the same unresolved finding survives
two consecutive rounds with no new information (surface it to the user instead of looping
forever), or the user interrupts.

## Reporting

When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each),
what was pushed back on as a false positive and why, and the final Greptile score / thread count.

## Hard rules

- Never post the two re-review mentions as a single combined comment.
- Never resolve a thread without replying to it first.
- Never fix a finding with a hacky workaround — if the clean fix isn't obvious, find the sibling
pattern elsewhere in the codebase solving the same class of problem and match it.
- Never silently drop a finding — every thread gets either a code fix or a reasoned reply.
- Always re-run the `/ship`-style sync check before every push in the loop, not just the first.
41 changes: 34 additions & 7 deletions .agents/skills/ship/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,48 @@ You help ship code by creating commits, pushing to the remote branch, and creati
When the user runs `/ship`:

1. **Check git status** - See what files have changed
2. **Generate a commit message** following this format: `type(scope): description`
2. **Sync check**: `git fetch origin staging && git log --oneline origin/staging..HEAD`. Read the actual commit list, not just how many there are — it must show ONLY commits you can attribute to this session (recognizable subjects/SHAs). A worktree/branch can silently be cut from a stale local `staging`, dragging in unrelated commits; a corrupted branch's inflated commit *count* can coincidentally match a later check even when the *commits* are wrong, so always compare content, never just a number.
- If it shows commits you don't recognize, fix it now, **before** staging/committing any new work (step 7 hasn't run yet):
- If the working tree has uncommitted changes, stash them first — `git stash push -u -m ship-sync-fix` — so the rebase below isn't blocked by dirty state. Restore with `git stash pop` once the branch is fixed.
- Try `git rebase origin/staging` first.
- **A rebase finishing without conflicts does NOT by itself mean the branch is clean** — it can replay stray commits onto the new base with no conflict at all. After the rebase (clean or not), re-run `git log --oneline origin/staging..HEAD` and re-check the commit list against what you recognize.
Comment thread
waleedlatif1 marked this conversation as resolved.
- If the rebase conflicted on commits you don't recognize, OR it finished cleanly but the re-checked log still shows commits you don't recognize, abandon that result (`git rebase --abort` if still mid-rebase) and rebuild instead, in this exact order:
1. **While still on `<original-branch>`**, identify the SHA(s) to preserve — **not** the whole range. `git log --oneline --reverse origin/staging..<original-branch>` lists everything ahead of `origin/staging`, but in exactly this scenario that range also contains the unrecognized/stray commits you're trying to leave behind — blindly cherry-picking the full range recreates the same polluted branch. Read the list and write down only the SHA(s) you recognize as your own session's work (e.g. `abc1234 def5678`); do this *before* touching any temp branch, since once you check out `ship-sync-tmp` at `origin/staging` in step 4, `HEAD` no longer contains these commits and the same lookup at that point returns nothing.
2. `git checkout <original-branch>` — harmless no-op if you're already there, but required if an earlier interrupted attempt left you sitting on `ship-sync-tmp`: git refuses to delete the branch you're currently on, so deleting it before switching away silently fails and blocks the rest of the rebuild.
Comment thread
waleedlatif1 marked this conversation as resolved.
3. Delete any leftover from an earlier attempt: `git branch -D ship-sync-tmp 2>/dev/null || true` — always succeeds, including when there's nothing to delete (a first attempt), so it never blocks the rest of the rebuild on its own exit code.
4. `git checkout -b ship-sync-tmp origin/staging`.
5. `git cherry-pick` the SHAs captured in step 1, **in that oldest-first order** — cherry-picking more than one session commit out of order can fail or produce the wrong history. Resolve conflicts.
Comment thread
waleedlatif1 marked this conversation as resolved.
6. `git branch -f <original-branch> HEAD`, `git checkout <original-branch>`, and delete `ship-sync-tmp` (`git branch -D ship-sync-tmp`).
- Re-verify with `git log --oneline origin/staging..HEAD` — it must list only commits you recognize before you proceed to committing new work.
Comment thread
waleedlatif1 marked this conversation as resolved.
3. **Generate a commit message** following this format: `type(scope): description`
- Types: `fix`, `feat`, `improvement`, `chore`
- Scope: short identifier (e.g., `undo-redo`, `api`, `ui`)
- Keep it concise
3. **Run the cleanup pass** — only if the diff modifies UI code (any `.tsx` file, or anything under `apps/sim/components/`, `apps/sim/hooks/`, or `apps/sim/stores/`): `/cleanup`
4. **Run the cleanup pass** — only if the diff modifies UI code (any `.tsx` file, or anything under `apps/sim/components/`, `apps/sim/hooks/`, or `apps/sim/stores/`): `/cleanup`
- The six code-quality skills (effects, memo, callbacks, state, React Query, emcn) only apply to React code, so skip this step entirely when no UI was touched. When it runs, it applies fixes so they land in this commit.
4. **Run migration safety** — only if the diff touches `packages/db/migrations/**` or `packages/db/schema.ts`:
5. **Run migration safety** — only if the diff touches `packages/db/migrations/**` or `packages/db/schema.ts`:
- Run `/db-migrate` to review the migration for zero-downtime safety (expand/contract phasing, backward-compatibility with the deployed app version).
- `bun run check:migrations origin/staging` must pass (staging is the PR base). Do not silence a flagged statement with a `-- migration-safe:` annotation unless `/db-migrate` confirmed the old code no longer depends on it; otherwise split the destructive change into a later deploy.
5. **Run pre-ship checks** from the repo root before staging:
6. **Run pre-ship checks** from the repo root before staging:
- `bun run lint` to fix formatting issues
- `bun run check:api-validation:strict` to catch boundary contract failures before CI
6. **Stage and commit** the changes with the generated message
7. **Push to origin** using the current branch name
8. **Create a PR** to staging with a description in the user's voice
7. **Stage and commit** the changes with the generated message
8. **Push to origin** using the current branch name — `--force-with-lease` if step 2's sync
check did any history rewrite (a clean rebase or a cherry-pick rebuild) on a branch that had
already been pushed once; a plain push would be rejected in exactly the polluted-remote case
step 2 exists to fix
9. **Create a PR** to staging with a description in the user's voice, then do a final content check — not a count check — comparing what actually landed:
```bash
git fetch origin staging && git log --oneline --reverse origin/staging..HEAD
gh pr view <n> --json commits -q '.commits[].messageHeadline'
```
Comment thread
waleedlatif1 marked this conversation as resolved.
Re-fetch first — comparing against a stale local `origin/staging` ref can mask real drift or
flag a false mismatch even when the branch and push are correct. `--reverse` makes the git log
oldest-first, matching the PR commit list's order — plain `git log` is newest-first, and a
positional/line-by-line comparison against the PR's oldest-first list can spuriously fail on
any multi-commit branch. These two lists must describe the same commits in the same order
(same subjects, the last one being the commit from step 7). If they don't match, the branch
still has a problem — redo step 2's fix and `git push --force-with-lease`.

## Commit Message Format

Expand Down
Loading
Loading