---
name: ir:triage
description: >
  Autonomously triage GitHub issues on ingo-eichhorst/Irrlicht. Diagnostic-only:
  scores each issue against a 6-axis readiness rubric and lands it at
  ready-for-agent or needs-info, with a one-line justification per label.
  Triggers: "/ir:triage" (sweep), "/ir:triage #N" (single), "/ir:triage #N #M".
---

# Irrlicht Triage (Autonomous, Diagnostic-Only)

Triage issues on `ingo-eichhorst/Irrlicht` end-to-end without prompting the maintainer. **Triage is diagnostic, not prescriptive.** Score readiness, classify, label, post — never propose solutions, never invent acceptance criteria, never break work into subtasks. If an axis fails, flag it; the implementing agent (or a separate planning step) does the prescribing later.

This contract is borrowed from established practice — Chromium's triage guidance ("triage is assessment, categorization, prioritization — not solutioning"), INVEST (Independent / Negotiable / Valuable / Estimable / Small / Testable), Devin's Agents 101 (strong feedback loops, specified outcome, starting points, checkpoints), and mattpocock's AGENT-BRIEF (concrete testable AC, explicit scope boundaries — authored *before* an agent picks up, not by the triage step).

Every `gh` call passes `--repo ingo-eichhorst/Irrlicht` so the skill works from any worktree.

## Invocation

- `/ir:triage` — triage every open issue missing a state role.
- `/ir:triage #278` — triage one issue.
- `/ir:triage #278 #277 #280` — triage several.

Enumerate the "all" set:

```bash
gh issue list --repo ingo-eichhorst/Irrlicht --state open --limit 200 \
  --json number,title,labels \
  | jq -r '.[] | select(
      [.labels[].name] |
      any(. == "needs-triage" or . == "needs-info" or
          . == "ready-for-agent" or . == "ready-for-human" or
          . == "wontfix") | not
    ) | .number'
```

`ready-for-human` is in the filter as a defensive guard — the skill no longer applies it, but a stray issue with that label should not be unconditionally re-triaged.

Triage one at a time so a failure on issue N doesn't block N+1.

## Disclaimer

Every comment posted during triage **must** start with:

```
> *This was generated by AI during triage.*
```

## Per-issue workflow

1. **Read.** `gh issue view <N> --repo ingo-eichhorst/Irrlicht --comments`. **Skip-if-triaged rule:** in the bulk sweep (`/ir:triage` with no args), if a prior `*This was generated by AI during triage.*` comment already exists and a state label is already applied, skip — already triaged. **When the maintainer explicitly names an issue (`/ir:triage #N`), always re-triage** — the explicit invocation is the maintainer's signal that they want a fresh assessment (e.g. after a rubric change). Edit the prior comment in place via `gh issue comment <N> --edit-last` rather than appending a new one.
2. **Verify (light).** For bugs, sanity-check that cited code paths still exist. Don't run tests. For enhancements, glance at `core/domain/` / `core/ports/` if proposed interfaces are named.
3. **Score the 6 axes** (Readiness Rubric below). Each axis is ✓ or ✗ with a one-liner. The verdict falls out of the scoring.
4. **Justify every label** — category, priority, scope, flags, milestone. One line each. If you can't justify, don't apply.
5. **Post** the assessment using the template below.
6. **Label and milestone** in a single `gh issue edit` call (one round-trip per issue). Close if `wontfix`.

## Readiness Rubric (6 axes)

Each axis scores ✓ (pass) or ✗ (fail) with a one-line justification grounded in the issue body. Any ✗ → `needs-info`. All ✓ → `ready-for-agent`.

| Axis | ✓ when… | ✗ when… |
|---|---|---|
| **Scope** | The change is bounded — number of files / packages touched is knowable from the issue, no "and also" creep. | "Refactor the codebase", "improve performance", any wording that fans out unboundedly. |
| **Specification** | The desired outcome is stated (what, not how). Edge cases either listed or absent because they don't exist. | Outcome is implied or aspirational; "make it better"; multiple plausible interpretations. |
| **Verifiability** | A concrete signal exists for "done": a unit test, a replay scenario, a CLI command output, a UI behavior, a metric threshold. | No way to mechanically tell if the fix worked; only "looks right" judgment. |
| **Context** | Relevant code paths / docs / prior PRs are cited or trivially findable. The agent has a starting point. | Issue is abstract or implicates code with no obvious entry point. |
| **Independence** | Not blocked on another issue / PR / external decision. No concurrent work that would conflict. | Depends on #N which is open / unresolved / unmerged; depends on a maintainer decision not yet made. |
| **Reversibility** | Local change, easy to revert. No production data, secrets, or one-way doors. | Touches signing keys, prod migrations, irreversible deletions, anything with blast radius beyond the repo. |

**Complexity** is a separate signal that informs the verdict. Score it Low / Medium / High using the calibration below. Complexity does *not* block readiness on its own — but **High complexity with no breakdown filed (no child issues, no ordered phase plan in the body) is a Scope failure** and lands the issue in `needs-info`.

### Complexity calibration

Industry context: agent benchmarks consistently show top models resolving a much lower fraction of real-world / production-like tasks than of curated tasks — the gap is driven by patch size and specification ambiguity, not by raw reasoning ability. Calibrate accordingly:

- **Low** — one file, one concern, no cross-package coordination. Highest agent success rate.
- **Medium** — 2–4 files across one slice (e.g. domain type + adapter + service), all foreseeable from the issue. One coherent change.
- **High** — multi-package coordination, schema evolution, cross-adapter parity, multi-phase work, or any "and also wire it through the UI". Agents fail here without a breakdown.

If complexity is High and the issue body does not contain a phase plan or link to child issues: **fail Scope, route to needs-info, ask the reporter to break it down.** Triage does not propose the breakdown itself.

## Decision rules

### Category (apply with one-liner)

- `bug` — describes broken behavior, regression, or wrong output.
- `enhancement` — proposes new behavior, capability, refactor, or polish.

### State

Two active states (`ready-for-agent`, `needs-info`) plus `wontfix` for closure.

- `ready-for-agent` — all 6 axes pass. Default heavily here. Visual sign-off and final review happen at PR time, not as triage barriers.
- `needs-info` — one or more axes fail. Two flavors:
  1. **Reporter-side** — missing repro / outcome / verifiability / scope. Brief addresses `@reporter`.
  2. **Maintainer-prereq** — a one-shot setup the agent literally cannot do (cryptographic key custody, account enrollment with identity/wallet). Brief addresses the maintainer and says: "once <prereq> exists, this becomes ready-for-agent."

  Things that do **not** qualify as ✗ on any axis: aesthetic judgment ("matches the design system"), "needs a VM at the keyboard", manual hardware testing, Xcode `.pbxproj` edits, scope-commitment for multi-phase arcs *with a phase plan in the body*.

Closure label (separate from active states):

- `wontfix` — out of scope, duplicate, or invalid. Close after commenting.

### Priority (apply with one-liner)

- `Priority-High` — blocking or user-visible regression.
- `Priority-Medium` — should land soon, not blocking.
- `Priority-Low` — nice-to-have / polish.

Apply on every issue except `needs-info` (priority isn't yet meaningful there).

### Scope labels (apply with one-liner each)

If the issue is scoped to a specific adapter, platform, or module, add a `scope:<name>` label so issue lists are filterable. Use the directory name as the canonical scope token:

- Adapters (`core/adapters/inbound/agents/<name>`): `scope:opencode`, `scope:claudecode`, `scope:codex`, `scope:aider`, `scope:pi`.
- Platforms (`platforms/<name>`): `scope:macos`, `scope:web`.
- Other modules: `scope:daemon` (irrlichd), `scope:cli` (irrlicht-ls / irrlicht-focus), `scope:replay`, `scope:tooling`, `scope:site`.

Multiple scopes are fine for cross-cutting issues. Skip scope entirely for shared infra (`core/domain`, `core/application/services`) without a single owner.

Create a missing label on demand:

```bash
gh label create scope:opencode --repo ingo-eichhorst/Irrlicht --color BFD4F2 --description "OpenCode adapter" 2>/dev/null || true
```

### Flags (apply with one-liner each)

Each flag below is **only** applied when the criterion fits. The brief must include a one-liner explaining *why this flag is on this issue* — generic justifications ("looks beginner-friendly") don't count.

- `good first issue` — apply when **all four** hold: scope is one file or one cohesive function, complexity is Low, no domain-specific knowledge of irrlicht's hexagonal architecture or replay system is required, no concurrency / unsafe / FFI / signing code involved. One-liner names which of the four conditions make it newcomer-safe.
- `help wanted` — issue is well-scoped and acceptance is clear, but is **not** in the maintainer's near-term roadmap and external contribution is welcome. One-liner names why it's parked (e.g. "v0.6 milestone, not blocking 0.4").
- `documentation` — the only artifact that changes is documentation (`AGENTS.md`, `CLAUDE.md`, `site/docs/`, `README*`). One-liner names the doc surface.
- `adapter-request` — issue asks for support for a new coding agent or orchestrator. One-liner names the agent. When this lands at `ready-for-agent`, the brief must point at `ir:onboarding-factory` and the canonical scenario matrix rather than enumerating onboarding steps.
- `track:brand` — touches UI / marketing / overlay design that's part of the brand surface. One-liner names the surface.
- `duplicate` — a prior open issue covers the same scope. One-liner names `#N`.
- `invalid` — the report is wrong (works on current main, misunderstands the system). One-liner names the misunderstanding.

### Milestone (apply with one-liner)

Milestones group issues by release cycle. The convention is `vMAJOR.MINOR` (e.g. `v0.5`). Apply on every `ready-for-agent` issue; skip on `needs-info` and `wontfix`.

**If the issue already has a maintainer-set milestone, do not change it.** Note the existing milestone in the brief; the maintainer's choice wins.

**Resolution.** At the start of the run (or first single-issue invocation), enumerate open milestones and compute the three buckets from the latest published release:

```bash
LATEST_MM=$(gh release view --repo ingo-eichhorst/Irrlicht --json tagName --jq .tagName \
            | sed -E 's/^v//; s/^([0-9]+\.[0-9]+).*/\1/')
# 0.4 → ACTIVE=0.5, NEXT=0.6, FUTURE=0.7
ACTIVE="v$(awk -v v="$LATEST_MM" 'BEGIN{split(v,a,"."); print a[1]"."a[2]+1}')"
NEXT="v$(awk   -v v="$LATEST_MM" 'BEGIN{split(v,a,"."); print a[1]"."a[2]+2}')"
FUTURE="v$(awk -v v="$LATEST_MM" 'BEGIN{split(v,a,"."); print a[1]"."a[2]+3}')"

gh api "repos/ingo-eichhorst/Irrlicht/milestones?state=open" --jq '.[] | .title'
```

**Pick by priority:**

| Priority | Milestone |
|---|---|
| `Priority-High` | `$ACTIVE` (the in-progress release) |
| `Priority-Medium` | `$NEXT` |
| `Priority-Low` | `$FUTURE` |

**Create if missing.** If the chosen milestone doesn't exist in the open set, create it before applying — once per run, not per issue:

```bash
gh api repos/ingo-eichhorst/Irrlicht/milestones \
  -f title="$FUTURE" -f state=open \
  -f description="Future / longer-horizon work" --silent 2>/dev/null || true
```

Description is optional — only set it for `$FUTURE` since that bucket's intent isn't obvious from the title alone.

## Output: assessment (`ready-for-agent` or `needs-info`)

Single template for both verdicts. The brief is purely diagnostic — short, structured, every line earns its place.

```markdown
> *This was generated by AI during triage.*

## Triage Assessment

**Category:** <bug | enhancement> — <one-liner why>
**Priority:** <High | Medium | Low> — <one-liner why> *(omit if needs-info)*
**Scope:** <scope:X[, scope:Y]> — <one-liner per scope> *(omit if shared infra)*
**Flags:** <flag1, flag2> — <one-liner per flag> *(omit if none)*
**Milestone:** <vMAJ.MIN> — <one-liner why; or "existing — maintainer-set"> *(omit if needs-info or wontfix)*
**Complexity:** <Low | Medium | High> — <one-liner: files/packages touched, judgment calls required>

**Readiness:**
- ✓/✗ **Scope** — <one-liner>
- ✓/✗ **Specification** — <one-liner>
- ✓/✗ **Verifiability** — <one-liner>
- ✓/✗ **Context** — <one-liner>
- ✓/✗ **Independence** — <one-liner>
- ✓/✗ **Reversibility** — <one-liner>

**Verdict:** ready-for-agent | needs-info

<!-- if needs-info: -->
**Blocked on, before this becomes ready-for-agent:**
- <specific, one-shot item> (@reporter | @maintainer)
- <specific, one-shot item> (@reporter | @maintainer)
```

### What to leave out

The brief never contains:
- Invented acceptance criteria. If Verifiability fails, that's the gap; do not paper over it with a checklist.
- Proposed subtasks. If complexity is High without a breakdown, that's a Scope failure; the reporter or maintainer files child issues.
- Implementation suggestions, "watch for" invariants, "key interfaces", "out of scope" lists. The agent reads `AGENTS.md` / `CLAUDE.md` and the codebase before starting.
- Re-narration of current behavior or desired behavior. The issue body has those.

If you find yourself writing any of the above, stop — that's brief-authoring, not triage.

## Output: `wontfix`

One short paragraph naming the reason (out of scope / duplicate of #N / invalid + one-liner why). Close after posting.

## Apply labels and milestone (one call per issue)

```bash
gh issue edit <N> --repo ingo-eichhorst/Irrlicht \
  --add-label <category>,<state>,<Priority-X>[,<flag>...][,scope:<area>...] \
  --remove-label needs-triage,needs-info \
  --milestone "<vMAJ.MIN>"
```

Omit `--milestone` on `needs-info` and `wontfix` issues, and on issues that already have a maintainer-set milestone (don't overwrite). If a `scope:<name>` label is new to the repo, create it first. If the chosen milestone doesn't exist in the open set, create it via the `gh api` call above before this command.

## Worked examples

### Example 1 — `ready-for-agent`

For a localized OpenCode adapter bug (one root cause, three named files, clear acceptance test):

```markdown
> *This was generated by AI during triage.*

## Triage Assessment

**Category:** bug — describes a 2.5s tail latency on a state transition; user-visible regression compared to other adapters.
**Priority:** Medium — UX-visible, OpenCode-only, not data-loss or crash.
**Scope:** scope:opencode — root cause and fix surface localized to OpenCode adapter + a flag on `agent.Event`.
**Milestone:** v0.6 — Priority-Medium → next cycle; bounded refactor, ships independently of v0.5 work.
**Complexity:** Medium — three packages in one slice (`core/domain/agent` flag, OpenCode watcher, session detector short-circuit).

**Readiness:**
- ✓ **Scope** — three named files, all foreseeable from the issue body.
- ✓ **Specification** — root cause identified; fix path stated; reasons against alternatives given.
- ✓ **Verifiability** — unit test for terminal `EventActivity` mid-debounce; replay scenario records `working → ready` within ≤500ms.
- ✓ **Context** — exact code paths and constants cited (and verified extant).
- ✓ **Independence** — no blocking issues; flag defaults false so other adapters are unchanged.
- ✓ **Reversibility** — additive flag + local short-circuit; revertible.

**Verdict:** ready-for-agent
```

### Example 2 — `needs-info` (reporter-side gap)

For an enhancement that lists multiple implementation paths without picking one:

```markdown
> *This was generated by AI during triage.*

## Triage Assessment

**Category:** enhancement — proposes new behavior (smarter notification copy).
**Flags:** help wanted — existing flag preserved; brief was already filed by the maintainer and external contributors are welcome.
**Complexity:** Medium-to-High — depends on chosen approach (heuristic vs bundled small model vs piggyback vs hybrid; each has different scope, dependency surface, and binary-size impact).

**Readiness:**
- ✓ **Scope** — bounded to notification text generation; per-adapter parser hooks named.
- ✗ **Specification** — issue lists four implementation paths without picking one; the path choice changes architecture, not just code.
- ✗ **Verifiability** — no acceptance bar stated for "good enough" generated text quality.
- ✓ **Context** — code paths for transcript reads exist per adapter.
- ✓ **Independence** — no blocking issues.
- ✓ **Reversibility** — local change either way.

**Verdict:** needs-info

**Blocked on, before this becomes ready-for-agent:**
- Pick one path: heuristic-only, bundled small model, piggyback, or hybrid (@reporter / @maintainer). Each has different scope, dependency surface, and binary-size impact.
- State the acceptance bar — manual eyeball on N sessions, fixture-based test suite, or specific quality metric (@reporter).
```

### Example 3 — `needs-info` (maintainer-prereq)

For an enhancement gated on a one-shot cryptographic-identity setup the agent cannot perform:

```markdown
> *This was generated by AI during triage.*

## Triage Assessment

**Category:** enhancement — distribution improvement.
**Scope:** scope:macos, scope:tooling — `tools/build-release.sh`, cask, `site/install.sh`.
**Complexity:** Low — script edits in `tools/build-release.sh` (sign + notarytool + stapler) plus two cleanup edits.

**Readiness:**
- ✓ **Scope** — three named files, no fan-out.
- ✓ **Specification** — exact `codesign` / `notarytool` / `stapler` commands given.
- ✓ **Verifiability** — `spctl -a -t open` and `xcrun stapler validate` plus a clean `brew install --cask`.
- ✓ **Context** — current build-release.sh and cask postflight block both findable.
- ✗ **Independence** — implementation requires Apple Developer Program enrollment + Developer ID cert in keychain + notarytool credentials. The agent has no keychain.
- ✓ **Reversibility** — script edits revertible; the prereq itself is one-time but not destructive.

**Verdict:** needs-info

**Blocked on, before this becomes ready-for-agent:**
- Apple Developer Program enrollment ($99/yr) (@maintainer).
- Developer ID Application certificate generated and installed in the maintainer's macOS keychain (@maintainer).
- App-specific password configured for `notarytool` against the maintainer's Apple ID (@maintainer).
```
