surreptakos

triage

Move issues and external PRs through a state machine of triage roles, categorise, verify, grill if needed, and write agent-ready briefs.

surreptakos 0 Updated 2w ago

Resources

4
GitHub

Install

npx skillscat add surreptakos/claude-dotfiles/triage

Install via the SkillsCat registry.

SKILL.md

Triage

Move issues on the project issue tracker through a small state machine of triage roles.

If this repo treats external pull requests as a request surface (see the issue-tracker config), triage covers them too: a PR is an issue with attached code, using the same roles, same states, and same machine, with a few deltas marked "for a PR" below. Resolve a bare #42 to an issue or PR per the tracker config.

Every comment or issue posted to the issue tracker during triage must start with this disclaimer:

> *This was generated by AI during triage.*

Reference docs

Roles

Two category roles:

  • bug: something is broken
  • enhancement: new feature or improvement

Six state roles:

  • needs-triage: maintainer needs to evaluate
  • needs-info: waiting on reporter for more information
  • ready-for-agent: fully specified, ready for an AFK agent
  • ready-for-local-agent — fully specified, but the next step needs the desktop's live tree or a proxy-blocked capability, so a cloud container cannot do it — a sync.ps1 -Mode pull on the desktop to apply a merged profile change, a remote branch delete the session proxy refuses, or an edit the auto-mode classifier blocks in a container (a project-board sweep is no longer one: the Board sweep job runs it, claude-dotfiles issue 216)
  • ready-for-human — reserved for a person's judgment, credential or sign-off; a step a local session can perform never carries ready-for-human
  • wontfix: will not be actioned

For a PR, the same states read against the attached code: ready-for-agent means a brief is attached and an agent should take the next step on the diff; ready-for-human means it's ready for a human to merge.

Every triaged issue should carry exactly one category role and one state role. If state roles conflict, flag it and ask the maintainer before doing anything else.

These are canonical role names. The actual label strings used in the issue tracker may differ. The mapping should have been provided to you. If not, tell the user to run /setup-matt-pocock-skills.

State transitions: an unlabeled issue normally goes to needs-triage first; from there it moves to needs-info, ready-for-agent, ready-for-local-agent, ready-for-human, or wontfix. needs-info returns to needs-triage once the reporter replies. The maintainer can override at any time — flag transitions that look unusual and ask before proceeding.

Invocation

The maintainer invokes /triage and describes what they want in natural language. Interpret the request and act. Examples:

  • "Show me anything that needs my attention"
  • "Let's look at #42" (issue or PR)
  • "Move #42 to ready-for-agent"
  • "What's ready for agents to pick up?"

Show what needs attention

Query the issue tracker and present three buckets, oldest first:

  1. Unlabeled: never triaged.
  2. needs-triage: evaluation in progress.
  3. needs-info with reporter activity since the last triage notes: needs re-evaluation.

When PRs are in scope, include external PRs in these buckets and tag each line [PR] or [issue]. Discovery surfaces only external PRs (the tracker config defines who counts as external), so a collaborator's in-flight PR is not triage work. This filter is discovery-only; an explicitly named PR is always triaged regardless of author.

Show counts and a one-line summary per item. Let the maintainer pick.

Triage a specific issue or PR

  1. Gather context. Read the full issue or PR (body, comments, labels, author, dates; for a PR, the diff too). Parse any prior triage notes so you don't re-ask resolved questions. Explore the codebase using the project's domain glossary, respecting ADRs in the area. Run two checks against the codebase: (a) redundancy: search for an existing implementation of the requested behavior by domain concept (not just the request's wording), and report where you looked. If found, it's an already-implemented wontfix (step 5). (b) prior rejection: read .out-of-scope/*.md and surface any that resembles this request.

  2. Recommend. Tell the maintainer your category and state recommendation with reasoning, plus a brief codebase summary relevant to the request (including whether it's already implemented). Wait for direction.

  3. Verify the claim. Before any grilling, check that the claim holds up. For a bug, reproduce it from the reporter's steps. For a PR, confirm the diff does what it claims: check it out, run the relevant tests or commands. Report what happened: confirmed (with code path), failed, or insufficient detail (a strong needs-info signal). A confirmed verification makes a much stronger agent brief.

  4. Grill (if needed). If the request needs fleshing out, call the Skill tool twice, for "grilling" and "domain-modeling", and grill it into shape a round of questions at a time, sharpening domain terms and updating CONTEXT.md/ADRs inline as decisions land.

  5. Apply the outcome:

    • ready-for-agent: post an agent brief comment (AGENT-BRIEF.md).
    • ready-for-local-agent — same structure as an agent brief, but name the desktop-only or proxy-blocked capability that keeps a cloud container from taking it (a sync.ps1 -Mode pull on the desktop, a remote branch delete the session proxy refuses, or an edit the auto-mode classifier blocks; a project-board sweep is no longer one, the Board sweep job runs it). A desktop session can pick it up.
    • ready-for-human — first run the ruling check. Then the same structure as an agent brief, but note why it can't be delegated (judgment calls, external access, design decisions, manual testing). A step a local session can perform never carries ready-for-human.
    • needs-info: post triage notes (template below).
    • For wontfix, close the issue, with the comment depending on why:
      • Already implemented: the change already exists in the codebase. Point to where it lives; do not write to .out-of-scope/ (that KB is for rejected requests, not built ones).
      • Rejected (bug): give a polite explanation, then close.
      • Rejected (enhancement): write to .out-of-scope/, link to it from a comment, then close (OUT-OF-SCOPE.md).
    • needs-triage: apply the role. Optional comment if there's partial progress.

Ruling check before ready-for-human

Before moving any ticket to ready-for-human — step 5, a bulk pass, a relabel from ready-for-agent, a quick state override — read its comments newest-first for an owner ruling. A ruling comment carries one of these markers: **Owner ruling**, rulings-page:, Ruling (Dan. The ticket body's acceptance line ("Dan rules ...") is not the source: it says a ruling was needed, not that none was given.

If a ruling is found, the owner has already answered, so do not re-queue the ticket for him. Route by the ruling instead:

  • it rules the work in or settles the question — ready-for-agent, with the ruling quoted and linked in the agent brief;
  • it rules the work out or moot — close it (wontfix or completed, as the ruling says), citing the ruling;
  • it leaves a later, different step to a person — keep the current label and comment citing the ruling and naming what is still open. Move to ready-for-human only when that open step is genuinely new, not the question the ruling answered.

On a quick state override, tell the maintainer the ruling you found before applying the move. Four claude-dotfiles tickets (714, 718, 720, 721) went back to ready-for-human on 2026-09-25 hours after Dan had ruled on each, and sat in his queue asking what he had answered (issue 934).

Triaging a discovery list into tickets

A discovery-triage chore hands you a list of findings — the FOLLOW-UPS.md entries a ticket-fleet run appended, a review list, a sweep's output — and asks for one outcome per finding: a ticket filed, a doc fix landed in the same pass, or noise struck with a one-line reason.

Before creating a ticket, search the open issues for the same file, symbol or failure — by what the finding is about, not just its wording (gh api "repos/{owner}/{repo}/issues?state=open&per_page=100" and read the titles and bodies; MCP list_issues in a container). On a match, comment on that ticket with the new evidence instead of creating another, and record that comment's URL as the finding's outcome. File a new ticket only when no open ticket covers the finding.

List the open issues again at the moment you are about to file, not once at the start of the pass: another discovery-triage chore may be running beside you. Two of them in one ticket-fleet wave filed the same tools/tracker-audit.js short-fetch as #281 and #285 two minutes apart (claude-dotfiles issue 319) — the next scout listed both as ready-for-agent, two implementers built the same fix on two branches, and one PR was closed unmerged.

The chore ticket that asks for such a pass must carry that rule in its own brief, in the ticket body's scope or rules lines: before creating a ticket, search the open issues for the same file, symbol or failure and, on a match, comment on that ticket with the new evidence rather than creating another. An agent runs the brief it was handed, not this file. Where a repo has the harness, node tools/tracker-audit.js reports such a pair afterwards as an advisory duplicate-title? finding — a backstop for what slipped through, not the guard.

Quick state override

If the maintainer says "move #42 to ready-for-agent", trust them and apply the role directly. Confirm what you're about to do (role changes, comment, close), then act. Skip grilling. If moving to ready-for-agent without a grilling session, ask whether they want to write an agent brief.

Needs-info template

## Triage Notes

**What we've established so far:**

- point 1
- point 2

**What we still need from you (@reporter):**

- question 1
- question 2

Capture everything resolved during grilling under "established so far" so the work isn't lost. Questions must be specific and actionable, not "please provide more info".

Resuming a previous session

If prior triage notes exist on the issue or PR, read them, check whether the reporter has answered any outstanding questions, and present an updated picture before continuing. Don't re-ask resolved questions.

In a cloud container: same duties, different instruments

A cloud session (claude.ai/code, Cowork) reaches GitHub through the session's proxy — CLAUDE_CODE_REMOTE_SESSION_ID set in the environment is the tell. A claude.ai/code container has gh installed and gh api repos/<owner>/<repo>/... (REST) works, but every gh issue and gh pr subcommand goes through GraphQL, which the proxy refuses (HTTP 403 "GitHub GraphQL is not available from Claude Code sessions"): use gh api REST or the GitHub MCP tools for those (Cowork may have no gh at all; MCP covers it). gh auth status there says "The token in GH_TOKEN is invalid" because the variable holds a proxy placeholder, yet gh api calls succeed — the proxy injects the credential, so that line is not a broken gh. Every role and transition above still applies; only the tool changes. Do not report a step as impossible because its gh issue / gh pr spelling failed — use its gh api REST path or the GitHub MCP equivalent, same shape as the mcp branch of aac-skills/ticket-fleet/ticket-fleet.js and the grill-ready-for-human skill:

The step says In a container use
gh repo view --json nameWithOwner derive <owner>/<repo> from git remote get-url origin
gh issue list --label <role> --state open --json ... MCP list_issues (label + state)
gh issue view N --comments MCP issue_read for the body, its comments method for the thread
gh issue create --title ... --body ... --label ... MCP issue_write method create (title, body, labels)
gh issue comment N --body-file <path> MCP add_issue_comment (read the file into body)
gh issue edit N --add-label ... --remove-label ... MCP issue_write method update (labels: the full new set — update replaces every label, so read them first with issue_read method get_labels, then pass the existing labels minus the removed plus the added)
gh issue close N --reason completed / --reason "not planned" MCP issue_write method update (state: closed, state_reason: completed / not_planned)
gh issue view N --json labels,state MCP issue_read (verify labels + state)
gh pr view N --json ... / gh pr diff N MCP pull_request_read / get_pull_request_diff
gh pr create --title ... --body ... MCP create_pull_request (only when a triage step actually creates a PR — triage rarely does)

Read-only checks can also go straight to REST — gh api repos/<owner>/<repo>/issues/<n> or curl https://api.github.com/repos/<owner>/<repo>/issues/<n> — the session's egress proxy authenticates api.github.com, private repos included. Git itself (clone, push, branch delete) works normally through the same proxy.

Verifying a claim (step 3) still runs the same way — git checkout <ref> and the project's tests execute in the container. The only calls that need MCP are the tracker writes: role transitions, comments, closes.

MCP write calls (create, update, comment, close) may raise a permission prompt; when the user typed /triage, that prompt is the confirmation, not a reason to skip the step. Every posted comment still opens with the > *This was generated by AI during triage.* disclaimer — MCP does not carry an implicit one.