/sdlc:backlog-triage
Generated from solutions/ontological/skills/backlog-triage/SKILL.md.
Description
Section titled “Description”Walk untriaged backlog files in docs/planning/backlog/ and decide what to
do with each: promote to task, promote to milestone, defer (bump
last_reviewed), or skip. Promotion calls sdlc task create / sdlc milestone create
directly and updates the backlog frontmatter to point at the new artifact.
Commits each decision atomically. Operates on main; no worktree.
Allowed tools
Section titled “Allowed tools”ReadEditBashGlobAskUserQuestion
Source
Section titled “Source”Usage:
/sdlc:backlog-triage— walk every untriaged backlog file./sdlc:backlog-triage <slug-or-path>— triage just the named file.
Untriaged ≡ frontmatter has no status: field. Any task with status: set (whether promoted/* or
closed/*) is considered already triaged and skipped by the no-arg pickup.
Project context (don’t re-derive every run):
- Backlog files live at
docs/planning/backlog/<slug>.md. Schema at${CLAUDE_PLUGIN_ROOT}entities/backlog/schema.ts. - Two usage patterns coexist (see the template head comment). This skill targets
single-candidate files — those representing one idea that may get promoted. Multi-item dump
files (where the body holds many ideas) typically don’t ever get
status:set; for those, this skill offers “defer” (bump last_reviewed) but the per-item promotion is up to the author. - Frontmatter mutations go through
sdlc backlog update <backlog> --set '<json>'(schema-validated before write, body byte-untouched, anullvalue deletes a key); the standalone validator issdlc entities validate. - Promotion writes both a new task/milestone file AND updates the backlog frontmatter. Both go in one commit per decision so the link is atomic.
- Commit directly on
main. There is no worktree and no PR.
1. Survey
Section titled “1. Survey”If an argument was given, resolve it:
- Absolute path → use as-is if the file exists under
docs/planning/backlog/. - Otherwise, glob
docs/planning/backlog/<arg>*.mdthendocs/planning/backlog/*<arg>*.md. If exactly one matches, use it. If multiple match, present the list via AskUserQuestion and let the user pick. If none, exitNO BACKLOG FOUND for "<arg>".
If no argument, list docs/planning/backlog/*.md and read frontmatter from each. Filter to the
untriaged set (no status: field at all). Sort by filename (alphabetical). If the untriaged set is
empty, exit NO UNTRIAGED BACKLOG FILES.
Report at the start: how many files will be triaged, and their slugs.
2. Per-file decision loop
Section titled “2. Per-file decision loop”For each file in turn:
-
Read the file. Capture:
- Frontmatter (current
status,tags,last_reviewed). - Headline (the first
#line in the body, stripped). - Body summary — the first ~200 chars after the headline. If the body is short, show the whole thing.
- Frontmatter (current
-
Present the file to the user as a short text block: slug, headline, current tags, last_reviewed (if set), then the body summary. Keep this concise — three to six lines.
-
Ask via AskUserQuestion (4 top-level options):
- Promote to task — create a task via
sdlc task create, then set this backlog tostatus: promoted/taskwithresult: [[<new-task-basename>]]. Default action for items that map to one concrete change. - Promote to milestone — create a milestone via
sdlc milestone create, then set this backlog tostatus: promoted/milestonewithresult: [[M-<NNNN>-<slug>]]. For larger outcomes spanning multiple tasks. - Close — discard without promotion. A second AskUserQuestion asks the reason:
abandoned(won’t do / no longer relevant; noresult:needed) orduplicate(followed by a plain-text ask for the canonical backlog slug →result: [[<other-slug>]]). - Defer — bump
last_reviewed:to today; no other change. Use when the item is still relevant but you’re not ready to promote.
- Promote to task — create a task via
-
Execute the chosen action (steps 3–6 below). Then continue the loop.
There is no explicit “skip” option. To leave an item undecided, interrupt the session: committed decisions stay and the unprocessed files remain untriaged, resurfacing in the next run.
3. Promote to task
Section titled “3. Promote to task”-
Preview the slug + id
task createwould assign for the backlog headline, and surface any collision so the user can decide whether this backlog is already a task:${CLAUDE_PLUGIN_ROOT}cli/sdlc task preview-id "<backlog-headline>" --output jsonThe op is read-only: it derives the slug (the same capped
deriveSlugcreateuses with--slugomitted) and reports the would-beid, plusexists/exact_match(an existing task with the identical slug) andsimilar[](existing tasks with a close slug). If the input has no alphanumeric content it exits non-zero (INVALID_INPUT) — that’s the only “empty slug” case; when it happens, ask the user via AskUserQuestion for a different headline to derive from.Then make the load-bearing “is this backlog already a task?” decision via AskUserQuestion, keyed off the preview output:
- If
existsis true (orexact_matchis non-null), an existing task already carries this exact slug — offer reuse (point this backlog’sresult:atexact_match.idand skip creation) vs override (free-form text via Other for a distinct slug to pass as the positional below). - Else if
similar[]is non-empty, surface those neighbors (their ids + slugs) and offer the same reuse-vs-proceed choice. - Else (no exact, no similar) proceed straight to creation — no override needed.
- If
-
Create the task. With
--slugomitted,createDERIVES the slug from the headline (the same derivationpreview-idjust showed), so don’t hand-pass it. Only pass an explicit<slug>positional when the user chose override above:${CLAUDE_PLUGIN_ROOT}cli/sdlc task create \--headline "<backlog-headline>" \--status planning/draft \--impact medium \--complexity mediumCapture the printed path and the minted id. The task’s canonical id is the
T-<NNNN>formpreview-idreported (andcreateprints) — the schema-assigned id, not a dated basename. Use it for theresult:wikilink. (When the user chose reuse, skip this step and pointresult:at the existingexact_match.idinstead.) -
Update the backlog frontmatter via
sdlc backlog update(schema-validated; the body is left byte-untouched):${CLAUDE_PLUGIN_ROOT}cli/sdlc backlog update <backlog-path> \--set '{"status":"promoted/task","result":"[[T-<NNNN>-<slug>]]","last_reviewed":"<today>"}'The
result:wikilink MUST be the canonical[[T-<NNNN>-<slug>]]form (the schema-assigned id, NOT the dated basename); the op rejects a date-prefixed shape.<today>is today (UTC, YYYY-MM-DD). The op fails non-zero if the resulting frontmatter does not validate — fix the--setpayload and re-run before committing. -
Commit atomically — only the two files; don’t sweep in unrelated changes. Stage the exact paths, then author the message through
sdlc commit create(subject + body; no shell re-parse of the conventional-commit parens):git add <task-path> <backlog-path>${CLAUDE_PLUGIN_ROOT}cli/sdlc commit create \--subject "docs(planning): promote backlog/<backlog-slug> to task/<task-basename>" \--body "<backlog-headline>"
4. Promote to milestone
Section titled “4. Promote to milestone”Mirror step 3, but:
-
Use
sdlc milestone createinstead ofsdlc task create. The milestone op auto-assigns the nextM-<NNNN>id — capture it from the printed path. -
Update the backlog via
sdlc backlog updatewithstatus: promoted/milestoneandresult: [[M-<NNNN>-<slug>]]:${CLAUDE_PLUGIN_ROOT}cli/sdlc backlog update <backlog-path> \--set '{"status":"promoted/milestone","result":"[[M-<NNNN>-<slug>]]","last_reviewed":"<today>"}' -
Stage both files and commit through
sdlc commit create:git add <milestone-path> <backlog-path>${CLAUDE_PLUGIN_ROOT}cli/sdlc commit create \--subject "docs(planning): promote backlog/<backlog-slug> to milestone <M-id>" \--body "<backlog-headline>"
The milestone create call:
${CLAUDE_PLUGIN_ROOT}cli/sdlc milestone create \ --title "<backlog-headline>" \ --status open/draft(Don’t seed tasks or other fields — the milestone will be fleshed out later by /sdlc:milestone-new
follow-ups or by hand.)
5. Close
Section titled “5. Close”The user picked Close in step 2.3. Ask via AskUserQuestion which reason applies (2 options):
- Abandoned — won’t do, no longer relevant. No
result:needed. - Duplicate — already captured elsewhere. Follow up with a plain-text ask: “Which backlog slug
is this a duplicate of? (just the slug, no
[[...]])”. Validate the response againstSLUG_REfrom${CLAUDE_PLUGIN_ROOT}lib/util/slug.ts. Confirm the target file exists atdocs/planning/backlog/<slug>.md; if not, ask again or offer to abandon instead.
Then update the backlog frontmatter via sdlc backlog update:
-
Abandoned: the schema FORBIDS
result:onclosed/abandoned, so null-delete any priorresult:in the same call.${CLAUDE_PLUGIN_ROOT}cli/sdlc backlog update <backlog-path> \--set '{"status":"closed/abandoned","result":null,"last_reviewed":"<today>"}' -
Duplicate:
${CLAUDE_PLUGIN_ROOT}cli/sdlc backlog update <backlog-path> \--set '{"status":"closed/duplicate","result":"[[<other-slug>]]","last_reviewed":"<today>"}'
The op is schema-validated; it fails non-zero if the result does not validate. Then commit only that
file through sdlc commit create:
git add <backlog-path>${CLAUDE_PLUGIN_ROOT}cli/sdlc commit create \ --subject "docs(planning): close backlog/<backlog-slug> as <reason>" \ --body "<one-line note: backlog headline for abandoned; '\"<headline>\" — duplicate of <other-slug>' for duplicate>"6. Defer
Section titled “6. Defer”-
Update the backlog frontmatter via
sdlc backlog update: setlast_reviewed:to today. Do not changestatus:. The op is schema-validated and leaves the body byte-untouched.${CLAUDE_PLUGIN_ROOT}cli/sdlc backlog update <backlog-path> \--set '{"last_reviewed":"<today>"}' -
Commit only that file through
sdlc commit create:git add <backlog-path>${CLAUDE_PLUGIN_ROOT}cli/sdlc commit create \--subject "docs(planning): defer backlog/<backlog-slug>" \--body "Reviewed but not yet promotion-worthy."
7. Wrap up
Section titled “7. Wrap up”After the loop finishes (or the user stops it), print a short summary:
- N files considered.
- For each: what happened (
promoted to task/<basename>,promoted to milestone M-<NNNN>,closed/abandoned,closed/duplicate of <slug>,deferred). - If anything was committed, hint that the user can
git log --onelineto review orgit reset HEAD~<n>to undo a contiguous batch.
Acceptance criteria
Section titled “Acceptance criteria”- Every promoted backlog file has
status: promoted/<entity>AND a canonicalresult:wikilink ([[T-<NNNN>-<slug>]]for a task,[[M-<NNNN>-<slug>]]for a milestone — never a date-prefixed basename) ANDlast_reviewed:set to today. - Every closed/duplicate backlog file has
status: closed/duplicateANDresult: [[<other-backlog-slug>]]ANDlast_reviewed:set to today, and the referenced backlog exists. - Every closed/abandoned backlog file has
status: closed/abandonedANDlast_reviewed:set to today, and NOresult:field. - Every modified backlog file passes
sdlc entities validate. - Every promotion produced exactly one new artifact file AND one updated backlog file, both in the same commit. Every close updated exactly one backlog file in its commit.
- Deferred backlog files have only their
last_reviewed:changed. - Files left unprocessed by the session are byte-identical to their pre-skill state.
- No file outside
docs/planning/was modified.
- Promotion does NOT invoke the interactive
/sdlc:task-newor/sdlc:milestone-newskills — they ask too many follow-up questions for a triage flow. The new artifact lands with sensible defaults (task:status: planning/draft,impact: medium,complexity: medium; milestone:status: open/draft, no version or target_date). The author refines them later via/sdlc:task-defineand/sdlc:milestone-newfollow-up edits. - The backlog body is NOT carried over into the new task/milestone. The body stays in place on the
backlog file as the origin story; the new artifact starts empty. To seed the body content into the
task, follow up with
/sdlc:task-defineor paste it in by hand. - This skill never changes the body of a backlog file — only frontmatter. Strike-through / deletion of promoted items inside multi-item dump files is the author’s job.
- Backlog files with
status:already set are NOT re-triaged. To re-promote (e.g. you decide the task it pointed at should actually be a milestone), edit the backlog frontmatter manually — clearingstatusandresultreturns the file to the untriaged set. - Committing model-generated messages. Each decision’s commit is authored through
sdlc commit create(--subject/--body, or--message -for whole messages on stdin) so the conventional-commit parens never reach a re-parsing shell. See${CLAUDE_PLUGIN_ROOT}conventions/commit-messages.md— the op is the canonical pattern; themktemp+ quoted-heredoc +git commit -Fform stays documented there only as the no-CLI-available fallback. Stage the exact paths withgit add <paths>first —commit createcommits the staged index, it does not narrow scope.