Imported from gunnartorfis/personal-finance (
.agents/skills/autopilot/SKILL.md). Install upstream withnpx skills add gunnartorfis/personal-finance --skill autopilot. Copyright stays with the author.
Autopilot
Drive a task from an ADR / GitHub issue / freeform description all the way to a
review-clean PR, test-first, without babysitting. Big work is split into small
chunk PRs stacked on one feature branch; small work is a single PR to
main. Every PR must reach all CI checks green + Greptile Confidence 5/5
before it moves on. Chunks auto-merge into the feature branch; the final PR to
main is prepared and handed off for a human to merge.
Full command cookbook + edge cases: reference.md.
Run me
/loop /autopilot <task>
Examples: /loop /autopilot work on ADR-0017 · /loop /autopilot tackle issue #19.
- Must run under
/loopwith no interval (self-paced). Each loop tick I re-derive state from git + GitHub, advance as far as I can, and only when I must wait on GitHub (CI running, Greptile pending) do IScheduleWakeupand end the tick. IScheduleWakeup({stop:true})when the work is landed or blocked.ScheduleWakeuponly exists inside/loop— if it is unavailable, tell the user to relaunch under/loop; do not busy-wait. - Interactive session (the plan gate and any pause use
AskUserQuestion).
Prime directives
- Re-derive, don't remember. Treat git + GitHub as the source of truth. The
only thing carried across ticks is
<task>(re-passed in the prompt) and the durable checklist stored in the epic PR body. Never assume in-memory state survived — a tick may start with a compacted context. - Never merge without green + 5/5. A PR merges only when all required CI checks are green AND Greptile Confidence Score is 5/5.
- Never touch
maindirectly. No commits, no force-push, no merge tomain. The terminal step is hand-off (see Phase 7). - Stay in your lane. Identify this run only by its branch
feat/<slug>(derived deterministically from<task>) and itsfeat/<slug>-<n>-…chunks — select PRs by that branch, never by the sharedautopilotlabel alone (a concurrent run in another worktree carries the same label). Never modify, rebase, merge, or delete a branch/PR that isn't this run's. - TDD is not optional. Production code only ever appears to satisfy a
failing test. Follow
/tdd. - One plan gate, then autonomous. Get the breakdown approved once, then run to the end, pausing only on the conditions in Stop & pause.
Every tick: re-derive state, then route
Run the discovery block (reference.md §State) to find this run's PR by its deterministic branch — never by the shared label (that would grab a concurrent autopilot run's PR from another worktree):
TASK="<the exact task text you were invoked with>" # stable across ticks
HASH=$(printf '%s' "$TASK" | shasum -a 256 | cut -c1-8) # collision-resistant suffix
SLUG=$(printf '%s' "$TASK" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+|-+$//g' | cut -c1-32 | sed -E 's/-+$//')
BRANCH="feat/${SLUG}-${HASH}" # identity: kebab prefix + hash of full task
gh label create autopilot --color 1f6feb 2>/dev/null; gh label create autopilot-epic --color 8250df 2>/dev/null
RUN=$(gh pr list --state all --head "$BRANCH" --base main --json number,state,labels,body,url --jq '.[0] // empty')
RUN is this run's PR: a big-feature epic if its labels include
autopilot-epic, else a small single PR. Pick the FIRST matching row:
| If … | Phase |
|---|---|
No RUN, and no local $BRANCH yet |
0 Intake & plan |
Just approved a big plan, $BRANCH/epic not created yet |
0 → create epic then Phase 1 |
RUN is an epic whose checklist has an unchecked chunk with no open chunk PR |
1 Implement the next chunk |
RUN (or a chunk PR based on $BRANCH) is open |
inspect that PR → 4 / 5 / 6 |
RUN is an epic with all chunks merged |
7 Land |
RUN merged (small) or epic handed off |
Done — ScheduleWakeup({stop:true}) + report |
When RUN (or a chunk PR based on $BRANCH) is open, classify it and act, using
the verified one-liners in reference.md §Poll:
PR=<number>
SCORE=$(...) # Greptile score FOR THE CURRENT HEAD commit; "pending" until Greptile
# has reviewed the latest push (a stale prior-revision score never counts)
CHECKS=$(...) # "green" only if all six required jobs are present AND successful
CHECKS=failed→ Phase 5 (fix the failure, push, re-review).CHECKS=pendingorSCORE=pending→ still running → Phase 4 (wait).CHECKS=greenandSCORE=5→ Phase 6 (merge / advance).CHECKS=greenandSCORE<5→ Phase 5 (address feedback).
Phase 0 — Intake & plan gate
- Resolve the task.
ADR-XXXX→ readdocs/adr/*XXXX*.md(+ any linked plan underdocs/).issue #N/#N→gh issue view N --comments.- freeform → take it at face value.
- Always read
CONTEXT.mdand any ADRs in the area for domain vocabulary (test names and interfaces must match it). Note the i18n rule: user-facing strings live inmessages/en.jsonandmessages/is.json(keys must stay in parity —lib/i18n/parity.test.tsenforces it).
- Decide size.
- Small — one cohesive change, one reviewable diff (≈ ≤ a few files / one
behavior cluster). → single PR to
main. - Big — multiple independent behaviors, a multi-slice ADR/plan, or a diff a reviewer could not hold in their head at once. → feature branch + stacked chunk PRs. Each chunk must be an independently reviewable vertical slice (a real behavior, test-first), not a horizontal layer.
- Small — one cohesive change, one reviewable diff (≈ ≤ a few files / one
behavior cluster). → single PR to
- Plan gate (once). Present, via
AskUserQuestion:- size decision + one-line rationale;
- for big: the ordered chunk list (each a short title), the feature-branch
name
feat/<slug>, and that chunks land on the feature branch while the final PR tomainis handed off; - for small: the branch name and the single PR title. Offer Proceed / Adjust breakdown / Cancel. On Adjust, revise and re-ask. Only after approval do anything that writes to the repo/GitHub. (Re-derivation skips this gate automatically on later ticks, because a branch or PR already exists.)
Big-feature setup
After approval, create the feature branch, seed it, and open the draft epic PR whose body is the durable checklist (reference.md §Epic):
git checkout main && git pull --ff-only
git checkout -b feat/<slug>
git commit --allow-empty -m "chore(<scope>): start <feature> epic"
git push -u origin feat/<slug>
gh pr create --base main --head feat/<slug> --draft --label autopilot --label autopilot-epic \
--title "feat(<scope>): <feature> (epic)" --body "$(cat <<'EOF'
## Autopilot epic: <feature>
Source: <ADR-XXXX | issue #N | task text>
Stacked chunk PRs land here; this PR lands on main by hand-off once every chunk
is merged and it is green + Greptile 5/5.
### Chunks
- [ ] 1. <chunk title>
- [ ] 2. <chunk title>
- [ ] 3. <chunk title>
EOF
)"
Then continue into Phase 1 for chunk 1.
Phase 1 — Implement a chunk (TDD)
- Branch off the right base, freshly updated:
- big:
git fetch origin && git checkout feat/<slug> && git pull --ff-only && git checkout -b feat/<slug>-<n>-<short> - small:
git checkout main && git pull --ff-only && git checkout -b feat/<slug>
- big:
- Invoke
/tddfor this chunk's behaviors. Strict red→green→refactor, one behavior at a time, vertical slices; tests assert observable behavior through public interfaces; expected values are independent literals (no tautological tests); a bugfix's first RED test reproduces the bug. Commit on green — never commit red. - Commit convention (this repo — see Conventions): scoped
Conventional Commits, e.g.
feat(holdings): add balance staleness nudge, with theCo-Authored-Bytrailer. For a chunk of a big feature, put(slice <n>)in the subject to match repo history.
Phase 2 — Local gate (before every PR open AND before every push in Phase 5)
Reproduce the CI gate locally; all four must pass before pushing a PR-opening commit (reference.md §Gate):
pnpm lint && pnpm typecheck && pnpm test:run \
&& pnpm dlx react-doctor@0.5.8 --yes --scope full --blocking warning --no-telemetry
- These are the checks the user requires locally: lint, test, typescript,
react-doctor. Note react-doctor needs the CI flags above — the bare
pnpm doctorscript does NOT reproduce the gate. buildandmigrationsare also CI gates but may need env/DB; do not block PR creation on them — they are enforced at merge time viagh pr checks(Phase 4). If migrations are relevant to the change, runpnpm db:generateand commit any new files underdrizzle/(the CImigrationsjob fails on uncommitted schema drift).- On failure: fix and re-run. Cap at 3 fix attempts for the same failure; past that, pause + report (Stop & pause).
Phase 3 — Open PR
git push -u origin HEAD
gh pr create --base <BASE> --label autopilot \
--title "<scoped conventional title>[ (slice <n>)]" --body "$(cat <<'EOF'
## Summary
- <what changed and why>
## Risk areas
- <what a reviewer should scrutinise>
## Test plan
- [x] pnpm lint · pnpm typecheck · pnpm test:run
- [x] react-doctor (full scope)
EOF
)"
<BASE>=feat/<slug>for a chunk,mainfor small. Reference the ADR/issue in the body (ADR-0017,#19) when applicable.- Then go to Phase 4 (wait for review).
Phase 4 — Await review
CI runs and Greptile reviews (Greptile edits a single issue comment in place,
posting <h3>Confidence Score: X/5</h3> once done; it takes a few minutes).
- Read
CHECKSandSCORE(reference.md §Poll). - If
CHECKS=failed→ Phase 5. - If
CHECKS=green+SCORE=5→ Phase 6. - If
CHECKS=green+SCORE<5→ Phase 5. - Otherwise still pending →
ScheduleWakeup~240s while Greptile is pending, ~150s while only CI is pending (Pacing), then end the tick.
Phase 5 — Address until 5/5
When checks are red or the score is <5:
- Invoke
/address-pr-feedback <PR>. It triages inline + PR-body (Greptile) feedback, applies valid fixes, runs build/test, commits, pushes, replies to threads, and re-triggers Greptile (@greptileai review). It proceeds autonomously on clearly-valid feedback. - If checks were red for a reason
/address-pr-feedbackwould not touch (e.g. a flaky/infra failure or a lint/type error), fix it yourself, re-run the local gate, and push. - Re-review detection: capture the Greptile comment
updated_atbefore pushing; the review is fresh onceupdated_atadvances past your push and a score is present. ThenScheduleWakeupand return to Phase 4. - Cap: 3 address rounds per PR. If still
<5/5after 3 rounds, stop the loop and report the specific unresolved Greptile items so the user can decide (some findings are intentional and worth pushing back on, not fixing).
Phase 6 — Merge chunk & advance
Only reachable at green + 5/5 for the current head commit. (The Poll check
reports SCORE=pending when Greptile's 5/5 was for an earlier revision, so a fix
pushed after a prior 5/5 can never merge until Greptile re-reviews the new head.)
- Chunk (big):
gh pr merge <PR> --squash --delete-branch(merges into the feature branch). Then tick the chunk in the epic PR body checklist (reference.md §Checklist) and go implement the next unchecked chunk (Phase 1). If it was the last chunk → Phase 7. - Small: do NOT merge. Go to Phase 7 (hand off).
Never merge a chunk whose base (the feature branch) has moved underneath it in a
way that made checks stale — if CHECKS is not currently green, go back to
Phase 4/5 first.
Phase 7 — Land (hand off)
- Big: mark the epic PR ready (
gh pr ready <EPIC>), ensure it is green + Greptile 5/5 (run Phases 4–5 on the epic PR itself — Greptile reviews the whole feature here). Then stop: do not merge tomain. - Small: ensure the single PR is green + 5/5.
- Post a short hand-off summary and
ScheduleWakeup({stop:true}):✅ Ready to merge to
main: . All CI checks green, Greptile 5/5. <for big: N chunks merged intofeat/<slug>.> Merge when you're ready.
Pacing
At a wait point, call ScheduleWakeup and END the tick (stop calling tools):
prompt: the exact/autopilot <task>you were invoked with (re-fires the loop).delaySeconds: ~240 while Greptile is pending (stays inside the cache window; Greptile typically takes a few minutes); ~150 while only CI is pending. Do not sleep between local steps — only yield on GitHub waits.reason: specific, e.g."waiting on Greptile review of PR #124".- When landed or blocked:
ScheduleWakeup({stop:true}).
Stop & pause
Stop the loop (ScheduleWakeup({stop:true})) or pause with AskUserQuestion, and
report clearly, when:
- Greptile stuck
<5/5after 3 address rounds on one PR → stop; list the unresolved items and why they may be intentional. - Local gate fails 3× for the same failure, or CI stays red after fixes → stop; paste the failing output.
- A human reviewer requests changes → apply if clearly valid (via
/address-pr-feedback); if it implies a product/scope/architecture decision → pause +AskUserQuestion. - A product/scope ambiguity surfaces during implementation → pause + ask.
- Anything would require touching
mainor another team's branch → stop.
Always leave the world in a clean, resumable state (branch pushed, PR open, checklist current) so the next tick — or a human — can pick up.
Conventions
- Base branch:
main(this repo has nodev). Chunk PRs base on the feature branch; small + epic PRs base onmain. - Branches: this run's branch is
feat/<slug>— the epic branch for big work, the single branch for small work; chunks arefeat/<slug>-<n>-<short>.<slug>is a deterministic kebab of<task>plus an 8-char hash of the full task (so distinct tasks that share a prefix never collide), so every tick recomputes the same branch and finds this run's PR by--head. The real conventional type (fix/feat/…) lives in the commit and PR title, not the branch name. - Commits: scoped Conventional Commits (
feat(scope): …,fix(scope): …), imperative, lowercase, no trailing period. End withCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>(matches this repo's history and the harness rule). Commit on green only. - PR body:
## Summary/## Risk areas/## Test plan. Label every PRautopilot; label the epicautopilot-epic. - i18n: never hard-code UI strings; add keys to both
messages/en.jsonandmessages/is.json. - Do NOT use
/create-pror/commit-and-pushhere — they target adevbase withHQ-XXXXLinear tickets and would prompt for a push, which breaks this repo'smain-based, ADR/issue-referencing, autonomous flow. Own git directly per the above.