| name | ir:triage |
| description | Autonomously triage GitHub issues on ingo-eichhorst/Irrlicht. Diagnostic-only: scores each issue against a 7-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).
It has exactly one carve-out: triage may prescribe the instrument, never the fix. When the Observability axis fails, the brief names how to make the behavior visible — record a fixture, emit an event, add a snapshot scene (see "Observability cost rule" and "Instrument menu" below). That is verification infrastructure, not a solution to the issue's problem: it constrains what evidence will exist, not what the code should do. Nothing else about the diagnostic-only rule is relaxed.
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:
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
- 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.
- 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.
- Score the 7 axes (Readiness Rubric below). Each axis is ✓ or ✗ with a one-liner. The verdict falls out of the scoring — except a ✗ on Observability, which is priced first and only blocks at High cost.
- Justify every label — category, priority, scope, flags, milestone. One line each. If you can't justify, don't apply.
- Post the assessment using the template below.
- Label and milestone in a single
gh issue edit call (one round-trip per issue). Close if wontfix.
Readiness Rubric (7 axes)
Each axis scores ✓ (pass) or ✗ (fail) with a one-line justification grounded in the issue body. Any ✗ on the first six axes → needs-info. All ✓ → ready-for-agent. Observability is the exception — a ✗ there is priced, not fatal; see its cost rule below.
| 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. |
| Observability | The behavior can be reproduced and observed with what exists today — a recording, replay scenario, events.jsonl, an of query, a metric, a CLI output, a snapshot test. | Reproduction is possible but the effect is invisible (no durable artifact to assert on), or reproduction itself needs a rig that doesn't exist. |
| 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. |
Verifiability vs Observability
These two are adjacent by design and must not be scored as one. Verifiability names the measurement; Observability asks whether the instrument to take it exists today.
"A replay scenario asserts working → ready within 500ms" is a fine measurement — but if no recording of that adapter × scenario exists, there is nothing to replay. ✓ Verifiability with ✗ Observability is the common and interesting case: everyone agrees what "done" looks like, and nothing can currently show it. If both axes' one-liners say the same thing, you've scored the same concern twice — Verifiability is about the criterion, Observability about the evidence.
Observability cost rule
Unlike the other six axes, a ✗ on Observability does not route to needs-info on its own. Most gaps in this repo are closed by an agent running one command; bouncing those to a human is ceremony. Price the gap instead, using the instrument menu below:
- Low — the instrument exists; the ticket just runs it (record a fixture, replay a transcript). Verdict unchanged.
- Medium — extend an existing surface: a new
lifecycle.Kind, a session.SessionMetrics field, a new snapshot scene. Verdict unchanged.
- High — nothing in the menu fits; the instrument must be built from scratch, so it's a ticket in its own right. →
needs-info, blocked on a separate observability issue.
The menu is the whole test: if a row fits, the gap is Low or Medium and the verdict is whatever the other six axes say (typically ready-for-agent) — the Observability plan rides in the brief as the ticket's first step, so the agent records the fixture then fixes the bug instead of discovering the gap mid-flight. Only High blocks. The menu isn't exhaustive; if nothing fits but the instrument is still a small, obvious addition, that's Medium — reserve High for a rig that's genuinely its own project.
Instrument menu
When Observability is ✗, name the cheapest surface from this table. Reach for what exists before inventing anything; the cost column feeds straight into the cost rule above.
| Gap | Reach for | Cost |
|---|
| Adapter behavior with no recording | of record run --agent <a> --scenario <s> (via ir:onboarding-factory); find the gap with matrix query --cells --agent <a> | Low |
| Recorded, but nothing asserts on it | an expected.jsonl phase; of verify --agent <a> --scenario <s> | Low |
| State-machine / timing bug on an existing transcript | go run ./tools/onboarding-factory/cmd/replay <transcript>; tools/replay-fixtures.sh | Low |
| Session-lifecycle / ghost bug | go run ./tools/onboarding-factory/cmd/replay --ghosts <events.jsonl> | Low |
| Web dashboard rendering | npm run snapshot in platforms/web/ → readable HTML under snapshots/out/ | Low |
| Effect invisible in the event stream | a new lifecycle.Kind in core/domain/lifecycle/event.go, asserted via expected.jsonl | Medium |
| Effect invisible in metrics | a new session.SessionMetrics field, asserted via the spec's observations block | Medium |
| macOS view rendering | a scene in platforms/macos/Tests/*SnapshotTests.swift (swift test; not CI-gated) | Medium |
| Nothing above fits | the rig doesn't exist — file it as its own issue and block this one | High |
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 — the first six axes pass. Default heavily here. A ✗ on Observability at Low/Medium cost still lands here, with the plan as the ticket's first step. Visual sign-off and final review happen at PR time, not as triage barriers.
-
needs-info — one or more of the first six axes fail, or Observability fails at High cost (the instrument is a project in its own right). Two flavors:
-
Reporter-side — missing repro / outcome / verifiability / scope. Brief addresses @reporter.
-
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 exists, this becomes ready-for-agent."
-
Observability-prereq — Observability failed at High cost: the instrument must be built before the fix is even visible. Brief addresses the maintainer and names the child issue the instrument needs.
Things that do not qualify as ✗ on any of the first six axes: aesthetic judgment ("matches the design system"), Xcode .pbxproj edits, scope-commitment for multi-phase arcs with a phase plan in the body. "Needs a VM at the keyboard" and manual hardware testing are Observability questions — score them on that axis and price the instrument; they are not a Verifiability ✗, and at Low/Medium cost they don't block at all.
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:
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:
LATEST_MM=$(gh release view --repo ingo-eichhorst/Irrlicht --json tagName --jq .tagName \
| sed -E 's/^v//; s/^([0-9]+\.[0-9]+).*/\1/')
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:
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.
> *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>
- ✓/✗ **Observability** — <one-liner>
- ✓/✗ **Context** — <one-liner>
- ✓/✗ **Independence** — <one-liner>
- ✓/✗ **Reversibility** — <one-liner>
<!-- only when Observability is ✗ — omit the whole block otherwise. Comes BEFORE the
verdict because Cost decides it: Low/Medium leave the verdict alone, High blocks. -->
**Observability plan:**
- **Gap:** <what can't be seen today, one line>
- **Instrument:** <cheapest surface from the menu, named concretely>
- **Cost:** <Low | Medium | High> — <one-liner; High → file as its own issue and block this one>
**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.
The single exception is the Observability plan — the instrument, never the fix. "Record the codex × user-esc-interrupt cell first" is in bounds; it says what evidence will exist. "Short-circuit the debounce when the event is terminal" is out of bounds; it says what the code should do. If your Instrument line would survive being pasted into a different fix for the same bug, it's the right altitude.
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)
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):
> *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.
- ✓ **Observability** — the OpenCode × multi-turn cell is already recorded; `tools/replay-fixtures.sh` shows the 2.5s tail today.
- ✓ **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:
> *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.
- ✓ **Observability** — generated copy is inspectable per-session against existing recordings; no new instrument needed to see the output.
- ✓ **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:
> *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`.
- ✓ **Observability** — nothing needs building to see the result; the verification tooling ships with macOS.
- ✓ **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).
Example 4 — ready-for-agent with a failed Observability axis
The case the axis exists for: a well-specified adapter bug that nothing on disk can currently show. Every other axis passes and the gap is one command wide, so the ✗ doesn't block — it becomes the ticket's first step.
> *This was generated by AI during triage.*
## Triage Assessment
**Category:** bug — a codex background subagent shows up as a root session; the dashboard never links it to its parent.
**Priority:** Medium — user-visible orphan in the session list; no data loss.
**Scope:** scope:codex — child-session discovery, localized to the codex adapter.
**Milestone:** v0.6 — Priority-Medium → next cycle.
**Complexity:** Low — one adapter, one concern (`ParentSessionID` on the child).
**Readiness:**
- ✓ **Scope** — one adapter file named in the body.
- ✓ **Specification** — desired outcome stated: the child session carries its parent's id.
- ✓ **Verifiability** — an `expected.jsonl` phase asserting `ParentSessionID` on the spawned session.
- ✗ **Observability** — the codex × `background-subagent` cell is `pending-record`, so there is no transcript to replay and nothing that shows the orphan happening.
- ✓ **Context** — the discovery path and `ParentSessionID` are both cited.
- ✓ **Independence** — no blocking issues.
- ✓ **Reversibility** — local adapter change.
**Observability plan:**
- **Gap:** the bug is real in a live session but leaves no artifact — this adapter × scenario has never been recorded.
- **Instrument:** record the cell — `of record run --agent codex --scenario background-subagent` (via `ir:onboarding-factory`), then assert the link in `expected.jsonl`.
- **Cost:** Low — the rig exists; this is one command, and it runs before the fix.
**Verdict:** ready-for-agent