skills/linear-grill/SKILL.md
Grill a collaborator's Linear tickets and move every processed ticket to where it belongs. Resolves the board from the repo's .ro-linear.json, reads the collaborator's Backlog / Ready-for-agent issues, then per ticket either posts 3-5 decision-extracting questions (state moves to "Question for <name>") or confirms it build-ready (state moves to "Grilled", the gate /ro:linear-nightshift consumes); shipped-and-confirmed tickets close as Done. The async-collaborator counterpart of /ro:day-shift for people who never touch GitHub. Triggers on "grill linear", "grill eoin's tickets", "linear grill", "add questions to the linear tickets", "/ro:linear-grill".
npx skillsauth add RonanCodes/ronan-skills linear-grillInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Process a collaborator's Linear tickets so every one ends the run in one of three places: Question for <name> (we asked, they answer async in comments), Grilled (build-ready, the gate /ro:linear-nightshift consumes), or Done (shipped and confirmed). The agent moves the tickets; the collaborator only ever answers in comments (dragging a ticket to "Ready for agent" is their priority nudge, not a build-ready signal). Pairs with /ro:linear-nightshift (consumes Grilled) and /ro:linear-update (closes the loop on deploy).
Part of the hybrid one-way sync model (locked 2026-06-11, see ronan-skills#61): Linear is where collaborator stories are born and prioritised; GitHub is the execution layer. This skill is the grilling step that makes a born story buildable.
https://api.linear.app/graphql. Header Authorization: <key> — NO Bearer prefix for personal API keys. Key: LINEAR_API_KEY in ~/.claude/.env.LINEAR_API_KEY, never the OAuth mcp__linear-server__* tools. The key belongs to the dedicated RoBot service-account user, so comments / labels / state-moves made with it are attributed to RoBot — distinct from Ronan, and filtered by /ro:linear-listen so they never re-trigger. The MCP tools authenticate as Ronan; a write through them would post as Ronan, break attribution, AND trip the listener loop (a "Ronan" comment triggers a run). Use the MCP tools only for read-only convenience if at all; do every comment / issueUpdate / issueCreate via curl + LINEAR_API_KEY.@[displayName](user-uuid) in markdown bodies renders as a real mention and notifies. Get the uuid via { users { nodes { id name displayName } } }.issue.creator and @-mention them in grill questions, validation, and deploy comments (fall back to .ro-linear.json collaborator only if the creator can't be resolved). A ticket Ronan filed gets Ronan tagged (and the technical register); a ticket Eoin filed gets Eoin (plain-language, domain-only). The collaborator in config is just the default board owner, not a hardcoded mention target. Match the register to the creator: technical for Ronan, plain-language for the non-technical collaborator.subscriberIds: [<their-uuid>] so they're notified too.team(id){ states { nodes { id name type } } }, match by name (Backlog / Question for <name> / Grilled / In Progress / Interview / Holdoff / Done).LINEAR_API_KEY. Authorship is therefore intrinsic — every agent comment/label/move already shows "RoBot" as the actor in Linear's UI, and /ro:linear-listen filters the agent's own events by that user id. Do NOT append the old *🤖 RoBot (agent)* footer and do NOT apply a 🤖 RoBot label for loop-safety — both are obsolete now that identity comes from the account. (Historic tickets may still carry the label; harmless, ignore it.) Write plainly, as RoBot.No linear- skill ever moves a ticket to Done on the agent's own judgement.* Done means the collaborator has tested the shipped work and said they're happy with it — nothing less. The default path to Done is the Interview gate (/ro:linear-update hands them the built work + acceptance criteria; they drag it to Done). An agent may set Done only when the collaborator has, in a comment, explicitly confirmed they tested it and it does what they wanted — a clear "tested, all good", not a vague "thanks", not silence, not "looks good" without trying it. If there's any doubt they actually used it, leave it in Interview and ask them to confirm they've tried it. This rule overrides every "→ Done" outcome below and in the sibling skills.
.ro-linear.json)The current repo maps to its Linear board via a committed .ro-linear.json at the repo root:
{
"teamKey": "NUT",
"collaborator": "econnolly321",
"owner": "[email protected]",
"reworkLabel": "🔁 Rework",
"reviewLabel": "🚨 Technical Review Needed",
"states": { "questions": "Question for Eoin", "grilled": "Ready to Build", "holdoff": "Hold Off", "interview": "Ready to Test" }
}
owner (name or email) is the human who, alongside the collaborator, may prompt the agent — /ro:linear-listen setup resolves it to ownerId. Use a value that uniquely matches the human owner and NOT the RoBot user (the agent's service account) — an email is safest, since RoBot may share the owner's name or domain. The old robotLabel / humanReviewLabel keys are gone: the agent is now its own RoBot Linear user, so authorship needs no label (see § "Agent identity").
The states keys are fixed concepts (questions / grilled / holdoff / interview); the values are the collaborator-facing column names on the board, which you pick to read clearly for a non-technical person. Eoin works the columns (not labels), so name them for him: the nutmeg board uses Ready to Build (the grilled/build-ready queue), Hold Off (parked), and Ready to Test (the interview/validation gate the collaborator owns — built work lands there for them to test and accept → Done, or kick back → rework). The collaborator's priority-nudge column is Up Next (unstarted-type). All skills match states by these config values + type, never a hardcoded string, so renaming a column only means updating this file. See /ro:linear-update § "Interview" and /ro:linear-run § "Rework".
Labels are agent bookkeeping (Eoin ignores them): 🔁 Rework (bounced from validation), 🚨 Technical Review Needed (3+ failed rework attempts — a technical human should look, though the agent keeps retrying; see /ro:linear-run § Rework), linear-mirror (has a GitHub execution mirror). Use labels freely for agent state; never make Eoin's flow depend on one. (The retired 🤖 RoBot / 🧑 RoHuman labels are no longer applied — agent-authored events are now identified by the RoBot user, and review tickets reach Ronan by being assigned to him, see /ro:linear-run § "Review tickets for Ronan".)
If the file exists, skip discovery entirely. If it doesn't: infer the team (repo name vs team names, or the one obvious collaborator team), ask when ambiguous, then WRITE the file and commit it so the next run starts instantly. Not in a repo, or no plausible team: ask.
--team <KEY> overrides the config file; both override workspace inference.--collaborator <name>: config file, else the team's non-Ronan member with created issues (match on users.displayName).--issue <ID> list if given; else ALL team issues in backlog-type or unstarted-type states except the grilled and holdoff states (so: Backlog, the nudge column e.g. "Up Next", Question for <name>), regardless of creator. The grilled state is the nightshift's build queue and holdoff is parked — both stay untouched unless named explicitly with --issue. Match by config value + state type, never a hardcoded name. Ronan files proposal/menu tickets on the collaborator's board too (e.g. NUT-61, a "which of these would you use?" ticket); a creator filter silently drops their answers to those. Tickets already in Grilled are the nightshift's queue, leave them alone.Edits count as new. Classify on each comment's updatedAt, not createdAt. If a comment you previously read and acted on now has a newer updatedAt (the collaborator edited it — e.g. they went back and answered a question inline, or changed their answer), treat it as unread and re-process the ticket from that comment. Never assume a comment you've seen before still says what it said; an edit to an already-read message means re-read it. Track the last-processed updatedAt per ticket (the listener passes it; a standalone run reads it from the latest grill/RoBot comment's timestamp) so an unchanged board is a no-op but an edited comment isn't missed.
Before classifying, load the candidate's full context per /ro:linear-run § "Load full ticket context" and emit the context-load receipt there (mandatory gate — no grill without it): the whole issue description, every comment oldest→newest (not just the latest grill), and every image/attachment on the body and in comments. The most recent comments are the most authoritative — a later comment supersedes an earlier one (or the original ask) where they conflict. Don't grill a question the thread or a pasted screenshot already answers.
Read each candidate's comments, latest grill comment first:
Before writing a single question, look at the repo the way the ticket implies — the relevant route / store / component, the patterns already in use, what already ships. Questions must be informed by what exists: "you already have a fixtures picker; should this live there or as its own page?" beats a generic "where should this go?". Reading the code is also how you separate domain from technical (below) — most "where/how" uncertainty is technical and you resolve it yourself by looking.
Only ask the collaborator about domain / product decisions — things only they can decide: which competitions matter, what the user should see, product behaviour, scope, what's worth paying for. Technical decisions (where a file lives, which library, the data model, framework choice) you decide yourself in the working chat with Ronan — never on the Linear ticket. If a ticket's only open questions are technical, don't grill — decide and confirm build-ready. Grill only when a real domain fork blocks the build.
Mark each question. Mandatory = blocks the build (a real product fork with no safe default). Optional = refines it but has a sensible default you'll use if unanswered — state the default inline (Optional — default: monthly unless you say otherwise). This lets the collaborator answer just the mandatory ones and still unblock the build; unanswered optionals become stated assumptions. The /ro:linear-nightshift answer-gate only blocks on unanswered mandatory questions.
(mandatory) or (optional — default: …), each one a real domain choice. Never generic "can you clarify?", never a technical question you could answer by reading the code.@[<creator displayName>](<creator uuid>) — a few questions so this gets built the way you picture it:. No footer — the comment posts as the RoBot user (§ "Agent identity").Post via commentCreate. With --dry-run, print the would-be comments instead.
Eoin has found that builds sometimes miss pieces of what he asked for. So no ticket is built until its Acceptance Criteria are written down and the creator has agreed to them. Order, per ticket:
questions state (it's back with them to sign off)./ro:linear-update — validation checks the exact list they signed off, so nothing silently gets dropped.The sequence is: (open questions →) answers → post AC → they agree → build. If there were no questions, skip straight to posting AC and awaiting agreement; never jump from a fresh ticket to building.
The board is the report; never leave a processed ticket where you found it. Every move posts a plain comment (as the RoBot user — no label, no footer). Four outcomes, each a comment + issueUpdate of stateId:
questions from the config (e.g. "Question for Eoin"; create it, type backlog, if missing).questions (back with the creator to sign off). This is NOT build-ready.
2b. Build-ready (the creator has explicitly agreed the AC) → post a short confirmation that locks it in ("Locked in, building to the AC above → Ready to Build.") → state grilled (create it, type unstarted, if missing). Creating a state that already exists fails with "duplicate workflow state": on that error, look it up with workflowStates(includeArchived: true, filter: {team, name}) instead. Only AC-agreed tickets reach this state.holdoff from the config (create it, type backlog, if missing). A holdoff ticket drops out of every other run's target set until the collaborator moves it back; don't grill it, don't build it.Print per ticket: questioned / grilled (build-ready) / done / held off / skipped (open grill). The cycle: the collaborator answers in comments, the NEXT grill run promotes answered tickets to Grilled, and /ro:linear-nightshift drains Grilled.
| Condition | Behaviour |
|---|---|
| No LINEAR_API_KEY | Fail loudly, point at ~/.claude/.env |
| Collaborator not found in workspace | List members, ask |
| Mention renders literally (@[name](id) as text) | Fallback: profile-URL link form + subscriberIds; note it in the report |
/ro:linear-run — the single front door that chains this skill → build → close, and adds the idea-ticket pass; run that to do the whole loop/ro:linear-nightshift — consumes the Grilled tickets this skill matured/ro:linear-update — closes the loop on deploy/ro:day-shift — the GitHub-native sibling for Ronan's own backlogtesting
--- name: linear-pipeline description: The Fable orchestrator for a single dispatched Linear ticket. Holds almost no context itself; it receives `--issue <ID> --detached`, decides the stage sequence, and fans out a sub-agent per stage, passing forward only each stage's artifact (never re-derived, never inlined into its own context). Step zero, before any planning or stage routing, is a boundary triage against `canon/security-boundary.md` (#199): a match tags Ronan Connolly and stops the run, no
development
--- name: in-your-face description: Capture a chat-only answer into a durable artifact (markdown + HTML, PDF when cheap) and launch it automatically so the user cannot miss it. Use when user says "in your face", "don't let me lose this", "save that answer", "make that durable", or right after answering a substantive side question (a recipe, comparison, how-to, or generated prompt) that would otherwise die with the context. category: workflow argument-hint: [--no-open] [--vault <short>] [hint of
tools
One-shot headless OpenAI Codex CLI calls for background/admin AI tasks — summaries, classification, extraction, admin glue. The default engine for anything that runs AI constantly in the background (daemon-driven, per-event), because it bills the flat ChatGPT subscription instead of Claude usage or per-token API spend, and it keeps working while Claude is rate-limited. NEVER for coding — coding stays Claude. Use when a skill or daemon needs a cheap always-on AI call, when the user says "use codex", "ask codex", "codex as backup", or when building a background summarizer/classifier into a listener or loop. Reads auth from ~/.codex/auth.json (ChatGPT account, no API key).
research
Turn a warranty rejection, repair quote, or RMA email into a cited decision brief — legal read (NL/EU consumer law), is the part user-serviceable, live part and new-unit prices, repair-vs-DIY-vs-new economics, before-you-send-it checklist, deadlines. Use when the user pastes or screenshots a repair quote, warranty rejection, "not covered" email, onderzoekskosten fee, or asks "should I repair or replace this".