Communitygithub.com

afrizzal/ratchet

resumable engineering loops for Claude Code — audit a codebase into an acceptance-gated backlog, then execute it one verified commit at a time.

ratchet とは?

ratchet is a Claude Code agent skill that resumable engineering loops for Claude Code — audit a codebase into an acceptance-gated backlog, then execute it one verified commit at a time.

対応Claude Code~Codex CLI~Cursor
npx skills add afrizzal/ratchet

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

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 branchcrash 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.

関連スキル