Skip to content
Back to skills

Triage

ASecurity

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

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 19, 2026
code-qualityrustgotestingrefactoringgitapisecurityperformance

Works with

  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned October 19, 2026

npx -y skills add PVMalove/claude-agent-harness --skill triage --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Triage?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Triage
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/pvmalove-triage/badge)](https://www.skillsdirectory.com/skills/pvmalove-triage)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: triage
description: Move issues and external PRs through a state machine of triage roles — categorise, verify, grill if needed, and write agent-ready briefs.
disable-model-invocation: true
---

# Triage

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

If this repo treats external pull requests (merge requests on GitLab) as a request surface (see the issue-tracker config), triage covers them too: **a PR is an issue with attached code** — same roles, same states, 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

- [AGENT-BRIEF.md](AGENT-BRIEF.md) — how to write durable agent briefs
- [OUT-OF-SCOPE.md](OUT-OF-SCOPE.md) — how the `.out-of-scope/` knowledge base works

## Roles

This project uses a namespace-based, enterprise-style label taxonomy. Every triaged issue carries exactly one `type::*` label and exactly one `status::*` label; while it carries `status::specs` the execution-mode axis below doesn't apply yet (see that role), otherwise it also carries exactly one of `hitl`/`afk`. Optional `priority::*`/`severity::*` labels are purely informational and not part of this required-axis check. If any required axis ends up with zero or multiple labels, flag it and ask the maintainer before doing anything else.

**Type** (`type::*`):

- `type::bug` — something is broken
- `type::feature` — new feature or improvement
- `type::refactoring` — internal restructuring, no behavior change
- `type::chore` — maintenance, tooling, or dependency upkeep
- `type::security` — security fix or hardening
- `type::performance` — performance improvement
- `type::hotfix` — urgent production fix

**Status** (`status::*`) — where it sits in the pipeline:

- `status::specs` — design/spec-writing: an epic or large task still being turned into a spec and decomposed into tickets (normally applied by `/to-spec`, not by triage directly — see below). Too early to take into development; execution mode isn't decided yet.
- `status::ready` — fully specified, ready to be picked up (by an agent if `afk`, by the maintainer if `hitl`)
- `status::in-progress` — currently being worked on in an active session
- `status::blocked` — blocked, either by another ticket's dependency or because triage is waiting on more information from the reporter (post triage notes either way, see the triage notes template below)

**Execution mode** — who does the work (required once an issue leaves `status::specs`):

- `hitl` — human-in-the-loop; needs architectural review, testing, or approval from the maintainer
- `afk` — away-from-keyboard; an agent can complete it alone

**Context labels** — applied when relevant, not part of the required axes above:

- `resolution::wontfix` — the request was explicitly rejected. Applied at close time (see "Apply the outcome" below), not during normal triage.

`task-report::required` is a context label too, but `triage` doesn't apply or read it — `/to-spec` and `/to-tickets` apply it, `/to-pull-requests` acts on it. See `docs/agents/triage-labels.md`.

An unlabeled issue is implicitly "needs triage" — there is no dedicated label for that state, it's just the absence of the required axes.

For a PR, the same labels read against the attached code: `status::ready` + `afk` means a brief is attached and an agent should take the next step on the diff; `status::ready` + `hitl` means it's ready for the maintainer to review or merge.

These are the actual label strings for this repo. See `docs/agents/triage-labels.md`.

Never apply upstream canonical labels (`ready-for-agent`, `ready-for-human`, `needs-triage`, `needs-info`, `wontfix`) or an `epic::<slug>` label. This repo groups a ticket under an epic with the tracker's parent link — a GitHub sub-issue, on GitLab a `## Parent: #<epic>` section plus a `relates_to` issue link, or the local-markdown folder structure (see `docs/agents/issue-tracker.md#wayfinding-operations`); explain that if the maintainer asks for an epic label.

State transitions: analyze an unlabeled issue to determine its `type::*` label and whether it's `hitl` or `afk`, then place it in `status::ready` or `status::blocked` (dependency or missing info) — or, if it's too large to specify in one triage pass, `status::specs` and point the maintainer at `/to-spec`. `status::blocked` returns to `status::ready` once the blocker clears or the reporter replies — `/to-pull-requests` sweeps a closed ticket's dependents, and `/implement`/`/fast-implement` re-check blockers in pre-flight; `triage` does not run that sweep on its own initiative. The maintainer can override at any time — flag transitions that look unusual and ask before proceeding.

## Tracker commands

Read and list issues (and PRs/MRs when they are in scope) with the commands in `docs/agents/issue-tracker.md` for the configured tracker. On GitLab, `<host>`, `<project-url>` and `<project-id>` are defined in that guide's GitLab → Conventions. Write every comment body to a file first, at the path from `docs/agents/git-workflow.md` §1 (`.harness/.sandboxes/pr_body/issue-comment-<issue>-<slug>.md`, or `pr-comment-<issue>-<slug>.md` for a PR/MR).

- **Comment:** GitHub `gh issue comment <n> --body-file <path>`; GitLab `GITLAB_HOST=<host> glab api projects/<project-id>/issues/<n>/notes -F body=@<path>` (GitLab calls comments notes).
- **Replace a label:** one command that adds the new label and removes the previous label with the same `key::` prefix — GitHub `gh issue edit <n> --remove-label <old> --add-label <new>`; GitLab `glab issue update <n> -R <project-url> --unlabel <old> --label <new>`. On GitLab Free a `key::value` label is an ordinary label (mutually exclusive scoped labels are a Premium feature), so a bare `--label` leaves both `status::*` labels on the issue and breaks the exactly-one-`status::*` invariant in [Roles](#roles).
- **Close:** post the comment first, then GitHub `gh issue close <n>`; GitLab `glab issue close <n> -R <project-url>`.
- **For a PR/MR:** GitHub uses the `gh pr` equivalents with the same flags. GitLab comments through `GITLAB_HOST=<host> glab api projects/<project-id>/merge_requests/<n>/notes -F body=@<path>`, replaces a label with `glab mr update <n> -R <project-url> --unlabel <old> --label <new>` and closes with `glab mr close <n> -R <project-url>`.

## 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 status::ready"
- "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. **Missing one of the required axes** — partially triaged, evaluation in progress.
3. **`status::blocked` 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) — 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 rejection, handled under `resolution::wontfix` (step 5). (b) **prior rejection** — read `.out-of-scope/*.md` and surface any that resembles this request.

2. **Recommend.** Tell the maintainer your recommended type (`type::*`), execution mode (`hitl`/`afk`), and status, with reasoning, plus a brief codebase summary relevant to the request — including whether it's already implemented. If the request is epic-sized (too big to specify in this pass), recommend `status::specs` and point at `/to-spec` instead of a state/mode pair. 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 signal to route to `status::blocked` pending more info). 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" — 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:**
   - `status::ready` + `afk` — post an agent brief comment ([AGENT-BRIEF.md](AGENT-BRIEF.md)).
   - `status::ready` + `hitl` — same brief structure, but note why it can't be delegated (judgment calls, external access, design decisions, manual testing).
   - `status::specs` — point the maintainer at `/to-spec` to turn this into a proper spec and decomposition; don't write an agent brief for it.
   - `status::blocked` — post triage notes (template below): a dependency ticket to wait on, or missing info from the reporter.
   - Rejected — apply `resolution::wontfix`, remove any `status::*` label, and close, 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-shaped report)** — polite explanation, then close. Skip the `.out-of-scope/` write — that KB is for rejected requests, not invalid bug reports.
     - **Rejected (feature/behavior request)** — write to `.out-of-scope/`, link to it from a comment, then close ([OUT-OF-SCOPE.md](OUT-OF-SCOPE.md)).
   - Still unlabeled after step 1–4 (partial progress only) — leave it unlabeled, or apply just the axis you've resolved. Optional comment noting what's settled so far.

## Quick state override

If the maintainer says "move #42 to status::ready", trust them and apply the label directly — ask only if the execution mode (`hitl`/`afk`) isn't already clear from context. Confirm what you're about to do (label changes, comment, close), then act. Skip grilling. If moving to `status::ready` + `afk` without a grilling session, ask whether they want to write an agent brief.

## Triage notes template (for `status::blocked`)

```markdown
## 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.

Files in this skill

  • AGENT-BRIEF.md8.2 KB
  • OUT-OF-SCOPE.md4.8 KB
  • SKILL.md11.5 KB
  • agents/openai.yaml135 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.