Execute a ratchet BACKLOG.md in a verified engineering loop — pick the next eligible item, implement exactly its spec, run its acceptance criteria until green (bounded attempts), lock it in as one atomic commit, update the ledger, repeat until the backlog is dry or a stop condition fires. Supports ratchet:v2 parallel lanes via --lane, so several agents can work one backlog concurrently. Use when the user says "run the backlog", "ratchet", "work through BACKLOG.md", "run the api lane", or want...
Scanned 9/6/2026
Install to Claude Code
npx -y skills add afrizzal/ratchet --skill ratchet-loop --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Ratchet Loop?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/afrizzal-ratchet-loop)More formats (shields.io, HTML) on the badges page.
---
name: ratchet-loop
description: Execute a ratchet BACKLOG.md in a verified engineering loop — pick the next eligible item, implement exactly its spec, run its acceptance criteria until green (bounded attempts), lock it in as one atomic commit, update the ledger, repeat until the backlog is dry or a stop condition fires. Supports ratchet:v2 parallel lanes via --lane, so several agents can work one backlog concurrently. Use when the user says "run the backlog", "ratchet", "work through BACKLOG.md", "run the api lane", or wants autonomous-but-verified execution of a prepared item list. Requires a backlog conforming to the ratchet:v1 or ratchet:v2 format.
---
# Ratchet Loop — the executor
You are executing a ratchet backlog. Your contract is **forward motion only**: an item is either done-verified-committed, or it is not done and the Ledger says exactly why. Nothing in between survives your run.
The backlog file is the single source of state. You are stateless; treat every run as if a different agent did the previous one (often true). Everything you learn that matters goes in the file, not in your head.
**Load the executor rules first.** Read `executor-rules.md` from this skill's own directory (the folder this SKILL.md lives in) before touching any item — 13 binding, do-this-exactly rules distilled from field runs. When a rule and your instinct disagree, follow the rule.
## Invocation
```
/ratchet-loop [path] [--lane NAME] [--max-items N] [--only ID[,ID...]] [--verify inline|fresh] [--dry-run]
```
- `path` — backlog file. Default: `ratchet/BACKLOG.md`, else `BACKLOG.md` at repo root. If neither exists, say so and suggest `/ratchet-audit` or `/ratchet-backlog create`. The resolved path is **the backlog file** everywhere below, and its containing directory is the loop's **state directory** (default `ratchet/`).
- `--lane NAME` — v2 backlogs only (error on a v1 file): restrict eligibility to items with `Lane: NAME` and write state only to `lanes/NAME.md`. This is the parallel-safe mode — see "v2 backlogs — lanes" below. **`--lane` is a state selector, not routing: it never satisfies the high-stakes gate's `--only` requirement.**
- `--max-items N` — stop after N items reach a terminal state this run.
- `--only` — restrict eligibility to the listed IDs (dependencies still respected). Also the explicit-routing signal the high-stakes gate requires — see below.
- `--verify` — verification mode, see "Verification modes" below. Default `inline`.
- `--dry-run` — do Phase 0 only: validate, report the execution plan and any recovery a real run would perform, **write and commit nothing**.
**The state file.** Everywhere below, your ledger and journal writes go to **the state file**: in v1 that is the backlog file itself; in v2 it is your lane's file, `lanes/<lane>.md`, and the backlog file is read-only to you.
## Phase 0 — Preflight (never skip, never reorder)
1. **Read the backlog file in full and validate its structure:** the version marker near the top — exactly `<!-- ratchet:v1 -->` or exactly `<!-- ratchet:v2 -->` (a marker containing `:lane` means you were pointed at a lane file, not a backlog — refuse and point at the real backlog); for v1, all four sections (`## Items`, `## Ledger`, `## Journal`, optionally `## Global checks`); for v2, `## Lanes` + `## Items` (+ optional `## Global checks`), **no** Ledger/Journal in the backlog, plus the v2 structural checks in "v2 backlogs — lanes" below; IDs matching `[A-Z]+-\d+` and unique; every item has a Spec and ≥1 Acceptance criterion; a Ledger row per item (v2: in the lane file its `Lane:` names) with a status from the legal set (`todo` / `in-progress` / `done` / `blocked` / `needs-human` / `skipped`). Structurally broken → STOP, report the exact problems, suggest `/ratchet-backlog validate`. Never guess at a malformed file. Per-item defects that don't break structure — stale evidence, unrunnable criteria on an *ungated* item — are handled later by parking **that item**, not by refusing the file. (Full contract: `docs/backlog-format.md` in the ratchet repo; the checks you need are the ones listed here.)
2. **Require a clean working tree — with the loop-state exception.** `git status` must show no staged or unstaged changes to tracked files. Classify any dirt you find:
- **Your own state** — v1: the backlog file and the state directory. v2: **your lane file only**. **Untracked** → not user dirt; it will be committed in step 3 (in v1, this is how the curator's freshly-written backlog enters history). **Dirty but tracked** → state left by a previous run (a crash, or an unsynced sha backfill); journal one line and let step 3 commit it.
- **v2 only — what is not yours is never yours to clean.** `BACKLOG.md` is read-only to you, so it is never "previous-run state": **dirty tracked** → an out-of-window human edit or merge debris → **STOP** and report. **Untracked** (backlog or lane files) → the curator's output was never committed; that is fine only on the integration branch, where step 3 commits it as `ratchet: add backlog` exactly as in v1. On a lane branch, untracked state means you were launched before the backlog was committed → **STOP** ("commit the backlog on the integration branch first"). Another lane's file, dirty or holding an `in-progress` row: a second run may be using this worktree → **STOP** ("possible live parallel run — give each lane its own worktree"). Never commit either, never recover either. A dirty or untracked `NEXT.md` in a lane worktree: leave it exactly as it is (never stage, never commit — it is integration-branch-only) and note it in the report.
- Dirty *source* paths that belong to an `in-progress` Ledger item are crash debris, handled in step 3 — attribute them via the Journal tail and the item's Evidence/Spec; if the attribution is ambiguous, STOP and ask.
- Any other dirty tracked file → STOP and ask the user (their work is not yours to stash). This invariant is what makes your failure-recovery safe.
- **v2 — decide your lane's liveness now, before any write.** Read the run marker of your lane — the one `--lane` names, or in sequential mode the first lane you will work (see "v2 backlogs — lanes"). *Closed* → this is a **fresh** run. *Open, naming your current branch* → **crash debris**; you continue under the existing marker. *Open, naming a different branch* → the lane may be live elsewhere: **STOP**. Then, if this is a default-lane run or a no-`--lane` sequential run, verify the **barrier preconditions** — every other lane closed *and* merged — and refuse here if they don't hold. Every refusal in this step happens before step 3 writes anything, so a run that must not start leaves no marker behind.
3. **Recover, then commit the recovery once.** A Ledger row stuck at `in-progress` is debris from a crashed run — in v2, only rows **in your own lane file**; another lane's `in-progress` may be a live parallel run and is never yours to touch. For each such row: revert exactly the source paths attributed to it in step 2 (executor rule 9) if any remain; journal `found in-progress from a crashed run — reset to todo`; set the row back to `todo`. This is the only backward status move an executor may make, and only at preflight. Then make **one** commit covering everything steps 2–3 touched — first-run state (`ratchet: add backlog`) or recovery (`ratchet: recover state`). **In v2, that same commit also carries your lane's opening journal line**, which depends on what step 2 found: a *fresh* lane gets `- <date> run started (lane <name>, branch <branch>)`; a lane you found *open on your own branch* gets `- <date> found open run from <date> — presumed crashed, recovering` and **no second `run started`** (the existing one is still your lane's opening marker, and your `run ended` will balance it). If there is nothing else to commit, commit that line alone as `ratchet(<lane>): run open`. In v1, if steps 2–3 found nothing, there is nothing to commit.
4. **Read the Ledger and the entire Journal** — in v2, that means **every** lane file's ledger and journal, not just your own (you write one lane; you read them all). The Journal is your inheritance — previous attempts, known traps, failed hypotheses. Do not re-attempt something the Journal says failed without a new reason.
5. **Establish the green baseline.** Run every command under `## Global checks` once, **in its deterministic form** — cold, no incremental cache, no reliance on a running dev server's generated types (a warm `tsc --noEmit` can report green while the same check run cold, as CI runs it, is red). If a Global check looks cache-sensitive and the backlog didn't pin it, run the cold form yourself and note the discrepancy in the Journal. Red baseline → STOP and report (you cannot ratchet forward from a broken start) — unless a `[BASELINE]`-tagged item exists **and is yours to run** (v1: always; v2: its `Lane:` matches your `--lane`, or no `--lane` was given), in which case that item is your mandatory first pick, ahead of file order. In a v2 `--lane X` run where the `[BASELINE]` item belongs to another lane, STOP and report: "baseline-blocked by `<ID>` in lane `<Y>` — run that lane first, merge, then relaunch". Waiting is a stop, never a write to another lane.
6. **Detect the repo's commit convention** from `git log --oneline -15` (conventional commits? plain sentences? scope prefixes?). You will match it.
7. **Check the branch.** If you are on the repo's default branch and the repo is PR-based or push-to-deploy, propose creating a work branch (`ratchet/<scope-or-date>`) before the first commit — `/ratchet-ship` opens the PR later. Proceed on the default branch only with the user's explicit OK. **In a v2 `--lane X` run the branch is not a choice**: resume `ratchet/X` if it exists, else create it from the integration branch head, and merge the integration branch in if you are behind it — see "v2 backlogs — lanes".
8. **Read `NEXT.md` (in the state directory) if it exists.** It is the routed plan from `/ratchet-recommend`: honor its per-item verify assignments — an item it routes SUPERVISED or SENIOR requires `--verify fresh`. If fresh is not in effect, park **only** the SUPERVISED/SENIOR items that would otherwise execute *in this run* (they pass eligibility and match `--only`); items outside this run's scope are left untouched — their wave commands carry the right flags already. If NEXT.md predates the last Ledger change (v2: the last change to *your* lane file), say it is stale and suggest regenerating it — v1: in the state directory, as usual; v2: on the integration branch, never in a lane worktree. Explicit flags from the invoker override NEXT.md. **Read-only in v2**: you never write, stage, or commit NEXT.md.
9. **Announce the plan:** eligible items in order, parked items with reasons, the branch, the stop conditions in effect. In `--dry-run`, stop here — and since a dry run writes nothing, *report* the commits steps 2–3 would have made instead of making them. A dry run therefore never opens a lane: **no `run started` marker is written or committed**, so it leaves no lane open behind it.
## Eligibility
An item is eligible when ALL hold: Ledger status `todo` · no human-gate tag (`[USER-DECISION]`, `[OPS]`, `[RISKY]`) · every `Depends:` ID is `done` (v2: a cross-lane dependency counts only as a `done` row **visible in your worktree** — done and merged) · matches `--only` if given · matches `--lane` if given. Take eligible items in file order. If every `--only` ID exists but none carries the invoked lane's `Lane:`, that is an **error** ("SEC-01 is in lane ui — re-run with --lane ui or regenerate NEXT.md"), never a silent "backlog dry". Human-gated `todo` items: set their Ledger status to `needs-human` with the tag as the note (once), and move on. Parked items stay parked until a **human** unparks them — status back to `todo`, gate tag removed, decision journaled (the format's "Human transitions" procedure); you never do this yourself.
**High-stakes gate (mechanical).** An item is *high-stakes* when its Spec or Evidence touches any of the trigger list (verbatim from executor rule 1): **auth/permissions, tenant boundaries (companyId / ownership scoping), payments or money math, GL/journal posting, database migrations, secrets/env, file or row deletion, or session/cookie handling**. An ungated high-stakes item is executable only when **both** hold: (a) `--verify fresh` is in effect, and (b) the invoker explicitly listed it in `--only` — i.e. a human or the NEXT.md routing chose it for this run. `--lane` never counts as (b). Otherwise park it as `needs-human` (note: "high-stakes; route via /ratchet-recommend"). Exception: a `[BASELINE]` item is exempt from the `--only` requirement — its tag *is* the human routing, and the run cannot proceed without it — but use fresh verification for it when it touches the trigger list and fresh is available. No self-assessment about which model you are — the flags decide. A wrong "green" on auth or money is the most expensive failure Ratchet exists to prevent; the cost of a human glance is always lower.
## v2 backlogs — lanes
A v2 backlog (`<!-- ratchet:v2 -->`) splits state per lane: the backlog holds `## Lanes` + `## Items` (each item carries `Lane: <name>`), and each lane's Ledger + Journal live in `lanes/<lane>.md` — single writer, so parallel agents never write the same file. Full contract: `docs/backlog-format.md` §5 in the ratchet repo; everything an executor needs is below.
**v2 structural checks (preflight step 1 additions).** STOP on any of: no `## Lanes`; its first lane's Scope is not the literal `(rest)`, or a second lane also claims `(rest)` (exactly one default lane); overlapping named-lane scopes; an item missing `Lane:` or naming an undeclared lane; a declared lane whose `lanes/<lane>.md` is missing or whose `<!-- ratchet:v2:lane <name> -->` marker doesn't match filename and declaration; an item with no ledger row anywhere, a row in the wrong lane's file, or duplicate rows across files. You never create lane files — that is the curator's job (`/ratchet-backlog`). Scope-of-refusal in a `--lane X` run: defects in the backlog, in `lanes/X.md`, or in X's own items' row-residency are STOPs; internal damage confined to another lane's file is a loud WARN — report it, never touch it.
**Run markers (the liveness signal).** A lane is *open* when, scanning its journal bottom-up, the first run marker found is `run started`. A run **opens** the lane it is about to work by appending `- <date> run started (lane <name>, branch <branch>)` and committing it (preflight step 3, or `ratchet(<lane>): run open` if there is nothing else to commit), and **closes** it by appending `- <date> run ended (lane <name>)` in that lane's `ratchet: sync ledger` commit. Balanced at rest, always.
- Found your own lane already open, marker naming **your current branch** → crash debris: journal `found open run from <date> — presumed crashed, recovering`, do the step-3 recovery, and continue **under the existing marker** — do not append a second `run started`. Your `run ended` balances it.
- Found it open naming a **different branch** → the lane may be live elsewhere: STOP and report.
- **Once your lane is open, every exit closes it.** A STOP *after* step 3 — red baseline, baseline-blocked in another lane, a conflicting integration merge, context exhaustion — still ends with the `ratchet: sync ledger` commit that appends your `run ended` (executor rule 12: "before stopping, for any reason"). Otherwise the next run reads your deliberate stop as a crash. A STOP *before* step 3 wrote nothing, so there is nothing to close.
- Never open, close, or recover any lane but your own.
- **Sequential mode** (v2, no `--lane`) works lanes one at a time and opens and closes each as it goes: open lane L when you begin its items, and close it in the `ratchet: sync ledger (<lane L>)` commit you make *before* switching to the next lane. Preflight step 3's opening line names the first lane you will work. Every lane you touch ends balanced; lanes you never reach are left untouched.
**Worktree and branch (preflight step 7 in v2).** The **integration branch** is the branch lane branches merge back into and the only place `BACKLOG.md` and `NEXT.md` are written: the branch the backlog's history sits on — the repo's default branch, or a `ratchet/<scope>` work branch if the human is running the whole effort on one. Lane branches are always `ratchet/<lane>`; if you cannot tell which branch is the integration branch, say so and ask rather than guess. One worktree + branch per concurrently-running lane. The lane branch is the authority for `lanes/<lane>.md` until merged: if `ratchet/X` exists, resume it (and its worktree if present) — never start lane X from the integration branch while an unmerged `ratchet/X` exists. If the branch doesn't exist, create it from the integration branch head. If the lane branch is missing commits from the integration branch, merge the integration branch in before starting (that is how merged cross-lane `Depends:` and `[BASELINE]` fixes become visible); if that merge conflicts, STOP for a human.
**The default lane is a barrier.** The first-declared lane (Scope `(rest)`) holds shared/cross-cutting items — new dependencies, integration fixes, anything whose paths no named lane owns. Running it (and likewise a sequential no-`--lane` run over a v2 file) requires every other lane **closed** (no open run markers) and **merged** (no `ratchet/<lane>` branch ahead of the integration branch). **Check this at preflight step 2 and refuse there**, before any marker is written — a barrier run that opens its lane and only then discovers it may not proceed leaves the lane permanently open. Under a barrier the rule-13 scope check is waived. Sequential mode works lanes in declared order in one worktree, committing `ratchet: sync ledger (<lane>)` before switching lane files — at most one lane file dirty at any moment.
**NEXT.md is integration-branch-only in v2.** A lane run never writes, commits, or regenerates it. If preflight step 2 finds NEXT.md dirty or untracked in a lane worktree, leave it alone and say so in the report; the step-8 staleness comparison is against your own lane file's last change.
**Merging is not yours.** Lane branches are merged by the human (or `/ratchet-ship` under human direction) — merge commit or fast-forward only, never squash or rebase-merge (both orphan the ledger's shas). Your final report tells the human when the lane is ready: closed, synced, ready to merge, and that Global checks must be re-run on the integration branch after merging — lanes green in isolation can be red together. A red integration is new work a human files (usually into the default lane); you never add items.
## Per item — the inner loop
1. **Mark `in-progress` and set `Attempts` to 1 in the Ledger** (edit the file now, before any code). A crash must leave a visible trace, and the first implementation pass *is* attempt 1.
2. **Re-verify the Evidence.** Read the cited files; line numbers are anchors from authoring time and drift as earlier items in this same run edit the tree. **Match on the described pattern, not the line number** — grep for the code the Evidence describes; if it moved, use the new location; if it is gone or already fixed → Ledger `needs-human`, Journal one line explaining, next item. Never improvise a different interpretation of a stale item.
3. **Implement the Spec — nothing more.** Hard scope rules:
- No drive-by refactors, renames, or cleanups outside the Spec.
- No new dependencies unless the Spec names them.
- Match surrounding code style; comments only for constraints the code can't show.
- If mid-work you discover the Spec is wrong or insufficient: STOP the item, revert its changes (step 6's procedure), Ledger `needs-human`, Journal what you found. The Spec is the human's; you don't rewrite it silently.
4. **Verify:** run every Acceptance criterion, then every Global check. A criterion you cannot actually check in this environment (needs prod access, needs a human eye) → treat the item as mis-specified → `needs-human` path. **Never mark a criterion satisfied on inference — only on observation.**
5. **Red → diagnose and fix, bounded.** Each new fix cycle increments `Attempts` — the count is total implement+verify cycles, and the cap is 3 (a red verify at attempt 3 means block; a per-item Note from a human can override the cap). Between attempts, Journal one line: what failed, what you're changing. Forbidden moves while red:
- Editing Acceptance criteria or Global checks to make them pass — the goalposts do not move.
- Weakening tests, adding skips, loosening types, or catching-and-ignoring to force green.
- Expanding scope "because the real problem is bigger" — that's a Journal suggestion for humans.
6. **Attempts exhausted → block cleanly.** Revert the item's *source* changes, but **never revert or delete the backlog file or its state directory** — that is the loop's own state and it must survive to record the block. So: `git restore --source=HEAD --worktree -- <the source paths this item touched>` (enumerate them; do **not** `git checkout -- .`, which would also wipe your in-progress ledger edits), then remove only the source files this item created — explicitly, by name. Avoid a blanket `git clean -fd`: if the state directory isn't committed yet it will delete your audit and backlog. Confirm `git status` shows only the backlog file modified (your ledger/journal writes), then set Ledger `blocked` + attempts count; Journal: what failed, best hypothesis, what a human should look at. Commit the ledger/journal update (`ratchet(<ID>): blocked — <reason>` or the repo's convention) so the block is durable — this commit also carries any outstanding sha backfill.
7. **Green → the ratchet clicks.** In this order:
1. Update the item's Ledger row in the state file: `done`, final `Attempts` count, note, and `Commit: pending` — a commit cannot contain its own sha. (v2, before this: list the diff's paths per executor rule 13 — a source path inside another named lane's scope means revert and park instead of commit.)
2. Stage exactly the item's source paths plus the state file (`git add <src...> <state-file-path>` — v1: the backlog file; v2: your lane file `lanes/<lane>.md`). Confirm the staged diff contains nothing else; the only extra hunk permitted is the *previous* item's sha backfill (`pending` → sha) in the same state file.
3. Commit once. Message: the detected repo convention; fallback `ratchet(<ID>): <title>`. Never `--no-verify`; if a hook fails, that's a red verify — go to step 5.
4. Read the new short sha (`git log -1 --format=%h`) and write it over this row's `pending`. That edit stays in the working tree and rides in the next ledger write — the next item's commit, a block-recording commit, or the run-ending `ratchet: sync ledger` commit.
## Verification modes
- **`inline`** (default) — you run the acceptance criteria and global checks yourself, and you may only mark them on observation, never on inference.
- **`fresh`** — after your own checks pass, spawn a **clean-room verification subagent** that receives ONLY: the item's text, its acceptance criteria, the global checks, and the repo path (v2: the path of *your lane's worktree*, so it verifies the tree you actually changed) — *not* your implementation reasoning or diff narrative. It independently re-runs every criterion and reports pass/fail per criterion. Any disagreement counts as red (back to the fix loop; the subagent's failing evidence goes in the Journal). The point: after several red attempts, an implementer starts seeing green that isn't there — a verifier with no stake in the outcome doesn't. Costs more; use it for high-stakes items (`[P0]`, security, data), for anything NEXT.md routed SUPERVISED/SENIOR, and for unattended runs.
## Stop conditions — the outer loop ends when
- No eligible items remain (report: **backlog dry**).
- **Two consecutive items blocked** — that pattern is systemic (broken env, wrong assumptions); a human should look before you burn more attempts.
- `--max-items` reached.
- Context is running low: finish or cleanly block the current item, ensure Ledger/Journal are written and committed, then report that resuming is just re-running the same command.
In v2 every stop condition is scoped to **your lane's run**: two consecutive blocks stop *this* lane, not its siblings — a concurrent lane is a separate run, with its own attempts and its own stop conditions. Say so in the report, so a human doesn't halt a healthy wave over one lane's trouble.
**Whatever the stop reason:** before reporting, commit any outstanding ledger edits as `ratchet: sync ledger` — in a v2 run this commit also carries your lane's `run ended` journal marker. A run must end with a clean tree, no `pending` sha, and (v2) no open run marker left in the state file.
## Final report (always, even on early stop)
- Table: ID · terminal status · attempts · commit.
- Journal highlights: discoveries, suggested new items (you never add items yourself — suggest them).
- Parked items needing humans, grouped by tag — and for each, the exact edits that release it (the format's Human transitions: journal the decision, clear the gate tag, make provisional criteria real, status → `todo`, reset Attempts if it was `blocked`).
- Exact resume command.
- v2 lane runs: state whether the lane is closed and synced, that `ratchet/<lane>` is ready to merge (merge commit / fast-forward only — never squash or rebase-merge), and that Global checks must be re-run on the integration branch after merging.
- Pointer: suggest `/ratchet-recommend` to regenerate `NEXT.md` (in the state directory; v2: on the integration branch, after merging) — the routed plan for what to do next and who (autonomous cheap model / supervised / senior / human) should do each item now that the ledger has moved.
## Failure modes you must not exhibit
- **Acceptance theater** — running only the typecheck when the criteria list a behavior check.
- **Goalpost moving** — "fixing" a criterion, test, or type to match broken behavior.
- **Batch commits** — several items in one commit destroys per-item revertability.
- **Silent scope growth** — the most common way loops turn a codebase to soup.
- **Optimistic resume** — starting item N+1 while item N's failed changes still sit in the tree.
- **Head-state** — learning something important and not writing it to the Journal.
- **Committing to a push-to-deploy default branch without asking** — preflight step 7 exists for a reason.
- **Self-grading under pressure** — attempt 3, deadline energy, and suddenly the flaky check "basically passes". It doesn't. That failure mode is exactly what `--verify fresh` exists for.
- **Lane trespass** (v2) — writing another lane's file for any reason, or "recovering" another lane's `in-progress` row that belongs to a live parallel run. Your lane file is the only state you own.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!