auto-wave
Skill0 starsChanged 3 months agoMITView on GitHub
Sequential wave runner. Drives a queue of "waves" automatically, one after another, each wave as a FRESH headless `claude -p` process (empty context, so nothing piles up), branch isolated so nothing auto-deploys, with an exception stop: it holds only when a wave is not green or a real product decision comes up. A wave counts as green ONLY with proof (executed_tests > 0, no skips, a real proof artefact; a non-empty caveat is a stop signal), self-attestation counts as red. After a green run the end-of-run hook does everything UP TO the merge: push, open the PR, post a fresh code review, write a deploy handoff, notify, report. It NEVER merges and NEVER deploys. Use when the user says "/auto-wave", "run the waves", "start auto-wave", "work through the queue", "start the wave runner", "build the waves automatically". Mode "full" chains waves, pre-deploy and deploy behind one word, with a fixed abort list as the gate. NOT for planning a single wave, building a single issue, reviewing, or deploying on its own.
- Commits and pushes
In this repository7 other files it gives its agents.
- check-domainSkill
- handoverSkill
- pitchSkill
- pmSkill
- process-interviewerSkill
- seo-geo-auditSkill
- usage-dashboardSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
17 rules. About 0 are common, 5 are distinctive, and 12 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
12
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Distinctive: line 34
Hard reset: Every wave is a new claude process with an empty conversation.
- Very specific: line 52
Proof of green instead of self-attestation: Every claude -p wave has to PROVE its green: executed_tests > 0, skipped_tests == 0, and proof …
- Very specific: line 65
Research before
blocked: Before a wave writes itself off as blocked (a self-fix fizzled, the same failure repeats, the fix goes in … - Very specific: line 70
The status file is the truth, but green needs proof: Every wave writes a wave-NN.status.json as its last action (green / blocked / …
- Distinctive: line 96
Instead of build waves only, the queue can hold mixed, typed tasks: work that needs an exclusive resource, ops and infrastructure fixes, …
- Very specific: line 101
Every entry is ## <id> plus fields:
- Very specific: line 153
Frontend review (release chain step 2): If the diff touches UI paths (frontend_review_globs), a SECOND throwaway process goes at the …
- Very specific: line 177
Staging instead of a deploy prompt: If deploy_queue_script is configured, the hook enters the run into that queue and ends there.
- Very specific: line 183
Best-effort contract: if push, gh, a review or the handoff fails, that does NOT crash the run.
- Very specific: line 257
Straight to
--run, no dry run, no question: No plan preview for confirmation, no "say go" gate. - Very specific: line 270
Start the run in the background (long runtime, hours are possible).
- Very specific: line 273
If the run stops (exit 2), relay the reason in plain language.
- Distinctive: line 278
If all waves are through, the end-of-run hook has already pushed, opened the PR, posted the review comments, written the deploy handoff and …
- Very specific: line 281
If push or PR failed (best-effort), name the branch and the log.
- Distinctive: line 311
The abort list: Every hit stops the chain BEFORE the next production mutation and sends a push with the reason in plain language:
- Distinctive: line 351
Anti-scope of the mode: no building, fixing or reviewing in the conductor chat itself (all of it in spawns), no scope beyond what was …
- Very specific: line 368
When the call, the status protocol or the safety contract changes, update this SKILL.md and the README in the same pass.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
auto-wave
A queue of waves, driven sequentially and automatically. Every wave runs as its own,
freshly started claude -p process. The runner itself is a deterministic Python script,
no LLM in the driver: the intelligence sits in the per-wave processes it spawns.
It replaces the manual loop of "open a new chat after every wave and paste the next one in".
auto-wave : wave-planning behaves like a loop runner to a single build step, one level
above building, at wave granularity rather than issue granularity.
Why a fresh process per wave
This is the whole point: context resets at every wave and does not pile up over N waves. Wave 10 is as sharp as wave 1. Four layers, all enforced in the wave prompt:
- Hard reset. Every wave is a new claude process with an empty conversation. At the start it reads only what is curated: its issue specs, the project plan, the relevant rules files, the project context file, the previous wave's handover.
- Heavy reading in throwaway sub-agents. Searching, reading many files, reviewing all
happen in
Explore/general-purpose/code-reviewersub-agents; only their conclusion comes back. - A one-page handover instead of a conversation history. The baton between waves lies on disk (handover file plus your project's living documents), not in a chat.
- Keep waves small. Queue discipline: one to two days of work per slice.
Safety contract (not negotiable)
- Branch isolation and the merge stop line. The runner creates
<branch_prefix>/<run-id>off the base branch and builds ONLY there. Never merge into the base branch. Never push to it. Never deploy. ALLOWED: pushing the feature branch to origin and opening a PR, because neither triggers a production deploy in the usual setup where only a merge into the default branch deploys. For the runner itself the rule stays: nevergh pr merge. - Proof of green instead of self-attestation. Every
claude -pwave has to PROVE itsgreen:executed_tests> 0,skipped_tests== 0, andproofpointing at an existing, non-empty artefact (real test runner output,wave-NN.proof.txt). The runner (enforce_green_proofin run.py) demotes green without proof toblocked(exception stop) and PERSISTS that demotion into the status file, so a resume does not count the wave as green either. A non-emptycaveatfield is a stop signal, not prose, on every path including typed tasks. The origin is measured, not theoretical: a wave once wrotegreenwith 0 executed tests and an honest caveat in the JSON that no parser read. - Exception stop. The run goes through on its own and holds by itself only when: a
wave does not turn green despite self-fixing (
blocked), OR a real product, design or strategy decision comes up (needs-decision, because a human decides product calls, never the AI), OR the state is unclear (no status file, timeout). On a stop it names the branch and the run log and exits 2. - Research before
blocked. Before a wave writes itself off asblocked(a self-fix fizzled, the same failure repeats, the fix goes in circles), the prompt tells it to STOP rather than guess the next fix: put an adversarialresearchsub-agent on an ALTERNATIVE cause first ("do NOT confirm my assumption"), work the finding in, and only then, if it still does not hold, writeblocked. - The status file is the truth, but green needs proof. Every wave writes a
wave-NN.status.jsonas its last action (green/blocked/needs-decision). Missing means stop, never drive on blind. The status file is the truth about what the wave CLAIMS; the proof check is the truth about whether that counts. After a green wave the runner additionally attaches the commit hash as acommitfield (for the PR body and the report); the wave does not write that field itself. - Sequential, one worktree. Waves run one after another on ONE branch. That is a deliberate choice against parallel worktrees: a shared checkout has one HEAD, and a second build stealing it mid-wave is a failure mode that costs more than the concurrency wins.
The queue
One ## <slug> section per wave, in build order. Template: queue.example.md.
## backend-add-service-form
goal: Backend form to add and edit a service # MANDATORY, one sentence
specs: .scratch/phase-2/ # optional, path to the issue specs
review: code-review # optional, default from config.jsonA wave with specs builds noticeably more precisely than one working off a goal sentence, so plan the issues first where you can.
Typed queue (solo / ops / gate + needs)
Instead of build waves only, the queue can hold mixed, typed tasks: work that needs an
exclusive resource, ops and infrastructure fixes, and steps held for a human word, with
ordering dependencies. One run drives all of it. The runner detects the mode
automatically by the lane: field. Template: queue.typed.example.md.
Every entry is ## <id> plus fields:
lane:(mandatory)solo|ops|gate.solostrictly serial, for tasks that need an exclusive resource (one shared test database, one device, one licence seat), so never two at once. Before each one the runner brings the shared checkout onto a clean base best-effort; uncommitted tracked work on a foreign branch stops the run instead of being clobbered.opsa headlessclaude -pthat reads and executes the spawn file. Serial by default, which is the safe answer. Concurrent only opt-in (parallel: true) and only whenops_parallel_isolatedis on inconfig.json. Reason: a shared checkout has one HEAD. The win of this runner is hands-off, not concurrency; do not over-promise it.gatenever runs automatically. The runner holds, pushes a notification "ready for your word:<note>" and writes a report. Gates are deferred to the end of the run (after all solo and ops work) and reported as "ready" or "needs still open". The deploy is the typical one.
spawn:mandatory for solo and ops (absolute or project-relative path to the spawn file, which has to exist, otherwise the run stops hard BEFORE it starts). Optional for a gate.needs:optional. The id of another task; this one runs only once that one is green (a needs DAG, topologically resolved and enforced). A non-gate entry may not depend on a gate: gates never run, so it would block forever, and validation refuses it.parallel:optional (ops only). Seeops_parallel_isolated.note:optional free text, mostly for a gate: what your word triggers here.
Validation BEFORE the run (a hard stop on any violation): known lane, existing spawn
file for solo and ops, needs pointing at an existing id, no self-needs and no gate-needs, no
duplicate ids, no needs cycle, parallel on ops only.
Runner behaviour: resolve the needs order, drive solo and ops in plan order, and every
task writes ITS own report (from its spawn file) plus a task-<id>.status.json as its
last action. The runner adds a collected report, RUN.md, PROGRESS.md and a notification.
Exception stop unchanged. A typed run deliberately does not create a shared branch and
does not open a collected PR: every task manages its own git and PR through its spawn
file, and the gate entry refers to the PRs that are already open.
End-of-run hook (everything UP TO the merge, never the merge itself)
Runs only when ALL waves are green (a full run or a resume to the end; not for
--only single-wave tests, not after a stop).
- Commit check. Does the branch have commits ahead of the base? At 0 (every wave a no-op) there is no push and no PR; the log and the report note "no changes, nothing to merge".
- Push:
git push origin <branch_prefix>/<id>, only the feature branch, never the base. - PR:
gh pr create, titleauto-wave <id>: <N> waves, body one line per wave out of itsstatus.json(summary plus commit hash), ending in the do-not-merge warning. If the PR already exists (a resume), it is reused instead of duplicated. IfBOARD_CARDis set, the resolved card reference goes into the body as an anchor (see "Board card" below). - Fresh review (release chain step 1). The WHOLE PR diff goes to ONE throwaway
claude -pprocess with a fresh context, deliberately NOT the build context. Findings with a[HIGH]/[MEDIUM]/[LOW]prefix are posted as a PR comment; high findings are marked separately in the comment, the notification and the report. - Frontend review (release chain step 2). If the diff touches UI paths
(
frontend_review_globs), a SECOND throwaway process goes at the running stack: boot the app, click the touched surfaces for real. This is the class of check that catches a dead first-run path which green unit tests walk straight past. Findings go up as their own PR comment and high findings count like review highs. No UI in the diff means the step is skipped as n/a. Mobile is mandatory, not a bonus: the same surfaces are checked at the widths infrontend_review_widths(default 390, 375, 430 CSS pixels; the narrowest is your supported floor, anything below is out of scope). Four measured things instead of a feeling: no horizontal overflow when a LONG name is used instead of a short one · tap targets do not vanish under a dialog, a footer or the keyboard · sticky headers stay visible while scrolling · text is not squeezed into a few pixels. Screenshots land in the run's state directory. A pre-existing defect that also reproduces on the base branch is named AND marked as pre-existing, so it does not block the release by mistake. - Deploy handoff. Once the PR is open the hook writes a finished, deterministic (no
LLM) handoff to
state/<id>/SPAWN-deploy.md: a start line, the release-chain gate (mandatory before the merge: code-review comment present · frontend-review comment present or n/a · every[HIGH]fixed and RE-reviewed · key-function tests really executed with proof artefacts), the scope detected from the PR diff throughdeploy_handoff.scope_globs, and the forced order (backup, ONE human go, schema and server functions, merge = frontend LAST, verify), stop at any deviation, plus a mandatory closing report. This is a briefing, nothing else. auto-wave still merges and deploys NOTHING. On a stop or a no-op run, no handoff is written, because there is nothing to deploy. - Staging instead of a deploy prompt. If
deploy_queue_scriptis configured, the hook enters the run into that queue and ends there. Push, PR and both reviews stay unchanged (a PR is not a deploy, and without a PR there would be no review comments). What falls away is the "reply deploy, it goes live" line. Reason: collecting several finished runs into ONE review round and ONE deploy beats deciding once per run.
Best-effort contract: if push, gh, a review or the handoff fails, that does NOT crash
the run. Errors land in state/<id>/end-of-run.log and in the report, and the branch stays
usable locally.
A report at EVERY end of a run (green OR stop, including --only):
<project_root>/.scratch/pm-inbox/<YYYY-MM-DD-HHMM>-auto-wave-<run-id>.md with front matter
(topic / issue / pr / status: done|blocked / worktree) and, per wave, ✅ or ✋ plus
the summary, the branch, the PR number and link, whether the review found anything high.
That is the same inbox the pm skill reads.
Teardown at EVERY end (green, stop, crash; it hangs on atexit, not on the success
path): teardown_local_stack() stops the containers matched by teardown_docker_filter
again. docker stop, never docker rm, so no data loss. What a run brings up does not keep
running afterwards. Switch it off with "teardown_after_run": false.
Board card (optional adapter)
The runner knows nothing about any board. If the env var BOARD_CARD is set, it looks
for an executable adapter at <project_root>/scripts/board-card and calls it best-effort:
scripts/board-card find "<query>" -> prints ONE card reference
scripts/board-card built "<card>" --pr <n> --url <u> --branch <b>find resolves whatever you put in BOARD_CARD (an id, a short link, a title query) to the
stable reference that goes into the PR body. That anchor matters: a board watch can only
couple a card to a PR through a reference in the title, the body or the branch name, because
that is the only coupling that is a measurement rather than an interpretation. built moves
the card to whatever "built, waiting to be tested" means on that board. Board id,
credentials, column names and label vocabulary all live in the adapter, where the board
lives. A project without an adapter gets one line and is done. That is not an error: a
board being down is not a build failure and must never cost a green run. (Same cut as the
board watch in the pm skill.)
Preconditions (before every run)
claudeCLI on PATH and logged in.- The repository is on the base branch and has no uncommitted tracked changes (untracked is fine). The runner refuses otherwise.
- The queue file exists. Templates:
queue.example.md,queue.typed.example.md. config.jsonpointsproject_rootat your project. Everything else has a working default.
Calls
python3 ~/.claude/skills/auto-wave/scripts/run.py --status # queue plus last run
python3 ~/.claude/skills/auto-wave/scripts/run.py --dry-run # plan, branch, wave-1 prompt, do NOTHING
python3 ~/.claude/skills/auto-wave/scripts/run.py --run # real run (create the branch, drive the waves)
python3 ~/.claude/skills/auto-wave/scripts/run.py --run --name <id> --from <N> # resume after a stop, from wave N
python3 ~/.claude/skills/auto-wave/scripts/run.py --run --only <N> # wave N only (no push, no PR)--config <path> loads an alternative config file, which is what a sandbox test uses.
Progress: notifications plus PROGRESS.md (wave level)
The runner reports at every wave boundary, deliberately at wave granularity only, with no sub-wave steps (anything finer is spam):
- A notification through
notify(title, message): a desktop banner (macOSosascript, fallbackterminal-notifier) and, ifntfy_topicis configured, a phone push. Both fire independently and best-effort, so they NEVER crash the run. Exactly ONE notification per boundary: a wave turning green, the run finishing (with the PR number and the handoff path, or a warning when the review found something high, or "no changes, nothing to merge"), and, most important, the run stopping with the reason in the text. - A live checklist at
<state_dir>/<id>/PROGRESS.md, rewritten at every boundary: one line per wave, done- [x] … ✅ green (HH:MM), running▶ running (since HH:MM), stopped✋ stopped: <reason>, upcoming without a suffix. On a resume (--from N), waves with a green status file from the earlier partial run count as done. It is readable from any other chat, because it lives on disk.
How YOU (Claude) drive a run
- Straight to
--run, no dry run, no question. No plan preview for confirmation, no "say go" gate. The only hard gate is the preconditions the runner checks itself (on the base branch, clean tree, queue not empty); it refuses with a clear message when something is missing, and only then do you relay to the user instead of building blind.--dry-runand--statusstay available as manual tools when they are explicitly asked for, never as the default. The exception is BUILDING the queue: whoever assembles the queue asks which worker model the waves should run on, as multiple choice with a recommendation, and the recommendation depends on the work rather than defaulting to the strongest model everywhere. Heavy or risky waves (schema, permissions, anything close to production) deserve the strongest one; simpler waves (UI, content, docs) do not. The answer goes intoconfig.jsonas"model"and applies to the WHOLE run, so a mixed queue is better split into two runs. The start itself stays question-free. - Start the run in the background (long runtime, hours are possible). Tee it into a
durable log: the state on disk (
<state_dir>/<id>/RUN.md) is readable at any time through--status, including from another chat. - If the run stops (exit 2), relay the reason in plain language. For
needs-decision, put the question to the user as a multiple-choice question with a recommendation, work the answer into the wave (or extend the specs) and continue with--from N. The runner has already dropped astatus: blockedreport in the inbox, and there is no push and no PR after a stop. - If all waves are through, the end-of-run hook has already pushed, opened the PR,
posted the review comments, written the deploy handoff and filed the report. You relay:
the PR number and link, whether the review found anything high, and the handoff path.
If push or PR failed (best-effort), name the branch and the log; the branch is usable
locally. Never merge (
gh pr merge) and never deploy yourself.
Mode "full": the whole chain behind one word
No new runner. The mode chains what already exists: an auto-wave run (this skill), then a pre-deploy step as its own spawn, then your deploy skill. The green path without "full" is byte-unchanged; new are this section and
scripts/full-lock.sh.
Trigger: the user says "full auto-wave" in a fresh chat.
The contract
- The invocation IS the deploy go, given in advance and conditioned on the abort list below. The ONE human go your deploy procedure demands before the first production mutation counts as given by the invocation, AS LONG AS nothing on the abort list hit. Outside a full run, your normal per-deploy go stays exactly as it was.
- Conductor principle. The chat never works inline: waves run as fresh headless processes anyway, pre-deploy runs as its own spawn, and the deploy runs through your deploy skill as another spawn. Handovers happen only through files (queue, handoff, inbox). The conductor reads end reports, never diffs or logs in full, which is what keeps its context from filling up.
- Scope per run, measured rather than guessed. A preflight measures the real state:
open queue waves (
run.py --status) plus every merge-ready staged PR (gh pr list,gh pr checks). Everything green that is not blocked, parked or needs-decision comes along. What cannot come this time stays staged and joins the next run automatically. - Review findings are a hard gate. Fix in the PR branch first, re-run the gates with proof, re-verify the finding in the same medium, then continue. Not autonomously fixable (a product decision, a production-data touch, a guardrail) means a needs-decision stop. It is NEVER deployed around an open finding.
- The abort list. Every hit stops the chain BEFORE the next production mutation and
sends a push with the reason in plain language:
- a gate that is not green with proof (executed > 0, 0 skipped); self-attestation is red
- needs-decision, or a product decision of any kind
- a schema change or a function deploy that is not in the handoff
- a smoke test after the deploy that is not green: roll back, then stop
- a test-infrastructure-only PR (no deploy artefact): merge only, never deploy
- Every end is reported: a push in plain language (on a stop, the question itself, not just "stopped"), a PM inbox report, board cards pulled forward best-effort. Nothing is left running, and the lock is ALWAYS released, including on a stop or a crash.
- Never two full runs at once. Lock through
scripts/full-lock.sh acquire(atomicmkdir; the lock path comes fromAUTO_WAVE_LOCK). A fresh lock holding means stop with a message instead of a second run; a lock older than 24 hours is reported as stale and may be taken over after checking that nothing is really running. Same rule as "never two deploys of the same target".
Conductor protocol
- Lock:
bash ~/.claude/skills/auto-wave/scripts/full-lock.sh acquire. Refused means stop, with the lock age in the message. From here on, releasing the lock belongs to EVERY ending. - Preflight (contract point 3): measure the scope, write it down as a run note (what comes along, what stays behind and WHY). If the user is away from the machine, keep the machine awake for the duration of the run.
- Step A, waves: queue not empty means
run.py --runin the background (the section above applies unchanged, including the end-of-run hook). Queue empty means the step is skipped. A stop of the runner is an abort-list hit, so the chain ends there: no pre-deploy, no deploy. - Step B, pre-deploy as its own spawn: a fresh headless process runs the pre-deploy convention over the COMPLETE staged batch (including the fresh auto-wave PR): preflight against the forge, ONE collected review, the findings gate (point 4), the deploy handoff, board and inbox. The conductor then reads ONLY the handoff and the report.
- Step C, deploy: your deploy skill with the most recent handoff, as its own spawn. The human go is given in advance by the invocation (point 1) and the spawn is told so explicitly. ONLY what is in the handoff gets deployed; the smoke test afterwards is mandatory, and red means roll back and stop.
- The end (point 6): push in plain language, the conductor's own report (waves ✅/✋,
pre-deploy ✅/✋, deploy ✅/✋/skipped, PRs, what went live), board cards, teardown, and
full-lock.sh release.
Anti-scope of the mode: no building, fixing or reviewing in the conductor chat itself (all of it in spawns), no scope beyond what was measured, no deploy around the abort list, no second full run next to a running one.
Anti-scope (what the runner does NOT do)
It does not merge into the base branch (never gh pr merge), does not push to it, does not
deploy, does not make a product decision, and does not skip a wave when things are unclear.
(Pushing the feature branch, opening the PR, posting the review comments and writing the
handoff IS the end-of-run hook's job, and that is allowed, see the safety contract. The
handoff is a briefing, not a deploy.) The per-wave review (review: field) is done by
the wave itself; the end-of-run fresh review is an ADDITIONAL independent instance with
fresh eyes. Planning the waves is a different job: a wave without specs plans minimal issues
for itself at the start, but well-specified specs give noticeably better results.
Self-maintenance
When the call, the status protocol or the safety contract changes, update this SKILL.md and the README in the same pass. Findings from real runs (what breaks, what the user corrects) get appended here.
Learnings from real runs
- Collision with a parallel build in the same checkout. The runner owns
git_repo_subdirexclusively (one HEAD, one working tree). If ANOTHER build runs in the same checkout at the same time, itsgit checkoutor rebase tears HEAD away and the running wave builds on the wrong branch or commits into foreign work. Symptom: after--run,git branch --show-currentshows a FOREIGN branch instead of the wave branch. Fix: give the run its own worktree instead of the shared checkout (git worktree add, symlink the dependency directory, and a custom config with"git_repo_subdir"pointing at it plus--config <path> --run). Both builds then share only the.gitobjects, never HEAD. Numbers that have to be unique across branches (migration numbers, for instance) must be coordinated by hand across the parallel branches, because each branch only sees the remote base and both would otherwise grab the same next number. - A background run can die silently. A run started in the background was killed
externally while it blocked on its wave-1 worker: NO timeout (the wave timeout was hours
away), NO exit status to the caller, NO
RUN.mdorstatus.jsonwritten. Symptom: no process,PROGRESS.mdstill says "wave 1 running", the branch exists with 0 commits, but the wave-1 files sit in the working tree as uncommitted tracked changes, because the killed worker had already edited and never committed. Diagnose first: smoke-test a headlessclaude -pseparately, so you know whether the nested worker is the cause at all. Recovery is a fresh start, not a resume (0 commits means there is nothing to resume): check out the base branch, discard the half-finished, unreviewed worker diff (a diff out of a killed process has not earned any trust, and the value of this runner is the verified, reviewed, committed result), delete the 0-commit orphan branch, verify a clean tree and start again. On the restart, tee into a durable log, because a harness's temporary output can be cleaned up when it dies. - A queue can be older than the repository. A wave once found that its issue had already
been merged and verified that instead of blindly rebuilding it. Good behaviour, keep it.
The consequence is for whoever WRITES the queue: check every issue against the repository
state first (
git log --all --grep=<ref>,gh pr list --state merged), because a status file can lag behind.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

