automation-mode.md
# Automation Mode
You are running unattended inside a `bmad-loop` sweep session. No human is
watching; a deterministic program spawned you, will validate your result.json
field-by-field, and will kill this session after your final turn.
## Identity & I/O contract
- `$BMAD_LOOP_RUN_DIR` and `$BMAD_LOOP_TASK_ID` are set in your environment.
- Your **result file** is `$BMAD_LOOP_RUN_DIR/tasks/$BMAD_LOOP_TASK_ID/result.json`.
Writing it is the LAST action of the run. Schema:
```json
{
"workflow": "deferred-sweep-triage",
"open_ids": ["DW-1", "DW-3", "..."],
"already_resolved": [{ "id": "DW-1", "evidence": "<file:line or commit that resolved it>" }],
"bundles": [
{
"name": "<kebab-case-name>",
"dw_ids": ["DW-3"],
"intent": "<2-6 sentences: the one cohesive goal>"
}
],
"blocked": [{ "id": "DW-4", "blocker": "<named story/epic that must land first>" }],
"skip": [{ "id": "DW-9", "reason": "<why this is moot/superseded>" }],
"decisions": [
{
"id": "DW-7",
"question": "<the choice the human must make>",
"context": "<2-4 sentences of code-grounded context>",
"options": [
{
"key": "1",
"label": "<short label>",
"effect": "build",
"intent": "<what a dev session would implement>"
},
{
"key": "2",
"label": "<short label>",
"effect": "close",
"resolution": "<optional: why closing is fine>"
},
{ "key": "3", "label": "<short label>", "effect": "keep-open" }
],
"recommendation": "1"
}
],
"escalations": []
}
```
- Validation rules the orchestrator enforces (a violation fails the whole
result and burns a retry):
- `open_ids` must list exactly this session's triage universe: every
`status: open` entry in the ledger or, when the invocation carries
`--only DW-1,DW-2,...`, exactly those named ids — the orchestrator
parses the ledger itself, applies the same selection, and compares.
- Every id in the triage universe appears in exactly ONE of
already_resolved / bundles / blocked / skip / decisions. No misses, no
duplicates, no invented ids — an open entry outside the `--only`
selection counts as invented.
- Bundle names: `^[a-z0-9][a-z0-9-]{1,39}\Z`, unique, non-empty `dw_ids`,
non-empty `intent`. An otherwise-valid overlong bundle name or decision
option `bundle_name` is truncated to 40 characters and journaled before
validation; post-truncation name collisions still fail validation.
- Every `already_resolved` entry needs non-empty `evidence`; every
`blocked` a `blocker`; every `skip` a `reason`.
- Decisions: >= 2 options with unique keys, `effect` one of
`build|close|keep-open`, `intent` required when effect is `build`,
`recommendation` must be one of the option keys.
- Write `already_resolved[].evidence` and an option's `label` and `resolution`
as a **single line** — each is copied onto one line of the line-oriented
deferred-work ledger. This is guidance, not a validation rule: a break is
collapsed to a space rather than rejected, so it costs nothing but reads
worse. Both `intent` fields are exempt — keep them at the length the schema
asks above (2-6 sentences for a bundle), newlines and all.
- **Migration sessions** (`--migrate`, see `./migration-mode.md`) use this
result schema instead:
```json
{
"workflow": "deferred-sweep-migrate",
"mapping": [{ "key": "<manifest key>", "dw_id": "DW-12" }],
"escalations": []
}
```
Validation rules: the rewritten ledger parses with ZERO legacy items;
pre-existing `### DW-<n>:` entries keep their ids and status; new entries
continue numbering past the highest existing number with `status: open` or
`status: done <date>`; `mapping` covers every manifest key exactly once and
each `dw_id` exists with the manifest's open/done state (two keys may share
a `dw_id` when merging duplicates of equal done-ness).
- Your **escalation file** is `$BMAD_LOOP_RUN_DIR/tasks/$BMAD_LOOP_TASK_ID/escalation.json`.
Use it only for blockers no rule resolves (e.g. the ledger is missing or
unreadable: `type: missing-ledger`, severity `CRITICAL`), then include the
same entries in result.json `escalations` and end your turn. Schema:
```json
{
"escalations": [
{
"type": "<short-kebab-kind>",
"severity": "CRITICAL|PREFERENCE",
"detail": "<one or two sentences>"
}
]
}
```
## Behavior rules
1. **Never HALT for input. Never ask the user anything.** No greeting, no
menus, no "what next" offers.
2. **Read-only — except migration mode.** In triage you never edit the
ledger, code, specs, or sprint-status; the orchestrator performs all
ledger edits deterministically and runs the bundles you propose through
separate dev/review sessions. A `--migrate` session edits exactly one
file: the ledger (still never code/specs/sprint-status, never commits).
3. **Verify before classifying.** Never trust an entry's own status or wording;
check the code. An entry that says "open" but is fixed goes to
already_resolved with evidence.
4. **Conservative on human territory.** Frozen-block renegotiations, scope
reversals, and API-shape changes are decisions, not bundles (see SKILL.md
Step 3). When in doubt between bundle and decision, choose decision.
5. **Never commit, never push, never open an editor.**
bmod.toml
[skill]
bmod = "bmod-bmad-loop"
source = "github:bmad-code-org/bmad-loop/skills"
deferred-work-format.md
# Deferred Work Format
Canonical entry format for `{implementation_artifacts}/deferred-work.md` (on a
BMAD v7 project, the active initiative folder's `deferred-work.md`, or
`output_folder`'s when no initiative is active). The orchestrator owns this file,
and two eras of dev session feed it:
- **Current (BMAD-METHOD 6.10.1-next.33+).** The unattended primitive
`bmad-build-auto` writes nothing here: it records defer-triaged review findings
in its spec's frontmatter `deferred:` list, and after the session the
orchestrator harvests those into canonical entries below, carrying a
fingerprinted `origin:` that starts with `spec-deferred`, plus `source_spec:`.
- **Legacy and attended.** Pre-rename primitives (`bmad-dev-auto`) and the
attended `bmad-build` append flat `- source_spec:` blocks directly into this
file (on v7, `- source_plan:` blocks, the same shape otherwise); a `bmad-loop sweep --migrate` session normalizes them into the canonical
form, and rewrites freeform pre-DW-format content from older projects wholesale
(see `./migration-mode.md`; the TUI shows such legacy items read-only until
then).
Either way this file stays the sweep's sole read surface. Multi-goal and token
splits are a legacy/attended source only — the current unattended primitive does
not split a multi-goal spec, it records a `multiple-goals` warning in the spec's
`warnings:` and proceeds.
The file is append-only — never rewrite or delete existing entries. The one
sanctioned rewrite is operator-run archiving (below): closed entries move to
`deferred-work-archive.md` — body preserved, with an `archived: <date>` marker
appended after the status line — leaving a stub behind.
## Archiving (`bmad-loop sweep --archive`)
The operator may periodically move closed entries
(`status: done <ISO date>`) to the sibling `deferred-work-archive.md`, leaving
a stub in this file:
```markdown
### DW-7: Old closed item
status: done 2026-05-25
archived: 2026-08-24
```
Rules for sessions reading the ledger:
- The stub's `status: done` line means what it always meant — the entry is
closed and not open work.
- An `archived:` line marks content that lives in `deferred-work-archive.md`.
The full body (evidence, resolution, dates) is there, keyed by the same
DW- id — read the archive file for anything beyond the stub. An id may own
more than one block there once a reopened entry has been archived twice.
Narrow by the date on this line; when several blocks share it — one entry
closed, archived, reopened and archived again inside a single day — take the
**last** of them. The archive file is append-only, so for one id a later
block is a later closure.
- An `archived-body:` line is that same pointer carried by an entry that was
archived and then **reopened** — the orchestrator writes it in place of the
`archived:` stamp, which would otherwise claim a live entry's body is
elsewhere. The entry is open work again and its `status:` says so, but the
body it carried before that close is still in the archive file — resolve
this line exactly as an `archived:` stamp above: narrow by its date, and take
the last of the blocks that share it. Reopening does not bring the body back, and a stub keeps neither `location:` nor `reason:`, so read that block
before triaging the entry. Never edit or drop the line.
- The archive may be absent even when a stub or an `archived-body:` line
references it: only the ledger is seeded into an isolated unit worktree. That
is not an error — the fields the stub preserves are sufficient for dedupe.
- Stubs keep load-bearing field lines (`gate:`, `origin:`/`source_spec:`,
resolution undo markers) — treat them exactly as if the entry were still
whole. Never edit or drop them when touching a stub.
- When deduping against existing entries, a stub still counts: match on the
id and preserved `origin:`/`source_spec:` lines, and check the archive for
the full substance before appending.
## Before appending: dedupe check
Scan the existing file for an entry describing the same issue or goal (same
location and same substance, even if worded differently). If one exists, do
NOT append a duplicate — add a `seen-again:` line to the existing entry
instead:
```markdown
seen-again: 2026-06-12 (code review of spec-3-3-export.md)
```
## Entry format
Number entries sequentially (`DW-1`, `DW-2`, …) by scanning the file for the
highest existing number. One entry per deferred item:
```markdown
### DW-<seq>: <one-line title>
origin: <workflow + artifact + date, e.g. "code review of spec-3-2-digest.md, 2026-06-12">
location: <file:line or component, or "n/a" for deferred goals>
severity: <critical | high | medium | low — how much it matters if never done>
reason: <why this was deferred rather than done now, one or two sentences>
status: open
```
`location:` is always written. Use `n/a` whenever there is nothing to open — a
deferred goal, but equally a finding whose reporter recorded no place. The field
says "no location was recorded", not "this item has none": a reader that finds
`n/a` should fall back to `reason:`, which often names the file even when
`location:` is empty. Entries written before this rule may omit the line; read
an absent `location:` as `n/a`, never as "not yet known".
Entries the orchestrator harvests from a spec carry one extra line,
`source_spec:`, directly after `location:` — the spec the deferral came from. On
v7 the field keeps its name and holds the plan's path relative to the initiative
folder (`tickets/epic-auth/tickets/story-login-plan.md`), because plan names repeat across epics.
It is half the dedupe key (with `origin:`), so never edit or drop it when
touching an entry; entries written by hand do not need it.
**Every field line is exactly one line, and so is the title.** The format is
line-oriented: readers find each field by scanning for `<name>:` at the start of
a line, and an entry ends at whichever comes first — the next `### DW-<n>`
entry, any other `#` .. `######` heading (indented up to three spaces, and
followed by a space, a tab or the end of the line; four spaces or a leading tab
makes an indented code block, which ends nothing), or a `- source_spec:` or
`- source_plan:` flat-append bullet. A value carrying a line break therefore does not wrap; it
becomes new ledger content, and three things can follow:
- a break followed by `### ` mints an entry nobody filed;
- a break before a `status:` line leaves one entry carrying two, so the ledger
no longer says one thing about it;
- a break followed by `- source_spec:` or `- source_plan:` cuts the entry short
at that bullet, and
everything after it re-surfaces as a phantom _legacy_ item.
Keep breaks out of field values, along with `### ` and a leading
`- source_spec:` or `- source_plan:`. If a reason needs two sentences, write them on one line.
`severity:` is optional — entries written before this field existed have none
and that is fine; readers must treat a missing or unrecognized value as
"unspecified". Use `critical` for correctness/security issues, `high` for
likely user-visible problems, `medium` for quality and robustness gaps, `low`
for polish and nice-to-haves.
When a deferred item is later completed, set its `status:` to `done` with the
date (e.g. `status: done 2026-06-20`) — do not delete the entry.
## Hard gates: `gate:`
Some entries are not merely deferred — they **block** specific stories. An
infrastructure leg nobody has wired yet is not a nice-to-have for the first story
that consumes it; that story must not run at all until the entry lands. Say so
with a `gate:` line naming the blocked story keys:
```markdown
### DW-1: wire the blob-storage credentials
status: open
gate: 3-2, 3-3
```
`gate:` is optional and most entries have none. Its value is a **comma-separated**
list of story-key tokens; several `gate:` lines in one entry union, so an entry
blocking three stories may list them on one line or on three. A token matches a
story key when it **is** that key, or is its prefix at a key boundary — either a
`-`, or the split-story suffix (one lowercase letter then `-`). So `3-2` gates the
sprint key `3-2-invite-link-student-surface`, the stories-mode id `3-2`, and both
halves of a split (`3-2a-…` / `3-2b-…`), but never `3-20-later-story`. The split
arm matters because breakdown can split a story _after_ the gate was written, and
a gate that quietly stops matching is worse than one that was never there. The
prefix must end at a story **number** for the split arm to apply, so a word id
like `auth` does not gate `authz-login`.
Like `source_spec:`, a `gate:` line is never edited or dropped when an entry is
otherwise touched: removing it un-gates the story silently, which is the exact
failure this field exists to prevent. During a `bmad-loop sweep --migrate` that
is enforced mechanically — the orchestrator refuses a rewrite that drops a token
a pre-existing entry declared (#519); every other ledger edit is still held by
this instruction alone.
Until the entry lands, the gate is enforced twice. `bmad-loop validate` **fails**
(`deferred.hard-gate`) for every story a token matches that the queue would
actually dispatch — sprint-status stories at `backlog` / `ready-for-dev`, or
manifest entries whose spec is not yet written or sits at `draft` /
`ready-for-dev` / `in-progress` / `in-review`. A `blocked` manifest entry is not
gated, nor is one the scheduler would stop on anyway (two specs matching one id,
or a skeletal sentinel from a failed planning halt): the queue cannot reach that
story, so a gate refusing it would report work held back that was never going to
run. A `run` that never called `validate` **pauses** (`story-gate`) rather
than dispatch a gated story. Two things clear it: closing the entry
(`status: done <date>`), or removing the token because it no longer blocks that
work. This is the one deferred-work check that gates rather than advises:
everything else here is traceability that may be wrong, while this is work that
must not start.
**Only an explicit `done` retires a gate.** A status the format cannot read —
`status: opne`, or an entry with no `status:` line — is not evidence the work
landed, so the gate still holds. Write the status word exactly.
A sweep is never gated by the ledger it is draining, whatever any entry's `gate:`
says: closing the gating entry is what a sweep is for, so gating it would
deadlock the gate against its own remedy.
**Quoting the field is safe.** A `gate:` line inside a fenced code block is an
example, not a declaration, so an entry that documents this convention gates
nothing. This holds for a whole quoted entry too — heading, `status:` and `gate:`
inside one fence, the shape shown above: the fenced heading starts no entry, and
a quoted heading or bullet does not end the entry that quotes it, so a real
`gate:` below the example keeps gating. One exception worth knowing when you
write an entry: a fence you open and never close is not treated as a fence at
all, because swallowing the rest of the entry could silently disable a real
`gate:` line below it — and, at file scope, hide every entry after it. Close your
fences.
Four shapes declare a gate that nothing can enforce, and all four are reported as
`deferred.hard-gate-unstructured` while the entry is unlanded:
- a token nothing can match. It must look like a story key
(`[A-Za-z0-9][A-Za-z0-9._-]*`, no spaces) **and** be a shape a key can actually
take — alphanumeric segments joined by `-`, or a full sprint key. So a
space-separated `gate: 3-2 3-3` is one bad token rather than two good ones, and
`gate: 3.2` / `gate: 3_2` are rejected: no key spells its numbers that way.
Inside a sprint slug those characters are fine — `gate: 3-2-a_b` is a real gate;
- a `gate:` line with nothing usable after the colon (`gate:`, `gate: ,`) — each
such line is reported, including one sitting beside a line that does name a
story, since the half that names nothing is the half you are wrong about;
- a `gate:` that is not lowercase at the very start of its line — `Gate: 3-2`, or
a line that indents `gate: 3-2`. These are reported rather than read as
declarations: the field is a fixed spelling, and guessing at near-misses is how
a line that was never meant to gate ends up refusing a story;
- prose declaring `HARD GATE:` — the convention that predates this field —
anywhere on a line of an entry that carries no `gate:` line. It is matched
mid-line because `reason:` prose is hard-wrapped, but never directly after a
quote character (`"`, `'`, `` ` ``, `«`, or a curly quote): an entry that merely
_cites_ the phrase stays silent, as does one that writes it without the colon.
Each reads like a gate already in force while holding nothing back. Add or repair
the `gate:` line to make it enforceable.
### `gate:` in tickets mode
On a BMAD v7 project run in tickets mode (`[stories] source = "tickets"`), a
`gate:` token names tickets, not story keys. Three forms are read:
- `1.2` — the ticket with that ref (epic id, then entry id), the form
`tickets.py` prints. Ids compare as `tickets.py` compares them, so `1.06` is
`1.6`;
- `1` — every ticket of the epic with that id in the initiative's table;
- `epic-cart` — every ticket of that epic folder.
There is no prefix or split arm: ticket ids are opaque, so `2.6` does not gate
`2.6a` and `1.2` does not gate `1.20`. A token in none of these forms (`1.2.3`)
is `deferred.hard-gate-unstructured`, as above. So is a backlog file name
(`story-login.md`): a run never dispatches a backlog row, and a ticket moved
into an epic runs under its `E.M` ref, so gate it by that ref instead.
A v6 story-key token (`3-2`, `3-2-invite-link`) is a migrated ledger's: it
keeps its `gate:` lines verbatim, and the v7 migration records each story's old
key as `v6_key` in its epic's `tickets.toml` entry. The token is looked up there,
matched as a story key (the exact key, or its `3-2` prefix or split arm). When
exactly one ticket claims it, it gates that ticket as its `E.M` ref would, and
messages keep the token as written. When no ticket claims it, or two or more do
(`3-2a-…` and `3-2b-…` for `3-2`), it names no single ticket: while the entry is
unlanded, `bmad-loop validate` fails `deferred.hard-gate` for it and a tickets
run pauses at `story-gate` before every ticket. Rewrite such tokens as ticket
refs, or close the entry. While such a token stands, a `tickets.toml` that
cannot be read for its claims pauses the run at `story-gate` and fails `validate` with kind `probe-failed`. Sprint and stories
modes read the story-key grammar only, so `gate: 1.2` is malformed there.
## Sweep annotations
`bmad-loop sweep` runs (the orchestrator and its bundle dev sessions) add two
optional field lines to existing entries — both directly after `status:`:
```markdown
resolution: <one line: what was built or why the entry was closed>
decision: <date> <chosen option label> — <detail>
```
- `resolution:` accompanies every sweep close (`status: done <date>`). Bundle
dev sessions write it when finishing a bundle's entries; the orchestrator
writes it when closing entries triage proved already resolved.
- `decision:` records a human's sweep-time choice on an entry. It does not by
itself change `status:` — a `keep-open` decision leaves the entry open.
## Closure declared by a story
A sweep bundle is not the only thing that closes an entry. A regular story may
declare the entries its work closes — on its `stories.yaml` entry (stories mode),
or in its story spec's frontmatter. The two are unioned:
```yaml
closes_deferred: [DW-5, DW-6] # DW-<n> ids this story closes
```
Both are written by a human, and breakdown time — with this file open — is where
it belongs, though not a deadline: the declaration is read when the story
commits, so one added to a spec's frontmatter mid-run still counts. No upstream
skill emits the field yet, and re-deriving `stories.yaml` will drop it unless the
intent is recorded in `.memlog.md` first.
When the story commits, the orchestrator annotates each declared id exactly as a
bundle close does — `status: done <date>` plus a `resolution:` line naming the
story:
```markdown
status: done 2026-07-23
resolution: resolved by story 3-2-export
```
The rules that keep this safe:
- **Declared, never inferred.** Closure comes only from this field; the
orchestrator does not guess it from a diff.
- **Only once the story actually lands.** The annotation is written at the
commit boundary — after verification, the review loop and every checkpoint,
and just before the story's commit is squashed. A story that fails, blocks, is
rejected by review, or escalates closes nothing; a commit that then fails
takes the annotation back with it, restored to the pre-close text.
- **In the story's own commit**, when this file lives inside the repo —
worktree isolation included: the unit's copy rides the unit commit and
reaches the target branch with the merge. If the artifacts dir is configured
outside the repo, the file is shared between worktrees and no commit can
carry it; the annotation is written all the same and the run journals
`deferred-close-external-ledger`. A location that cannot be read or written
when the write comes due closes nothing and is journaled — the entries stay
`open` for a sweep to re-verify, and an outage is never read as "no such
entries", never allowed to fail the story or crash the run.
- **Idempotent.** An id already `done` is left untouched, so a resumed run
re-driving the same close neither doubles the `resolution:` line nor warns.
- **Never a gate.** An id that matches no entry, an entry whose `status:` reads
as neither `open` nor `done`, and a story spec declaring a bare
`closes_deferred: DW-5` where a list belongs are each journaled and dropped —
none can fail the story. `bmad-loop validate` reports the same mismatches as
warnings before the run starts. The one exception is that same wrong container
in `stories.yaml`: the manifest is a schema the parser owns, so it fails to
load there like any other field of the wrong type — before any story runs, and
reported by `validate` up front.
- **Read at the commit.** The declaration that counts is the one on disk when the
story commits, not the one it was implemented from — edit it late and the edit
is honored, in both directions.
Keep the ids stable when editing this file: a reworded title is fine, but
renumbering an entry orphans any declaration that already references it.
migration-mode.md
# Migration Mode
Projects started before the DW entry format carry a freeform
`deferred-work.md` — "## Deferred from: ..." sections with bullets,
strikethrough done-markers, `### D-1.2-003: title — RESOLVED` headings,
topic sections suffixed "(... — DONE)". The orchestrator cannot sweep those:
`status:` lines don't exist to flip, and open items are invisible to its
parser. Your job is a one-time rewrite of every legacy item into a canonical
`### DW-<n>:` entry, after which the normal triage flow takes over.
This is the ONE workflow mode that edits a file: exactly the ledger at
`$BMAD_LOOP_LEDGER` — `{implementation_artifacts}/deferred-work.md` on v6, the
initiative folder's `deferred-work.md` on v7 — which the orchestrator resolved
and which wins over any path you would derive yourself. Never any other file,
never code, never specs or plans, never sprint-status. Never commit — the orchestrator
commits the migrated ledger after validating it.
## Inputs
- `--migrate <manifest-path>`: a JSON array — the orchestrator's parse of the
legacy items. Each element:
```json
{
"key": "<stable identity — echo it back in your mapping>",
"id": "<native id like W2 / D-CAP-001, or empty>",
"title": "<cleaned one-line title>",
"section": "<enclosing heading text>",
"done": true,
"severity": "high"
}
```
The manifest is authoritative for WHAT to convert and each item's
open/done state. Do not re-interpret done-ness from the prose; the
orchestrator validates your output against the manifest's `done` flags.
- `--feedback <path>` (retry only): the deterministic validation errors your
previous attempt failed on, including any items that still parse as legacy.
Read it FIRST and fix exactly those defects.
## The rewrite
1. Read the manifest and the full ledger.
2. Keep every existing `### DW-<n>:` entry **byte-identical** — the
orchestrator fails the migration if a pre-existing entry's status changes,
an entry disappears, or a `gate:` token an entry declared is no longer
present.
3. Replace all legacy content with canonical entries per
`./deferred-work-format.md`. Number new entries continuing
from the highest existing `DW-<n>` (start at DW-1 when none exist), in
the items' original file order. Per item:
- `### DW-<n>: <title>` — the manifest title, refined from the original
bullet when it improves clarity.
- `origin: migrated from legacy ledger ("<section>"), <today>` — keep the
original review/date context from the section text.
- `location:` — extract a file/component from the original text, else `n/a`.
- `severity:` — the manifest severity; omit the line when null.
- `reason:` — the substance of the original bullet, condensed but lossless
enough that a future dev session can act on it. Long forensic detail may
follow as body lines under the fields.
- `status: open`, or for done items `status: done <date>` using the
original completion date when recoverable (else today), plus a
`resolution:` line carrying the original resolution text when one
exists (e.g. the text after `→` or a `**Resolution:**` field).
4. Two manifest items describing the same underlying issue (e.g. a duplicate
`W1` re-raised in a later review) may merge into ONE DW entry — map both
keys to the same `dw_id`. Merge only when their `done` flags match. The
merged entry's severity is the highest normalized manifest severity across
all merged items (`critical` > `high` > `medium` > `low`); omit `severity:`
only when every merged item has null severity. The orchestrator validates
this grouped rule once per target id.
5. The finished file must contain only the `# Deferred Work` title line and
canonical `### DW-<n>:` entries. Any leftover freeform section, bullet
list, or strikethrough item fails the orchestrator's zero-legacy check
and burns a retry.
## Result
Write the result file per automation-mode.md with the migration schema:
```json
{
"workflow": "deferred-sweep-migrate",
"mapping": [{ "key": "<manifest key>", "dw_id": "DW-12" }],
"escalations": []
}
```
Every manifest key appears exactly once; every `dw_id` must exist in the
rewritten ledger with the manifest's open/done state. State in one line how
many items you converted (and how many merged), then end your turn.
SKILL.md
---
name: bmad-loop-sweep
description: 'Triage the deferred-work ledger for the bmad-loop orchestrator: verify every selected open entry against the actual codebase and return a machine-readable partition (bundles, already-resolved, blocked, skip, human decisions). Also migrates legacy pre-DW-format ledgers when invoked with --migrate. Automation-only — invoked by bmad-loop sweep runs, not by humans.'
---
# Deferred-Work Sweep Triage
**Goal:** Classify every selected open entry in `{implementation_artifacts}/deferred-work.md`
into a machine-readable triage plan the orchestrator can validate and execute.
This workflow is **read-only and automation-native**: it runs only inside a
`bmad-loop` sweep session. You never edit the ledger, never edit code, never
ask questions. Your sole output is the result file. (The one exception is
migration mode, which edits exactly one file — the ledger.)
## On Activation
### Step 0: Automation Check & Mode Dispatch
Run: `echo "${BMAD_LOOP_MODE:-}"`
If the output is not `1`, state that this skill only runs inside a bmad-loop
sweep and end your turn. Otherwise read `./automation-mode.md` fully — its
rules and result schema govern this entire run.
If the invocation carries `--migrate <manifest-path>`, this is a **migration
session**, not triage: read `./migration-mode.md` and follow it instead of
Steps 1–4 below.
### Step 1: Locate the ledger
Run: `echo "${BMAD_LOOP_LEDGER:-}"`
The printed path is the ledger — the orchestrator resolved it from the project's
BMAD config (on v6 `{implementation_artifacts}`, on v7 the active initiative
folder), so read that file and never re-derive it; its directory is
`{implementation_artifacts}`. Read the ledger in full. Open entries are
`### DW-<n>:` blocks whose `status:` line is `open`. If the output is empty, or
the ledger is missing or unreadable, escalate `CRITICAL`
(`type: missing-ledger`) per automation-mode.md and end your turn.
An entry carrying an `archived:` line keeps only a stub here — its full body
lives in the sibling `deferred-work-archive.md`, keyed by the same DW- id; read
it there before classifying that entry. An `archived-body:` line says the same
of an entry that was archived and later reopened: it is live work again, but the
body it carried before that close is still in the archive file, in the block
stamped with the date the line carries.
If the invocation carries `--only DW-1,DW-2,...`, those ids are the complete
triage universe for this session. Read the full ledger, but verify and partition
exactly those named open entries; do not add other raw-open entries to
`open_ids` or any result category. The orchestrator has already applied the
operator's named-subset or severity-floor selector and validates your result
against this exact scope.
If the invocation carries `--feedback <path>`, read that file FIRST — it lists
the deterministic validation errors your previous attempt's result.json failed
on. Fix exactly those defects in this attempt's output.
### Step 2: Verify every selected entry against the code
Ledger statuses are known-unreliable: entries are often resolved by later work
but never marked done. For EACH entry in the triage universe:
1. Read its `location:` (file/component) in the current tree.
2. Check whether the described issue still exists — read the code, grep for
the symptom, check `git log` for commits that touched the area since the
entry's `origin:` date.
3. Record concrete evidence either way. "Probably fixed" is not evidence;
a file:line or commit hash is.
Use sub-agents for parallel verification when available; never ask permission.
### Step 3: Partition
Classify each entry in the triage universe into exactly ONE category:
- **already_resolved** — the issue no longer exists in the code. Requires
concrete `evidence` (file:line that now handles it, or the commit that fixed
it). The orchestrator closes the ledger entry deterministically.
- **bundles** — buildable now: every file the fix needs already exists and no
future story has to land first. Group entries that share a touchpoint (same
file, same subsystem, same validator pattern) into cohesive single-goal
bundles sized for one dev session; an entry that stands alone is a
one-entry bundle. `name` matches `^[a-z0-9][a-z0-9-]{1,39}\Z` (kebab-case,
at most 40 characters; e.g. `unicode-string-hardening`), and `intent` is
2–6 sentences describing the one cohesive goal.
- **blocked** — the fix is only meaningful (or meaningfully easier) after a
named future story/epic lands. Name the blocker verbatim.
- **skip** — superseded, moot, or tied to a scenario the project explicitly
excludes. Give the reason.
- **decisions** — a human must choose. ALWAYS a decision, never a bundle:
renegotiating anything inside a spec's `<frozen-after-approval>` block,
reversing a deliberate human-approved scope decision, API-shape changes that
ripple to unbuilt consumers, and entries deferred with reason
"auto-mode: needs human decision". Each decision gets 2–4 concrete options
(each with an `effect`: `build` + an implementation intent, `close` +
optional resolution, or `keep-open`) and your `recommendation`. The only
exception: a frozen-block renegotiation that is clearly net-positive,
non-destructive, and behavior-tightening may be bundled — when in doubt,
make it a decision.
Every entry in the triage universe appears in exactly one category. The orchestrator validates
this deterministically; a missed or double-counted entry fails the whole
result and burns a retry.
For a bundle whose intended deliverables are confined to ignored content in the
configured `implementation_artifacts` directory strictly inside the code repository,
describe those deliverables and their location in the existing `intent` field.
Do not add a triage receipt field or assert a receipt on the executing session's
behalf. That session may write `Artifact only: true` on its own line beside
`Status:`, with `true` on the same line as the label and no trailing explanation,
in its own last genuine `## Auto Run Result` only when its actual deliverables
qualify, never based on old artifacts alone. The marker must be authored in the
current session, outside fenced blocks and without an orchestrator repair note; frontmatter does
not assert it, and the value must be the strict boolean `true`. The ordinary
proof-of-work probe must first positively find no changes, and the receipt gate
requires a positive ignored-file listing scoped to that directory. All other
verification and ledger-close checks still apply. In an isolated worktree, successful
integration publishes the accepted ignored bundle spec before teardown. Additional
ignored regular files must be named in the accepted spec's `artifact_deliverables`
frontmatter list, using exact paths relative to `implementation_artifacts`. Describe
these intended outputs in the bundle's `intent` so the executing session can declare
them. Directories, globs, absolute paths, traversal, symlinks, and the orchestrator-owned
ledger and sprint board are forbidden; undeclared files are not copied. Publication
checks destination baselines captured before execution. Conflicting main-checkout
changes pause publication and retain source artifacts for recovery. The receipt
alone does not publish files.
### Step 4: Write the result and end your turn
Write the result.json per `./automation-mode.md` and state in one line how
many entries went to each category. End your turn. Do not edit any file other
than the result file.