Backlog hygiene sweep - stale items, duplicate pairs, missing acceptance criteria, unblocked-but-unassigned work, and milestone burn, delivered as a report first with only user-approved actions applied. Works on GitHub Issues in tickets mode and on PLAN.md checkboxes in document mode. Use when the user asks to triage, groom, or clean up the backlog or the plan's open tasks.
Scanned 9/28/2026
Install to Claude Code
npx -y skills add AaravChadha/acstack --skill triage --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Triage?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/aaravchadha-triage)More formats (shields.io, HTML) on the badges page.
---
name: triage
description: Backlog hygiene sweep - stale items, duplicate pairs, missing acceptance criteria, unblocked-but-unassigned work, and milestone burn, delivered as a report first with only user-approved actions applied. Works on GitHub Issues in tickets mode and on PLAN.md checkboxes in document mode. Use when the user asks to triage, groom, or clean up the backlog or the plan's open tasks.
argument-hint: "[notes]"
---
# /triage — keep the backlog honest
A backlog rots in specific, findable ways. This skill finds them, reports
them with evidence, and applies exactly the actions the user approves —
nothing more, and nothing silently.
`Adjacent skills:` /audit docs (read-only drift report; /triage proposes
and applies tracker/plan actions) · /ticket (files one new item; /triage
grooms the many existing ones).
<!-- acstack:runtime -->
Run before the skill's steps — per invocation, not per session (4.36); failures degrade to markdown:
```bash
link="$(readlink "$HOME/.claude/skills/health" 2>/dev/null || true)" # empty = not symlinked
pack="$(dirname "$(dirname "$link")")" # NEVER trust this unless $link was non-empty
if [ "${link#/}" != "$link" ] && [ -x "$pack/bin/acstack-config" ] && ! "$pack/bin/acstack-config" runtime | grep -q '=off'; then
"$pack/bin/acstack-config" || true # resolved keys, with sources
"$pack/bin/acstack-update-check" || true # ≤1 fetch/day; silent ONLY if already checked today
"$pack/bin/acstack-recall" || true # LEARNINGS.md + bug-class names, capped 3KB
else
echo "runtime off — proceeding without recall/update-check"
fi
```
<!-- /acstack:runtime -->
<!-- acstack:principles -->
## Operating principles
- Be direct. Push back in writing when the plan or the user is wrong. No sycophancy.
- Never delete a decision. Supersede it: `~~old~~ → **Verdict (YYYY-MM-DD):** new call — reason.`
- Never fix, tune, or delete a test or eval case to raise a score. Log the miss honestly and leave the case unchanged.
- Name exact things: regex patterns, function signatures, model names, before → after numbers. Never "fixed bugs".
- Attribution: follow the project's `attribution` setting (default `none`) — no AI-tool mentions in generated docs, no attribution trailers in commits or PRs. Commit with explicit `-m`/`-F` messages only.
- Config: read `.claude/acstack.md` at the project root (fall back to `~/.claude/acstack.md`) before acting. `## Settings` keys override pack defaults; a `## <skill-name>` section overrides both. Unknown keys and sections are ignored.
- Docs: BRIEF.md (frozen seed) / PLAN.md (living plan) / JOURNAL.md (rolling journal). If the repo uses legacy names (PLANNING_PROMPT.md / PLANNING.md / STATUS.md), use those instead — never create both.
- Recall: if `LEARNINGS.md` exists at the project root, read it before starting.
- Conduct: follow the `acstack-conduct` block in this repo's AGENTS.md — the word is the mode; the user sets the pace.
- Hackathon lane: if the project's AGENTS.md carries the `acstack:hackathon-lane` block, only `/do` changes the repository during the event. Any other skill that would write a tracked file, commit or push says what it would have done and stops; a change that is not a task goes through the lane's operator route.
<!-- /acstack:principles -->
**One document set.** Resolve exactly ONE BRIEF/PLAN/JOURNAL set and name
its path in the report's scope line. If more than one candidate set exists
— a monorepo, nested products, an `apps/*` tree each with its own docs —
list the candidates and STOP. Never pick one silently: a confident answer
about the wrong product is worse than no answer (conduct rule 8).
## Stance: report, then apply
The report opens with a one-line verdict — `backlog healthy` or
`<N> findings across <M> categories` — before any finding. Then the
numbered findings, each with its evidence and its proposed action.
Then the user picks; only approved actions are executed, and the
report closes with the applied-actions list — each action confirmed
by name.
Nothing is ever silently deleted or closed: every close carries a written
reason. Supersede-don't-delete applies to backlogs exactly as it applies
to decisions.
**Applied actions edit the plan in place, so they collide with other
sessions.** Closing or reopening a box changes any count derived from those
boxes; two sessions closing *different* items both hand-write the *same* new
total, and git auto-merges the identical edit with no conflict — measured
2026-09-14, leaving the number one out. So: apply the box changes, and
**re-derive any stored total with the project's own tool** rather than
editing the digit. State in the applied-actions list that the post-merge
total belongs to whoever integrates, since this branch cannot see what
another session closed.
Config: `stale-days` from a `## triage` section in `.claude/acstack.md`
(default 30).
## Mode: the sweep itself
The sweeps are mode-specific and mutually exclusive — read exactly one,
chosen by the resolved `tracking` config, and not the other:
- `tracking: tickets` → `references/tickets-mode.md`
- `tracking: document` (the default) → `references/document-mode.md`
Root-cause clustering below runs in BOTH modes, after whichever sweep
you read.
## Root-cause clustering (both modes)
Every sweep above is a **local** comparison — this item against that item,
this box against its acceptance. Clustering is the **global** one: twelve
items that are all one cause is a finding no pairwise duplicate check can
see, because no two of them are duplicates.
Run it last, over the whole backlog at once, after the local sweeps have
already removed the noise.
**A cluster needs a stated cause, not a shared topic.** "These five all
mention the parser" is a tag. "These five all fail because the tokenizer
drops the final field when the input has no trailing newline" is a cause.
The test is one sentence: **would fixing the named cause close every item
in the cluster?** If some would survive it, the cluster is wrong — split it
or drop it.
The bar, all three required:
- **Three or more items to propose a PARENT.** A parent over two items is
more structure than it earns.
**But a same-cause pair is still named, never dropped.** Two items sharing
a cause are not a duplicate pair — the duplicate sweep keys on overlapping
text and will not see them, so a pair that falls below this bar would
otherwise vanish between the two sweeps. Report it under
`Related pairs`: the cause, both items, and the evidence, with no parent
proposed. Found by shakedown 7, where two items sharing a UTC-boundary
cause were invisible to both passes.
- **A cause written as a sentence**, naming the mechanism — not a
component, a label, or a theme.
- **Evidence per member**: the quoted line, error, or acceptance that ties
that specific item to that cause. An item nobody can tie to the cause is
not in the cluster.
**Propose nothing when the backlog is genuinely independent.** This is the
half that keeps the pass honest: a backlog of unrelated work is the normal,
healthy state, and a clustering step that always finds clusters is
astrology. Say plainly that no root-cause groups were found and move on —
that is a real result, not a failed sweep. Never widen a cause until items
fit it; a cause broad enough to cover everything explains nothing.
What the report proposes, per cluster:
- **A parent** — in tickets mode a new issue titled by the CAUSE; in
document mode a parent task in PLAN.md with the children as its subtasks.
Never a parent that merely renames the group.
- **The children redirected**, each with the same standardized line so the
tree stays navigable: `rolled up into <parent> — same cause: <cause>`.
- **What stays independent**, listed. An item that resisted clustering is
information, not a leftover.
Approval-gated exactly like the rest of this skill: the clusters are
proposed with their evidence, and nothing is filed, edited, or closed until
the user says so. Getting a cluster wrong reshapes someone's whole backlog,
so this is the sweep where a wrong proposal costs the most.
## Hard rules
- Findings without evidence (the quoted text, the failing command, the
date arithmetic) don't make the report.
- The report states what was NOT swept (e.g. acceptance commands too
expensive to run) — a sweep that implies totality it didn't do is
drift about drift.
- Applied actions are listed at the end exactly as executed — issue
numbers, label names, PLAN.md lines — so the JOURNAL entry can cite
them.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!