Skip to content
Skillv1.0.0

backlog-loop

Clear a repository's Beads backlog autonomously in budget-sized batches by driving the current compound-engineering:lfg stages through their supported child-skill seams, with CI-aware merge gates. Exp

by vinhnxv(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from vinhnxv/skills (skills/codex/backlog-loop/SKILL.md). Install upstream with npx skills add vinhnxv/skills --skill backlog-loop. Copyright stays with the author.

Clear this repo's issue backlog autonomously, one batch at a time, until CENSUS below shows no issue left in a category this loop is responsible for. An empty bd ready list is never that proof. Never ask the user anything; when a choice arises take your recommended option and record it.

A batch is 1..N sibling issues sized against a complexity budget, so one LFG-compatible pipeline is neither a wasted trip for one 15-minute chore nor an unreviewable multi-hour PR. One batch = one plan = one branch = one PR = one merge.

This procedure is the outer loop and shipping authority. For every batch, read the available compound-engineering:lfg skill and drive its current stages through the documented ce-plan, ce-work, review, browser-test, ship, and babysit seams. Do not invoke LFG as an indivisible child: the loop must retain control at the plan boundary to resize the batch, and must be able to replace an unavailable CI watch with local gates. Resolve every named child skill against the host's available-skills list and use host-native invocation; never guess slash or dollar syntax. Never bypass a child skill's applicable gates.

PREFLIGHT

Resolve from the repo, report once, then start. Stop if a REQUIRED item won't resolve.

Choose a unique <run-id> first (an ISO UTC timestamp is enough): the process-hygiene item below stamps it onto every command this loop launches, and the tracker marker below carries it.

  • Child skills (REQ): resolve every skill this procedure invokes against the host's available-skills list -- compound-engineering:lfg, compound-engineering:ce-plan, compound-engineering:ce-work, compound-engineering:ce-simplify-code, compound-engineering:ce-code-review, compound-engineering:ce-test-browser, compound-engineering:ce-doc-review, compound-engineering:ce-commit-push-pr, compound-engineering:ce-babysit-pr. Any name that does not resolve -> stop before any other item, and name every unresolved skill in the stop report so it reads as a missing prerequisite rather than a tracker or forge failure. Resolve first: this is the cheapest check here and the only one whose failure would otherwise surface at step 5, after issues are claimed and a branch is cut.
  • Tracker (REQ): bd prime and bd ready must both work. Anything else -> stop, naming Beads as the missing prerequisite. Every step below issues literal bd commands for claims, estimates, metadata, notes, and closure; there is no supported mapping onto another tracker, and improvising a partial one leaves tracker state half-mutated at the first unmapped operation with no defined rollback. When repo instructions make Beads the sole task tracker, treat any platform-local task-list capability requested by a child skill as unavailable; do not create a second source of task state.
  • Sync: bd dolt push only if a dolt remote exists; else skip.
  • Forge (REQ): gh authed on this repo. Not GitHub -> stop. Resolve <remote>: the git remote whose fetch URL names the same owner/repo that gh repo view --json nameWithOwner reports. No remote matches, or more than one does -> stop. Also stop when the matching remote is not named origin: ce-commit-push-pr's pinned contract pushes with a literal git push -u origin HEAD, so a correctly resolved upstream would pass every check here and then fail at shipping with the batch already claimed and built. Resolving the remote rather than assuming it is still what makes the mismatch a preflight stop instead of a late failure, and it is what keeps <remote>/<default> correct if a checkout has several GitHub remotes. Use <remote> for every fetch and every <remote>/<default> reference below.
  • Default branch (REQ): gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name'. Require one non-empty unqualified branch name such as main, never origin/main and never a hardcoded literal.
  • CI availability (REQ, non-fatal): refresh remote refs with git fetch <remote> --prune, resolve <default-sha> with git rev-parse <remote>/<default>, then enumerate EVERY run for that exact SHA - gh run list --branch <default> --commit <default-sha> --limit 100 --json workflowName,conclusion,status,createdAt,headSha - and reduce it to the latest attempt per workflowName. A commit carries one run per workflow, so reasoning about a singular newest run lets a fast lint workflow speak for a slow test workflow that is still running or already red. CI is UNAVAILABLE if any of: no run exists for that exact SHA; every matching run concludes startup_failure; or the Actions API returns a billing, quota, or spending-limit error. Record the initial observation, but step 1 re-probes every current trunk SHA because a backlog batch can add or repair CI. UNAVAILABLE is expected and pre-authorized; never ask the user to fix billing.
  • Merge capability (REQ): query gh api repos/{owner}/{repo}/branches/<default>/protection. 404 -> unprotected. A 403 is unprotected only when the response exactly says branch protection requires a paid plan or public repository; any other 403 is an authorization failure and stops. On 200 inspect .required_status_checks: none -> direct merge works; required checks with initial CI available -> continue because the pipeline waits to CI-decided and step 6 requires green; required checks with initial CI unavailable -> stop because they can never turn green. If a later batch resolves <batch-ci>=off while required checks exist, stop before claiming it. Merge queue is a separate query, because it is not in this response: gh api --paginate --slurp repos/{owner}/{repo}/rules/branches/<default>, [] when no rule applies. Paginate and flatten: the endpoint defaults to 30 per page, and an organization contributing rules alongside the repository can push merge_queue onto a second page that an unpaginated read never sees. Any entry whose type is merge_queue -> stop. On such a branch gh pr merge enables auto-merge or enqueues the PR instead of merging it, which step 6 forbids and cannot verify. This endpoint carries the SAME paid-plan carve-out as the protection query above and for the same reason -- measured 2026-09-04, a private repository under a free organization plan answers 403 Upgrade to GitHub Pro or make this repository public on /protection, /rulesets and /rules/branches/<default> alike -- so a 403 whose message is exactly that reads as no merge queue, because a merge queue is configured only through branch protection or a ruleset and neither can exist on that plan and visibility. Any other 403, and any other response, stops. Never pass --admin.
  • Drop --delete-branch if the repo forbids branch deletion.
  • Quality gate set (REQ): read CLAUDE.md/AGENTS.md/CONTRIBUTING.md/README at the commit being gated, with git show <sha>:<path> or from inside <clean-tree>, never from the working tree. Isolating where a gate RUNS while selecting it from a checkout that deliberately keeps <excluded-paths> dirty leaves the whole point undone: an uncommitted edit to AGENTS.md swapping npm test for npm run smoke makes the loop record and run the weaker command against the exact commit and merge code the committed gate rejects. Re-resolve the set separately for the trunk SHA, the batch head, and the merge commit. Record every unconditional gate plus every path-conditional gate. Else use the project's test command. Do not flatten a conditional gate into one command or omit it. For example, a repo declaring one command that runs on every change plus a second that runs only when changed files touch its data-access packages contributes two gates, not one: record the conditional gate together with the paths that trigger it, and evaluate that trigger against each batch's actual changed files. Child verification, post-merge verification, and the final report use this same gate set.
  • Gate coverage: on the first <batch-ci>=off observation, read both .github/workflows/*.yml and .github/workflows/*.yaml when present. List every command each job runs and compare it against the full gate set. Report any command CI would have run that the gates do not. While <batch-ci>=off such a gap is not informational: nothing else verifies that command, so every uncovered workflow command joins the gate set actually run, and one that cannot be run locally stops the batch rather than merging unverified. Re-resolve the whole gate set whenever a batch changes CLAUDE.md/AGENTS.md/CONTRIBUTING.md/README or anything under .github/workflows/; a catalog read once at preflight goes stale the moment the loop edits the files that define it.
  • Process hygiene (REQ): record <cores> from sysctl -n hw.ncpu, or nproc where that is the platform's command. Every gate, test, build, or app command this loop runs is launched non-interactively (CI=1, watch and UI modes off), inside its own process group, and under a hard deadline <gate-timeout>=20m: perl -e 'setpgrp(0,0); exec @ARGV' -- timeout -k 30s <gate-timeout> <command>. Where timeout is absent, keep the process group and enforce the deadline by killing that group. Export BACKLOG_LOOP_RUN=<run-id> into every such command so ownership stays provable after a runner dies. Append every launched leader PID to <owned-pgids>, the set the step 1 machine guard resets each iteration. Never leave a watcher, dev server, or REPL alive past the command that needed it; a command that returns while its group still exists is a reap target, not a success.
  • Constraints: read the repo's CLAUDE.md/AGENTS.md non-negotiables; else its documented conventions.
  • Worktree safety: inventory git status --short before any branch move and save every pre-existing dirty path as <excluded-paths>. Carry the list as context through planning, simplification, and review-fix commits; when non-empty, pass the documented exclude:<comma-separated-paths> carrier to ce-commit-push-pr. Do not pass that carrier to a child that does not document it: ce-work inventories pre-work WIP itself and must block on collisions. Verify excluded paths remain uncommitted before every push. Never reset, clean, stash, or use a tree-wide checkout to make the tree look clean. If planned work collides with an excluded path, stop the batch rather than absorbing or discarding it.
  • Owner-decision issues: find by content (body asks for a product or design call), not by hardcoded id. The scan itself does NOT run here -- it runs in ITERATION step 2, immediately after that iteration's CENSUS, over the issues that census classified as neither human-gate nor label-defect. It cannot run in preflight because no census has classified anything yet, and a content scan that runs first is exactly how a gate asking for a production approval gets read as a product call and shipped. ## HUMAN GATES owns why, and the order is not optional.

CLEAN-TREE GATE RUN

Every gate whose result authorizes a merge -- the step 1 trunk gate, the step 5 pre-PR gate, and step 7 post-merge verification -- runs against a throwaway worktree at an exact commit, never in the working tree.

The working tree deliberately keeps <excluded-paths> dirty and is forbidden from cleaning them. Subtracting those paths from a changed-file list keeps them out of the PR; it does not keep them out of the build. A dirty config, stub, or source patch sitting beside the code is loaded by the very gate that decides whether to merge, so a green result there says nothing about the commit that ships.

  • <clean-tree> is <worktree-root>/<sha>, where <worktree-root> is a single run-owned directory outside this repository named after <run-id>, recorded once in the ledger as backlog_loop_worktrees. A path invented per gate has no identity after a crash: the worktree stays registered, its installed dependencies stay on disk, and no later run can tell which registered worktree was whose. git worktree add --detach <clean-tree> <sha>.
  • If the gate set needs installed dependencies, run the repo's documented install or bootstrap command inside <clean-tree> first, preferring its frozen/locked form. Then require git -C <clean-tree> status --porcelain to report no change to a tracked file. A bootstrap that regenerates a checked-in artifact turns the gate into a test of locally regenerated content that is then deleted with the worktree, while the stale committed version is what merges. Any tracked change -> the gate has not passed. A gate that cannot execute there has not passed either, and neither may be recorded as green.
  • Run the gates there under the same process-hygiene rules, including the run token and <owned-pgids>.
  • git worktree remove --force <clean-tree> and git worktree prune when they finish, on the green path and the red path alike.

Tag every claimed member with bd update <id> --set-metadata backlog_loop_run=<run-id>; remove that metadata from members dropped before implementation. Final reporting queries this run marker instead of scanning all historical closed issues.

Do not resolve a plans directory here. ce-plan, under LFG's plan-brief.md contract, resolves its own artifact root and reports the path it wrote; step 5 uses that reported path and nothing else.

BATCH BUDGET

TARGET   = 90 minutes of estimate   # fill a batch toward this
CEILING  = 150 minutes              # never admit a member that crosses this
CONCERNS = 4 members maximum        # review cost scales with concern count, not only size

Estimates come from the tracker's own field (bd show --json -> estimated_minutes). When an issue has none, derive one from this rubric and write it back with bd update <id> --estimate <minutes> so the next session starts from data instead of a guess:

Shape of the issue Minutes
Docs/config/copy only, one file 15
Bug with a clear repro, one module 30
Task with 1-3 acceptance criteria, one module 45
Feature, or anything crossing modules 90
Vague, or touching auth/payments/migrations/external contracts 120, and treat as solo

STATE

State lives in the tracker. Re-read bd ready every iteration; never work from a remembered list. Also inspect non-epic in_progress issues and split them by the PRESENCE of backlog_loop_run metadata, never by its value. A fresh <run-id> is chosen every invocation, so an issue left behind by an interrupted earlier run can never carry the current one; matching on the value would file this tool's own abandoned claims as somebody else's and wedge the loop permanently on the first crash.

  • Marker present: an earlier run of this procedure claimed it and did not finish. Recover it through THE RUN LEDGER below.
  • Marker absent: externally owned. Never steal it. If no ready work exists and one of these remains, stop and report that the backlog is not clear.

That split is an ownership test and CENSUS extends it into a full accounting of every non-closed issue. Read CENSUS before deciding that anything is finished.

THE RUN LEDGER

Every fact a resumed run needs lives in tracker metadata on the batch's own members, because that is the only store that survives the process dying between two commands. Write these with bd update <id> --set-metadata for every member, at the moment the phase is reached and before the action the next phase depends on:

key written at value
backlog_loop_run CLAIM <run-id>
backlog_loop_phase each transition claimed, built, shipping-requested, pr-open, merge-requested, merged, or verified
backlog_loop_heartbeat CLAIM, every iteration step 1, and before CENSUS's first write current ISO UTC time
backlog_loop_ci step 1, before any gate on or off, the route this batch was gated under
backlog_loop_worktrees first CLEAN-TREE GATE RUN <worktree-root>
backlog_loop_base BASE <batch-base-sha>
backlog_loop_branch pipeline step 6 the branch the work sits on
backlog_loop_head pipeline step 6 <batch-head-sha>, the gated commit
backlog_loop_pr pipeline step 7 the PR URL
backlog_loop_merge step 6 <merge-sha>, once the PR reports MERGED
backlog_loop_cause every blocked write, and CENSUS backfill needs-person, transient:trunk-moved, or transient:merge-precondition
backlog_loop_attempts every blocked write count of blocks this issue has taken, incremented never reset
backlog_loop_census CENSUS, on every issue it writes to <census-run>
backlog_loop_census_heartbeat CENSUS, before each mutation the census token and the ISO UTC time, so one census's heartbeat can be told from another's
backlog_loop_quarantine CENSUS gate repair the census token and time that freed the issue
backlog_loop_edge_removed CENSUS gate repair space-separated ids at the far end of every edge removed from this issue; the records themselves are notes
backlog_loop_edge_ack AUTHOR ONLY, never this procedure space-separated dependent ids whose removal a person has acknowledged

LIVENESS, before any recovery. A marker proves an earlier run claimed the issue; it does not prove that run stopped. Read backlog_loop_heartbeat: less than 30 minutes old, or any live process carries that run's BACKLOG_LOOP_RUN token -> another invocation is still working. STOP and report it. Do not reap, reclaim, retire a PR, or claim anything. Two overlapping scheduled invocations would otherwise each treat the other as wreckage, kill its running gates, close its PR, and rebuild its work while it is still moving toward its own merge.

RECOVERY, by the recorded phase. Read it before touching anything; the whole point is that an interruption between merging and closing must not look like an interruption before merging.

  • merged or verified: the merge is proven. Finish the interrupted close. Post-merge verification is required unless the phase is already verified, and it runs under backlog_loop_ci, not under a fresh probe: the interrupted batch may have merged the very workflow that now makes CI look available, so re-deriving the route would skip the local gate that batch was actually merged on. Missing backlog_loop_ci -> run the clean-tree post-merge gate. Then the calibration note and bd close against backlog_loop_pr.
  • merge-requested: the outcome is unknown, which is exactly what this phase exists to record. Query backlog_loop_pr. MERGED -> treat as merged above. Any other state -> retire the PR, then reclaim.
  • shipping-requested: the push and PR creation were requested but their identifiers were never recorded, so a PR may exist under a URL the ledger does not hold. Search for it by the recorded branch and head - gh pr list --head <backlog_loop_branch> --state all --json number,url,state,headRefOid,baseRefName - and require exactly one match. MERGED -> treat as merged above. Open -> retire it, then reclaim. No match -> reclaim. More than one match -> STOP rather than guess which one this run created.
  • pr-open, built, or claimed: nothing shipped. Reclaim, retiring the PR first when backlog_loop_pr is set. Never resume a half-built branch into a merge; no gate receipt survived the interruption to say it was ever green.

Retiring a PR means gh pr close <backlog_loop_pr> --comment "superseded: interrupted backlog-loop run <old-run-id>" -- close, never merge. Reclaiming an issue while its PR stays open is how the same work ends up in two open PRs: the issue returns to bd ready, a later batch builds and merges a second PR for it, and the abandoned one still sits there mergeable by a human, carrying changes no current gate ever approved. Leave the branch itself in place and name both it and the closed PR in the FINAL REPORT, so nothing is silently discarded.

Reclaim means bd update <id> --status=open --assignee="" --unset-metadata backlog_loop_run --append-notes="reclaimed from interrupted run <old-run-id> at phase <phase>", plus unsetting every other ledger key, returning the issue to bd ready.

Before any of that, and only after LIVENESS has proved the old run is gone, reap its escaped processes by its <old-run-id> token exactly as step 8 does; a killed runner leaves its children behind, and they will contend with this run's gates. Remove its backlog_loop_worktrees directory and run git worktree prune, so an interrupted run's worktrees and their installed dependencies do not accumulate on disk until a later gate cannot create one.

Two things are deliberately NOT in the ledger. The consecutive-blocked and consecutive-merge-failure counters reset on a new invocation: they bound one run's thrash, and a human choosing to start the loop again is a new decision, not a continuation. <owned-pgids> is in-memory by design, which is why every command also carries the run token - the token is what makes a dead run's processes findable when its PID list is gone. A failed or blocked attempted batch is marked blocked, so it cannot be selected again silently. backlog_loop_attempts is the deliberate exception to that reset rule: it bounds one ISSUE across its whole life rather than one run's thrash, so a scheduled loop cannot rebuild the same failure forever. CI is batch-scoped and re-resolved from the exact current trunk SHA every iteration; it may change in either direction.

CENSUS

One classification pass over the whole tracker, written once and read by both entry points. Both evaluate the same recorded fields under the same stated precedence, so a category difference between a diagnostic run and a loop run over an unchanged backlog is a defect -- in this section, the fixture, or the tracker -- and never a permitted outcome. A shared section removes drift between two texts; it does not by itself make a model deterministic, which is why every test below is a lookup over recorded fields rather than a reading. The loop runs it at the termination decision; the companion census prompt runs it alone. An empty bd ready list has at least six causes and only one of them is "finished", so nothing below infers termination from that list.

Choose a <census-run> token the same way as <run-id>. It is a separate key from the claim marker because "claimed by this run" is itself a category below; reusing one key would make the categories overlap.

ENUMERATE. bd list --all --limit 0 --include-gates --json, then discard status=closed and issue_type=epic rows. Every flag is load-bearing: without --limit 0 the query returns at most 50 rows, without --include-gates it omits native gate issues entirely, and without --all it omits pinned issues, because --pinned filters a different attribute and does not surface them. --all also returns the repository's whole closed history, which is why discarding closed rows is part of the query and not an afterthought. Do NOT pass --include-infra or --include-templates: agent, role, message, and template beads are not work, have no category below, and would make the exhaustiveness claim false. Say so on the census header line, so an operator reading a count knows what it counted.

bd ready --explain --json is the AUTHORITY for the dependency question -- which issues are dependency-blocked, and by what -- and not merely a source of blocker names. Do not re-derive it from edges. The tracker propagates blocking through a blocked parent, so a child whose only edge is parent-child to an open epic is offered as ready when that epic is ready and withheld when it is blocked, and no walk over an issue's own edge list can see that. Re-deriving it produced four ready issues on a backlog where the tracker reported one.

That explanation covers OPEN issues only. A blocked issue appears in neither of its buckets, so every category below that turns on status is read from the issue's own stored status.

METADATA IS NOT IN THE ENUMERATION. bd list --json returns neither a metadata object nor a dependencies array; it carries dependency_count and nothing to resolve it with. So the marker-bearing rows below are answered by one bd list --has-metadata-key <key> --json per marker key -- backlog_loop_run, backlog_loop_cause, backlog_loop_quarantine, backlog_loop_heartbeat, backlog_loop_census_heartbeat -- intersected with the enumeration. That is five queries whatever the backlog's size, where reading the same fact per issue would be one bd show per issue and would put the census's cost on the tracker's.

BACKFILL, once per issue, BEFORE the precedence walk below. A blocked issue carrying backlog_loop_run but no backlog_loop_cause predates this section: it was written by an earlier form of the procedure that recorded only a note. This runs first because rows 6 and 7 read the cause key, and a blocked issue that reaches the walk without one would otherwise be filed by a key that is merely missing. It covers only blocks that already carry a marker; a block with no marker at all is older still, and row 10 handles it without writing anything.

  • The note text is not in the enumeration output, so read it with one bd show <id> --json per affected issue. That is the one place a per-issue query is allowed, and it is bounded twice over: only blocked marker-bearing issues with no cause qualify, and an issue that has a cause key is never re-read.
  • Classify it with step 6's reason patterns only. Step 7's rule is unconditional (needs-person whatever the gate said), so it contributes a note prefix to recognize, not a pattern to match: a note beginning post-merge verification failed: backfills as needs-person. So does a note matching nothing.
  • Derive the cause in BOTH modes; PERSIST it only when the WRITE GATE below allows. A diagnostic run classifies on the derived value and writes nothing, which is what lets --readonly hold. Persisting means backlog_loop_cause=<derived> plus backlog_loop_attempts=1.
  • This is the one place the procedure reads its own prose to decide a class, and the bounds above are what stop the exception spreading.

CLASSIFY. Every enumerated issue gets exactly one category. Walk this precedence top-down and stop at the first match, so an issue satisfying several is filed once and the same issue is filed the same way on every run:

# category test
1 human-gate carries the human-gate label, or issue_type is gate
2 label-defect title begins [HUMAN] but neither of the above holds
3 quarantined carries backlog_loop_quarantine
4 claimed-other-run backlog_loop_run present with a value other than <run-id>, and that run is LIVE by THE RUN LEDGER's liveness test
5 external-wip status=in_progress and no backlog_loop_run
6 self-blocked-needs-person status=blocked, backlog_loop_run present, and backlog_loop_cause is needs-person or still absent
7 self-blocked-transient status=blocked, backlog_loop_run present, backlog_loop_cause begins transient
8 claimed-this-run backlog_loop_run equals <run-id>, and status is not blocked
9 abandoned-claim backlog_loop_run present, that run is NOT live, and status is not blocked
10 legacy-blocked status=blocked, no backlog_loop_run, and no unmet dependency
11 dep-blocked an unmet dependency, by --explain's verdict above
12 hooked status=hooked
13 pinned status=pinned
14 deferred status=deferred
15 ready everything left

Every ordering above is load-bearing, and each one exists because the opposite order sends work somewhere destructive:

  • Rows 1 and 2 sit above everything because a gate must never be claimed whatever else is true of it, and a mislabeled gate must not fall through to ready and get built.
  • Row 3 sits above the rest because a quarantined issue is waiting on a person's eyes, not on the loop, and any lower row would put it back in the pool the quarantine exists to keep it out of.
  • Rows 6 and 7 sit above row 8 so that an issue THIS run blocked is filed as blocked rather than as still claimed. Below row 8 they would be shadowed entirely, and every self-block this run wrote would stay in the loop-responsible set, so the run could never report the backlog clear.
  • Rows 6 and 7 also sit above rows 10 and 11 because an issue this loop blocked can also carry a dependency edge, and the loop's own wreckage is the actionable half of that pair.
  • Row 10 sits above row 11 because a blocked issue that no dependency explains is not dependency-blocked, and filing it as though it were hides it among issues that will clear themselves. It is a block written by an earlier form of this procedure, before any marker was recorded -- the exact population the Problem Frame names -- and it is recognized the way everything else here is, from the ABSENCE of fields rather than from reading the note: no marker to own it, and no unmet dependency to explain it. The note is reported, never tested. A block a person wrote by hand has the same shape and gets the same treatment, which is correct: both need a person, and neither is this loop's to reopen.
  • Row 9 exists because rows 4, 5, and 8 between them do NOT cover an issue left in_progress by a run that has since died: row 4 requires that run to be live, row 5 requires no marker at all, and row 8 requires the marker to be this run's. Without row 9 such an issue reaches row 15 and is offered as ready work, and the loop claims an issue that may still have an open PR -- the exact double-shipping THE RUN LEDGER warns about.

An abandoned-claim is never selected directly. It is this procedure's own wreckage and it goes through THE RUN LEDGER's RECOVERY, which reads backlog_loop_phase and decides per phase whether to reclaim, adopt the existing PR, or leave it; only what recovery returns to open can be picked in a later iteration.

Two different rules govern the marker, and confusing them is what wedges the loop. ## STATE's rule -- split by the PRESENCE of backlog_loop_run, never its value -- decides ownership: present means this procedure's own work, recoverable through THE RUN LEDGER; absent means externally owned and never to be stolen. Rows 4, 8, and 9 are not an ownership question but a liveness one, and they are the ONLY place the marker's value is read, always together with backlog_loop_heartbeat under the liveness test. A stale marker with a dead heartbeat therefore still classifies as this procedure's own wreckage -- as row 9 for an unfinished claim, as rows 6 and 7 for a block -- exactly as ## STATE requires.

EMIT, always, in both modes. One line per enumerated issue, in this exact shape, so a fixture suite and an operator read the same output:

census <id> | <category> | <cause>

<cause> names why the issue sits where it does. Every category has one, so no line is ever left with a blank third field:

category <cause>
human-gate label or native
label-defect the label to apply, human-gate
quarantined the census token recorded in backlog_loop_quarantine
claimed-other-run that run's id
external-wip assignee:<name>, or unassigned
self-blocked-needs-person / self-blocked-transient the recorded backlog_loop_cause value
claimed-this-run the recorded backlog_loop_phase
abandoned-claim the recorded backlog_loop_phase, or none when unset
legacy-blocked no-marker-no-edge
dep-blocked the blocking issue's id, as --explain reports it
hooked / pinned / deferred none
ready none

One line per issue and nothing else on it.

ACCOUNT FOR THE COUNT before reporting anything. The census claims to have accounted for every issue, so it verifies that claim rather than asserting it: the number of census lines must equal the number of rows the enumeration kept. They differ only if a per-issue read failed, and such a read can fail without saying so -- one bd show in fifty-three returned nothing on a live tracker, silently, and a run that did not count would have reported a backlog one issue smaller than it is. On a mismatch, name the missing ids and stop; do not report a census, and do not enter a mutation pass.

Print the header line first, beginning with the literal census-run and carrying the census token, the counts per category, and the flags the enumeration ran under. The two prefixes are fixed and distinct on purpose: a header that also began census would be indistinguishable from a data line to anything counting them, and a reader downstream would report one more issue than the tracker holds.

LOOP-RESPONSIBLE SET. Exactly five categories are the loop's to clear: ready, claimed-this-run, abandoned-claim, self-blocked-transient, and a dep-blocked issue whose blocker is itself in one of those. Everything else is somebody's or something else's. quarantined and legacy-blocked are deliberately NOT in the set, both because they wait on a person. Counting quarantined would make a run that quarantined anything unable to ever report the backlog clear. legacy-blocked is excluded for a stronger reason: reopening one means overriding a decision an earlier run recorded and a person has not revisited, and the recorded reasons are routinely ones no later run can re-derive -- a release boundary the epic declares, a collision with work its caller owned, a unit no agent can build. Reopening those rebuilds what somebody deliberately stopped. The backlog is clear when no issue sits in that set -- never because bd ready came back empty.

Walking dep-blocked transitively meets the cycles this procedure reports and never repairs, so the walk carries a visited set, counts each issue at most once, and treats a repeat visit as the end of that branch plus a reported cycle. Walk the blocked_by lists in the bd ready --explain --json output already taken above, which reports its own cycle count; the enumeration carries no edges to walk.

WRITE GATE, covering EVERY write this section makes -- the BACKFILL persist above and both mutation passes below, not the passes alone. Each is skipped entirely, with no partial application, when either of these holds: the run is a diagnostic run, or any other run of this procedure holds a live heartbeat. The <repair-ceiling> is a third stop but a narrower one: it bounds GATE REPAIR PASS only, and never stops REOPEN PASS, which is bounded per issue by backlog_loop_attempts. Letting an unrelated run of gate repairs stall recovery would leave blocked work unreopened for a reason that has nothing to do with it. A skipped write still classifies, still emits, and still reports every action it would have taken.

"Another run holds a live heartbeat" means either of two things, and both are decided from a field that says WHOSE it is: a backlog_loop_heartbeat under 30 minutes old on an issue whose backlog_loop_run is not <run-id>, or a backlog_loop_census_heartbeat under 30 minutes old whose token is not <census-run>.

Before either pass, publish the second of those: bd update <id> --set-metadata backlog_loop_census_heartbeat="<census-run> | <iso-time>" on the issue that pass is about to write to. Without it the check above is unarmed -- backlog_loop_heartbeat is first written at CLAIM, so two invocations that start together both find no live run and both mutate. The value carries <census-run> and not a bare timestamp because ownership has to be decidable from the field alone: a run that published an unattributable heartbeat and then re-read it would read its OWN heartbeat as a competitor's and skip every pass it had just armed, which is a deadlock that gets worse the more correctly the rest of the rule is followed. Re-read before each individual mutation rather than once for the whole pass, so a run that went live in between is not invisible for the rest of it; a value whose token is <census-run> is this run's own and is ignored.

Repairs are idempotent, not ledger-phased. The ledger's premise is that recovery facts live on a batch's own members, and a census has no members, so there is no natural key to phase against. Every repair below instead re-reads current state and does nothing when it is already applied, which makes recovery "run the census again" and leaves no partial-completion state to reason about.

REOPEN PASS. For each self-blocked-transient issue, in ascending id order:

  • backlog_loop_attempts at or above <attempt-ceiling>=3 -> rewrite backlog_loop_cause=needs-person, name it in the report, and never reopen it again. The consecutive-blocked counters reset per invocation by design; this one must not, or a scheduled loop rebuilds the same failure forever.
  • Otherwise prove the recorded cause is gone before touching it. The cause is a recorded value, not a reading of the note: transient:trunk-moved is gone when <remote>/<default> is currently green under backlog_loop_ci's recorded route. transient:merge-precondition is gone when the recorded PR is CLOSED and no PR is open on the recorded branch. A MERGED PR is NOT that proof, even though a merged PR is also a closed one: the work is already on trunk, so reopening rebuilds shipped code. Read the PR's state explicitly and require CLOSED; on MERGED, rewrite the cause to needs-person and report it. A bare transient with no subtype, or any cause with no proof the census can evaluate on its own, is not a transient cause at all: record it needs-person rather than leaving it to expire on a condition nothing can satisfy.
  • Proven -> one atomic update, following the reclaim shape: bd update <id> --status=open --assignee="" --unset-metadata backlog_loop_run --append-notes="reopened by census <census-run>: <cause> no longer holds", plus unsetting backlog_loop_phase, backlog_loop_base, backlog_loop_branch, backlog_loop_head, backlog_loop_pr, backlog_loop_merge, backlog_loop_ci, and backlog_loop_worktrees. Reopening preserves every metadata key it does not name, so a stale backlog_loop_pr left behind would be read as this run's own PR at the next claim. backlog_loop_cause and backlog_loop_attempts are deliberately NOT cleared: the attempt count is what bounds the cycle.
  • A block written because post-merge verification failed is never reopened, whatever trunk currently looks like. Its code is already on trunk and its phase is already merged; the proven-merge rule in THE RUN LEDGER says that work is finished, not rebuilt. Step 7 records it needs-person for exactly this reason.

Stamp backlog_loop_census=<census-run> on every issue this section writes to, as part of that same write rather than as a second command. It is what lets ## FINAL REPORT find the issues a census touched without re-deriving them, and stamping only the issues actually written keeps the marker meaning "this census changed this issue".

GATE REPAIR PASS. See ## HUMAN GATES.

REPORT. Both modes end with the census lines, the loop-responsible count, every gate with what its person must do, every labeling defect with the exact label to apply, every legacy-blocked issue with the first line of its note -- that note is the only record of why it was stopped, and a person cannot decide without it -- every reported cycle, and -- in a loop run -- every repair and every reopen this run actually performed, each naming both issues, the marker state relied on, and where its restorable record sits. A run that mutates a person's tracker and does not say so is the failure this section exists to prevent, so that list is printed whether the run cleared the backlog or not.

For each ready issue also print how many issues it would unblock, counted transitively over the same visited-set walk, as two figures: those the loop can clear unaided, and those that become reachable only once named gates are released. One combined number hides the pairing an operator most needs -- a ready issue sitting in front of a gate is where a person-minute buys the most agent work -- so name those gates beside it.

DIAGNOSTIC RUN. Choose a <run-id> the same way an ordinary run does even though nothing will be claimed under it, so rows 4, 8, and 9 have a token to compare against. Classification, emission, and reporting are otherwise identical to a loop run; only authority differs. Every bd command carries the global --readonly flag, so a write is refused by the tracker rather than merely avoided by this prose. The run branches nothing, pushes nothing, and merges nothing, and it needs only the tracker and constraints preflight items -- the forge, default-branch, CI, and merge-capability items do not gate it, because diagnosing a backlog must work in a repository this loop could never merge into. It reports every repair and reopen it would perform. The operator authorizes those by starting an ordinary loop run; nothing here asks a question mid-run.

ITERATION

  1. TRUNK HEALTH. MACHINE GUARD, before touching git: run step 8 REAP against the previous batch's <owned-pgids>, which also covers an iteration that stopped early without reaping. Then read the 1-minute load average from sysctl -n vm.loadavg, or uptime where that is the platform's source. Above 2 x <cores> -> reap again, re-read after 60s, and STOP with a report if it is still above that line: opening a batch on a saturated machine stacks another full test run on top of whatever is already pinning the CPU, and every gate below then times out on contention instead of on code. Green -> record <iteration-start> as the current time, reset <owned-pgids> to empty, and refresh backlog_loop_heartbeat to the current ISO UTC time on every member this run currently holds. A stale heartbeat is what lets the next invocation tell an abandoned run from a live one, so it has to be renewed on a schedule, not written once at CLAIM. Update trunk safely with git fetch <remote> --prune, git switch <default>, and git merge --ff-only <remote>/<default>. Never use reset or a tree-wide checkout. Then require git rev-parse HEAD to equal git rev-parse <remote>/<default>. --ff-only succeeds and changes nothing when the local branch is AHEAD of the remote, so it does not by itself prove the two match; a local default branch carrying unpushed commits passes it untouched. Ahead -> STOP and report those commits without moving them. They are the user's, they were never offered to this loop, and branching from that HEAD would push and self-merge them inside the batch's PR. Resolve the current <remote>/<default> SHA and repeat the exact-SHA CI probe from preflight. Matching usable runs -> set <batch-ci>=on. No matching run, startup_failure, or billing/quota/spending failure -> set <batch-ci>=off. This decision is only for the current batch; re-probe next iteration so a batch that adds CI can move the following batch from off to on. Once the batch has claimed its members, write the resolved route to backlog_loop_ci before any gate runs under it. <batch-ci>=on: take the latest attempt of EVERY workflow for that SHA, not just the newest one overall. Any of them failed from code -> STOP and report; never pile another merge onto a broken trunk. Any of them still in progress -> wait under a bounded deadline of 10 minutes, re-reading every 30s, and require all of them terminal and green; still undecided at the deadline -> STOP and report. An undecided run is not a green one: later probes only ever look at their own exact SHA, so a trunk failure ignored here is never observed again and every following batch stacks onto it. <batch-ci>=off: LOCAL TRUNK GATE: run the unconditional quality gates through CLEAN-TREE GATE RUN at the current <remote>/<default> SHA. Skip them only when the previous iteration ended with a green POST-MERGE VERIFY and <remote>/<default> still resolves to that exact verified commit; record trunk gate: skipped (verified <sha>). Green -> continue. Red -> STOP and report.

  2. PICK BATCH. Run CENSUS first, every iteration, and select from it rather than from a bare ready list. The pool is its ready category exactly -- quarantined and abandoned-claim are separate categories, so neither can reach the pool and no subtraction is needed here. Take every abandoned-claim through THE RUN LEDGER's RECOVERY before selecting; what recovery returns to open appears as ready in the next iteration's census. Then run the owner-decision scan ## PREFLIGHT defers to this point, over the issues this census classified as neither human-gate nor label-defect. Skip [epic] containers; close an epic only when all children are closed.

    No issue in the loop-responsible set -> the backlog is clear. Report it with the census and stop. Loop-responsible work remains but none of it is selectable -> that is not clear either: report which categories hold it and why, and stop. Ready work remains alongside issues the loop cannot act on -> continue; a serialized backlog is still a healthy one, and reporting the rest at the end beats stopping on it.

    ANCHOR: highest priority P0->P4, then dependency depth (unblockers first).

    ESTIMATE: bd show <id> --json for the anchor and every candidate sibling; read estimated_minutes. Unset -> derive from the BATCH BUDGET rubric and write it back.

    ANCHOR SIZING:

    • anchor is an owner-decision issue -> the batch is the anchor alone; skip GROW entirely, because OWNER-DECISION ISSUES below requires one PR to carry one decision. The GROW rule only bars an owner-decision sibling, so without this line the anchor's own case is left to the executor and two of them would batch it differently.
    • anchor estimate >= TARGET -> the batch is the anchor alone; admit nobody.
    • anchor estimate > CEILING -> still run it alone, and record oversized on its line and in the final report: that issue wants splitting, not batching. Never skip it, the loop must make progress.

    GROW (only when the anchor is under TARGET): take the anchor's parent epic from its bd show --json, then bd ready --parent <epic-id> --json --exclude-type=epic, ordered the same way. Admit a sibling only if ALL hold:

    • same parent epic as the anchor
    • same priority band
    • not an owner-decision issue
    • batch total + its estimate <= CEILING
    • resulting member count <= CONCERNS

    Stop growing once the batch total reaches TARGET. Otherwise the batch is the anchor alone. Never batch across epics. bd ready already excludes a sibling blocked by another unfinished issue.

    Record batch estimate: <n> min across <k> issues.

  3. BASE. Repeat the safe fetch/switch/ff-only update from step 1. Record the resulting <remote>/<default> commit as <batch-base-sha>. Every batch starts from freshly merged trunk without disturbing pre-existing dirty files.

  4. CLAIM. bd show <id> for every member, then atomically claim each with bd update <id> --claim and set its run metadata, including backlog_loop_phase=claimed and backlog_loop_base=<batch-base-sha>. If any claim or metadata write fails, release every member claimed by this attempt back to open, clear its assignee and run metadata, then re-read bd ready; never continue with a partial batch.

  5. RUN THE PIPELINE, WITH TWO CHECKS AT THE PLAN BOUNDARY.

    Compose a brief that lists each member as its own unit, <id> | <title> | <description> | <design> | <relevant notes> | <acceptance criteria>, and states plainly: one plan with one unit per issue, one branch, one PR covering all members. Include <excluded-paths> as caller-owned WIP that every child must leave untouched. For an owner-decision member, also map the chosen design entry into LFG's settled-decisions brief with decision, provenance user-directed (the explicit backlog-loop invocation authorizes its recommended-option rule), rejected alternative, and reason. Use user-approved only when the tracker proves approval. This makes the plan carry one U-ID per issue and preserves tracker decisions instead of flattening the batch into one vague blob.

    Read the current available LFG SKILL.md, references/plan-brief.md, references/work-return.md, references/review-followup.md, and references/shipping-tail.md; they own the stage contracts below. Act as a headless automated pipeline caller equivalent to LFG: no child may present a user menu or ask a question, and every child returns control to this loop.

    PIPELINE:

    1. Invoke compound-engineering:ce-plan with the composite brief and explicit headless pipeline context. Require its reported plan path, implementation-ready code metadata, confidence receipt, and completed non-interactive doc-review envelope. The single missing-path retry and every blocked/readiness rule come from LFG's plan-brief.md. Do not run a duplicate doc review when that envelope is complete; if it reports skill_unreachable, invoke compound-engineering:ce-doc-review mode:non-interactive <reported-path> once and require a valid return.
    2. Perform the plan-boundary checks below. When a member is removed, delete its unit and all now-orphaned requirements, acceptance evidence, verification mappings, dependency edges, and Definition-of-Done entries; then re-run ce-doc-review mode:non-interactive on the edited plan and require a coherent result.
    3. Read LFG's work-return.md, then invoke compound-engineering:ce-work mode:return-to-caller <reported-path>. Do not add the unsupported exclude: carrier; ce-work's workspace setup owns the pre-work WIP inventory and collision gate. Only a valid status: complete receipt advances.
    4. Read LFG's review-followup.md; run compound-engineering:ce-simplify-code with the plan structure-pin context and explicit caller-owned WIP exclusions unless LFG's docs-only/trivial skip applies. Then run the supported review invocation compound-engineering:ce-code-review mode:agent base:<batch-base-sha> plan:<reported-path>. Its base scope can still observe tracked pre-existing dirt, so filter the structured return before LFG steps 5-6: a finding whose changed evidence is exclusively in <excluded-paths> is out of scope, must be recorded in Coverage as caller-owned pre-existing WIP, and is neither applied nor handed off as a batch residual. A finding connecting an intended batch path to an excluded path remains in scope and blocks rather than editing the excluded path. Excluded paths are never staged by review-fix commits. Require status: complete; a degraded, blocked, skipped, or malformed return takes the blocked path in step 7 -- a review whose reviewers all failed returns no findings, which is not the same as finding nothing. Then execute that reference's apply and residual-handoff stages before moving on, and reconcile every finding the review returned against either an applied fix or an entry in ## Unapplied review findings. Reconciliation is per finding, not per section: the section is composed only from findings that were NOT applied, so a review whose findings were all applied correctly produces none. An absent section is therefore clean when every returned finding carries an applied-fix receipt, and unreconciled only when some returned finding has neither a receipt nor a bullet.
    5. Browser testing is a merge prerequisite, so it runs like every other one: commit the candidate first, then start the application from <clean-tree> at that commit and invoke compound-engineering:ce-test-browser mode:pipeline against it, honoring its gate. Run against the working tree and an excluded uncommitted runtime config - a mock auth path, a stub endpoint - is loaded by the browser, passes, and is absent from the PR. Repeat this after any babysitter head change, exactly as step 4's gates are repeated.
    6. Refresh remote refs and require git rev-parse <remote>/<default> still equals <batch-base-sha>; if trunk moved during the pipeline, block and STOP without merging so no stale-base result can ship. Commit every remaining batch change first, including review fixes, never staging <excluded-paths>, so the gate runs against exactly what will ship; git status --porcelain may then list nothing outside <excluded-paths>. Record the branch head as <batch-head-sha> and write backlog_loop_branch, backlog_loop_head, and backlog_loop_phase=built for every member. The branch name is what shipping-requested recovery searches by, so it has to be in the ledger before the push that creates the PR. Build the final reviewed path set from git diff --name-only <batch-base-sha> <batch-head-sha> and subtract <excluded-paths>; if an excluded path appears in that diff it was committed by mistake, so block. Run the applicable local quality gate set through CLEAN-TREE GATE RUN at <batch-head-sha>, selecting conditional gates from that complete path set rather than an earlier child receipt. While <batch-ci>=off this set must also cover every workflow command found by preflight's gate-coverage read, re-resolved against this batch's own tree.
    7. Write backlog_loop_phase=shipping-requested for every member BEFORE invoking the child, because the push and the PR it creates are irreversible and the ledger must already know they might exist. Read LFG's shipping-tail.md, then invoke compound-engineering:ce-commit-push-pr mode:pipeline branding:on babysit:off exclude:<excluded-paths> when exclusions exist, otherwise omit the exclude: carrier. Thread the plan path, settled-decision conflicts, and residual section required by that reference. Require a pushed branch and open PR URL. Then require the PR to be the one this loop intended: gh pr view <url> --json baseRefName,headRefOid,url must report baseRefName equal to <default>, headRefOid equal to <batch-head-sha>, and a URL under the repository <remote> resolves to. A PR opened against another base merges nothing into trunk while every base-and-head guard below still passes. Mismatch -> blocked path. Only then write backlog_loop_pr and backlog_loop_phase=pr-open for every member.
    8. <batch-ci>=on: record the PR head, invoke compound-engineering:ce-babysit-pr mode:pipeline <pr-url>, and require its current structured CI-decided success contract. That contract authorizes the babysitter to commit and push fixes, and every gate above already ran, so re-read the PR head afterwards. Moved -> the new head has passed CI but has been through no code review, no browser test, and no local gate; return to pipeline step 4 for that head and repeat, at most twice, then block. Unchanged -> continue. <batch-ci>=off: do not invoke the babysitter; the green clean-tree pre-PR gate is the batch authority.

    Both checks below happen at the plan gate - after planning has written and reported a plan path, and before implementation reads that plan. Neither is a separate later step; once implementation starts it is too late for either.

    • If no plan path was reported, its own gate has failed: treat the run as blocked and go to step 7's blocked path. Never hunt the filesystem for a plan it did not report.

    • DOC REVIEW. Require the non-interactive document-review state returned by pipeline step 1 and fold its Apply-routed findings into the plan. Do not review the same unchanged plan twice.

    • RE-ESTIMATE AGAINST THE BUDGET (batches of 2+ only). The batch was a hypothesis built from tracker metadata; the plan is the first real evidence about scope and code surface. Re-estimate each unit from what the plan says it actually touches.

      • Total > CEILING, or units touch disjoint code surfaces -> drop members from the tail (lowest priority, then deepest) until the batch is back under TARGET and the remainder shares a surface. Never drop the anchor.
      • Total still far under TARGET -> do NOT go back and grow; a second grow round costs a second plan, which is what batching exists to avoid. Record the miss.

      Dropping a member requires a referentially complete plan edit: remove that unit and every artifact that exists only for it, or the implementation step may still build it and the drop is fiction. Then release its tracker claim so it returns to bd ready with bd update <id> --status=open --assignee="" --unset-metadata backlog_loop_run; write the re-estimate back and record <id> | returned to pool | re-estimated <old> -> <new> min. A dropped member is NOT counted as attempted.

    Continue the pipeline after the boundary checks. A batch advances to merge only with valid stage receipts, an open PR URL, zero canonical needs-human residuals, and zero unchecked entries in ## Unapplied review findings -- with every finding the review returned reconciled to either an applied-fix receipt or one of those entries, so an absent section is proof of nothing on its own. Unchecked entries are undecided findings and cannot cross an autonomous merge gate. <batch-ci>=on additionally requires the babysitter's mergeable CI decision. <batch-ci>=off requires green applicable local gates on the final diff. Any blocked, failed, malformed, red-required-check, missing-PR, or decision-needed result takes the blocked path in step 7.

  6. MERGE AFTER THE SELECTED PIPELINE GATE. With <batch-ci>=on, the babysitter already decided CI; do not add another watcher. With <batch-ci>=off, the final local pre-PR gate is the authority. Immediately before merge, refresh remote refs and require <remote>/<default> still equals <batch-base-sha>, and require the PR's current head to still equal <batch-head-sha>; if either moved after the gate, retire the PR as described below and then take the merge-blocked path. Checking the base alone leaves the reviewed head unpinned, and anyone pushing to the PR branch in that window would have their unreviewed commit merged -- and leaving that PR open afterwards just moves the same merge one human click away. Do not use GitHub auto-merge. Before issuing the merge, set backlog_loop_phase=merge-requested on every member: an interruption between the merge and the close is otherwise indistinguishable from an interruption before it, and THE RUN LEDGER's recovery reads exactly this phase to decide whether to finish the close or return the issue to the pool. Then run gh pr merge <url> --squash --delete-branch --match-head-commit <batch-head-sha>, exactly one merge per batch. Never pass --admin.

    The command's exit status decides nothing. A zero exit is a request, not a merge: on a merge-queue branch gh pr merge enables auto-merge or enqueues the PR and still returns success. A NON-zero exit is not a failure either: GitHub can complete the squash merge and the connection drop before the response arrives. So poll gh pr view <url> --json state,mergeCommit every 30s under a bounded 10-minute deadline after EVERY merge invocation, whatever it returned.

    MERGED -> record its merge commit as <merge-sha>, then require git merge-base --is-ancestor <merge-sha> <remote>/<default> after a fetch, so the commit is proven to be on the branch this loop was working against and not merely merged somewhere. Write backlog_loop_merge and backlog_loop_phase=merged for every member.

    Anything else -> the merge did not happen and the PR must not be left mergeable by someone else. Run gh pr merge <url> --disable-auto if auto-merge ended up armed, then `gh pr close --comment "not

Truncated - read the full file at https://github.com/vinhnxv/skills/blob/aebcfc3b86e038193efdbfc95fc685c762442894/skills/codex/backlog-loop/SKILL.md.

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/vinhnxv-skills-backlog-loop/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

vinhnxv-skills-backlog-loop.ocm.jsonjson
{
  "ocm": "1",
  "id": "vinhnxv-skills-backlog-loop",
  "kind": "skill",
  "name": "backlog-loop",
  "description": "Clear a repository's Beads backlog autonomously in budget-sized batches by driving the current compound-engineering:lfg stages through their supported child-skill seams, with CI-aware merge gates. Explicit invocation only; run before the matching long-running goal.",
  "publisher": "vinhnxv",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "finance"
    ],
    "tags": [
      "skill-md",
      "github"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Clear a repository's Beads backlog autonomously in budget-sized batches by driving the current compound-engineering:lfg stages through their supported child-skill seams, with CI-aware merge gates. Explicit invocation only; run before the matching long-running goal."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "github",
      "repository": "https://github.com/vinhnxv/skills",
      "path": "skills/codex/backlog-loop/SKILL.md",
      "ref": "aebcfc3b86e038193efdbfc95fc685c762442894",
      "url": "https://github.com/vinhnxv/skills/blob/aebcfc3b86e038193efdbfc95fc685c762442894/skills/codex/backlog-loop/SKILL.md",
      "key": "vinhnxv/skills/skills/codex/backlog-loop/SKILL.md"
    }
  },
  "instructions": "Clear this repo's issue backlog autonomously, one batch at a time, until CENSUS below shows no issue left in a category this loop is responsible for. An empty `bd ready` list is never that proof. Never ask the user anything; when a choice arises take your recommended option and record it.\n\nA batch is 1..N sibling issues sized against a complexity budget, so one LFG-compatible pipeline is neither a wasted trip for one 15-minute chore nor an unreviewable multi-hour PR. One batch = one plan = one branch = one PR = one merge.\n\nThis procedure is the outer loop and shipping authority. For every batc",
  "cost": {
    "context_tokens": 19337
  }
}

Fetch it by URL: GET /api/v1/registry/vinhnxv-skills-backlog-loop/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.