diff --git a/plugins/linear-triage/.claude-plugin/plugin.json b/plugins/linear-triage/.claude-plugin/plugin.json new file mode 100644 index 0000000..33e30cd --- /dev/null +++ b/plugins/linear-triage/.claude-plugin/plugin.json @@ -0,0 +1,21 @@ +{ + "name": "linear-triage", + "version": "0.1.0", + "description": "Claude Code plugin that polls a Linear team's triage queue for issues labeled 'Claude Code', then implements each one end-to-end (branch, PR, tests, status updates).", + "author": { + "name": "Shannon Hu", + "email": "shannon.hu@linear.app" + }, + "homepage": "https://github.com/linear/linear-solutions/tree/main/plugins/linear-triage", + "license": "MIT", + "keywords": ["linear", "triage", "automation", "agent"], + "skills": [ + "skills/linear-triage-setup", + "skills/linear-triage-poller", + "skills/linear-issue-worker" + ], + "requirements": { + "mcp": ["claude_ai_Linear"], + "cli": ["gh", "git"] + } +} diff --git a/plugins/linear-triage/.gitignore b/plugins/linear-triage/.gitignore new file mode 100644 index 0000000..5c77597 --- /dev/null +++ b/plugins/linear-triage/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +node_modules/ +*.log +config.json diff --git a/plugins/linear-triage/README.md b/plugins/linear-triage/README.md new file mode 100644 index 0000000..ce69d09 --- /dev/null +++ b/plugins/linear-triage/README.md @@ -0,0 +1,111 @@ +# Linear Triage Plugin for Claude Code + +Polls a Linear team's triage queue for issues labeled **"Claude Code"** and implements each one end-to-end: reads the issue, plans, creates a worktree on Linear's `gitBranchName`, implements, opens a draft PR, runs tests, and marks the PR ready for review. Every stage transition is posted as a comment on the Linear issue, so the issue stays the single source of truth. + +Built on the [Symphony](https://github.com/openai/symphony) pattern: tracker-as-queue + isolated worktree per issue + agent-per-issue. + +See [`docs/architecture.md`](docs/architecture.md) for a diagram of how the three skills coordinate with the Linear MCP server. + +## Install + +```bash +# 1. Clone the linear-solutions monorepo +git clone https://github.com/linear/linear-solutions ~/Claude/linear-solutions + +# 2. Copy the example config +cp ~/Claude/linear-solutions/plugins/linear-triage/config.example.json ~/.claude/linear-triage-config.json +$EDITOR ~/.claude/linear-triage-config.json # set repo, repoUrl, team + +# 3. Launch Claude Code with the plugin loaded +claude --plugin-dir ~/Claude/linear-solutions/plugins/linear-triage +``` + +> The `--plugin-dir` flag loads a local plugin for the session. To load it every time, add an alias (e.g. `alias claude-triage='claude --plugin-dir ~/Claude/linear-solutions/plugins/linear-triage'`) or pass multiple `--plugin-dir` flags to combine plugins. + +## Prerequisites + +- `gh` CLI authenticated (`gh auth status`) +- `git` available on PATH +- Claude Linear MCP connected (the `mcp__claude_ai_Linear__*` tools must be available) +- A target git repo cloned locally (the plugin will clone for you if missing) + +## Setup + +In Claude Code, run: + +``` +/linear-triage-setup +``` + +This: + +1. Verifies prerequisites +2. Creates the **"Claude Code"** label in Linear (if missing) +3. Registers a `RemoteTrigger` cron `*/15 * * * *` that runs `/linear-triage-poller` + +## Daily use + +Tag any Linear triage issue with the **"Claude Code"** label. Within 15 minutes the poller picks it up, claims it (moves to "In Progress"), and spawns a worker that delivers a draft PR. + +To trigger immediately: `/linear-triage-poller` + +## Configuration + +`~/.claude/linear-triage-config.json`: + +| Key | Purpose | +|---|---| +| `label` | Linear label that opts an issue into automation | +| `team` | Linear team key to scope the poller to (omit for all teams) | +| `repo` | Local path where worktrees are created | +| `repoUrl` | Remote URL — used if `repo` doesn't exist locally | +| `testCommand` | Run inside the worktree after implementation | +| `statusMap.inProgress` | Linear state name to claim issues into | +| `statusMap.inReview` | Linear state name to set when PR is ready | +| `maxConcurrentIssues` | Cap on workers spawned per poll | + +## Permissions (required for background runs) + +The poller fires via `RemoteTrigger` with no human in the loop, so every tool the workers call must be pre-approved — otherwise the background agent stalls on permission prompts that nobody answers. + +The plugin ships a recommended allowlist at `plugins/linear-triage/settings.json` covering the Linear MCP tools, broad `Bash` access, and file tools. Bash is allowed broadly because the worker has to run arbitrary shell commands to explore the repo, implement fixes, and run tests — narrow patterns can't cover an autonomous coding agent. This is the same trust posture as running Claude Code interactively; only enable this for repos you'd be comfortable letting Claude Code modify on its own. + +Merge it into your settings before kicking off background runs: + +```bash +# Option A — copy into project settings (scoped to one repo) +cp ~/Claude/linear-solutions/plugins/linear-triage/settings.json /.claude/settings.local.json + +# Option B — merge into user settings (applies everywhere) +$EDITOR ~/.claude/settings.json # add the entries from the plugin's settings.json under permissions.allow +``` + +If your `testCommand` isn't one of the common runners listed (npm/pnpm/yarn/pytest/cargo/go), add it to the allowlist. After editing, restart your Claude Code session. + +## Troubleshooting + +**Poller isn't firing every 15 minutes** +Check that the `RemoteTrigger` was registered: ask Claude Code to "list my remote triggers" or re-run `/linear-triage-setup`. Triggers only fire while you have a Claude Code session that can receive them — confirm your session is running. + +**"Claude Code" label not found / can't claim issues** +Re-run `/linear-triage-setup` to recreate the label. Verify your Linear MCP connection has write scope (`save_issue`, `create_issue_label`). + +**`mcp__claude_ai_Linear__*` tools unavailable** +Connect the Linear MCP server at [claude.ai/settings/connectors](https://claude.ai/settings/connectors) and restart Claude Code. The plugin cannot read or update issues without it. + +**Worker fails on `gh pr create`** +Run `gh auth status` and `gh auth refresh -s repo` to ensure the CLI has repo scope. The plugin opens PRs as the authenticated `gh` user. + +**Tests fail and the issue is labeled `needs-human`** +Expected behavior — inspect the draft PR, push a fix manually, then remove the label. The poller will not retry an issue that's no longer in triage. + +## Safety + +- Workers only operate inside `repo/.worktrees//` +- Each issue gets a fresh worktree on Linear's `gitBranchName` so the PR auto-links +- PRs always open as **draft**; tests must pass before they're marked ready +- Failing tests label the issue `needs-human` and stop — no merge attempt + +## License + +MIT diff --git a/plugins/linear-triage/config.example.json b/plugins/linear-triage/config.example.json new file mode 100644 index 0000000..2e70dff --- /dev/null +++ b/plugins/linear-triage/config.example.json @@ -0,0 +1,13 @@ +{ + "label": "Claude Code", + "team": "YOUR_TEAM_KEY", + "branchPrefix": "claude", + "repo": "/absolute/path/to/your/repo", + "repoUrl": "https://github.com/YourOrg/your-repo", + "testCommand": "npm test", + "statusMap": { + "inProgress": "In Progress", + "inReview": "In Review" + }, + "maxConcurrentIssues": 3 +} diff --git a/plugins/linear-triage/docs/architecture.md b/plugins/linear-triage/docs/architecture.md new file mode 100644 index 0000000..92d6286 --- /dev/null +++ b/plugins/linear-triage/docs/architecture.md @@ -0,0 +1,80 @@ +# Linear Triage Plugin — Architecture + +How the three skills coordinate with the Linear MCP server and local `gh`/`git`. + +``` + ┌─────────────────────────┐ + │ You (Linear user) │ + │ add "Claude Code" label│ + │ to a triage issue │ + └────────────┬────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────────────┐ +│ LINEAR (cloud) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ +│ │ Triage queue │───▶│ "Claude Code"│ │ Issue comments & │ │ +│ │ │ │ labeled issue│ │ state transitions │ │ +│ └──────────────┘ └──────────────┘ └──────────▲───────────┘ │ +└─────────────────▲──────────────▲────────────────────│────────────────┘ + │ │ │ + list_issues│ save_issue │ save_comment │ + get_issue │ get_user │ create_label │ + │ │ │ + ┌─────┴──────────────┴────────────────────┴──────┐ + │ Linear MCP Server │ + │ (mcp__claude_ai_Linear__*) │ + └─────▲──────────────▲────────────────────▲──────┘ + │ │ │ + ─ ─ ─ ─ ─ ─ ─ ─┼─ ─ ─ ─ ─ ─ ─ ┼─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┼─ ─ ─ ─ + │ │ │ + ┌──────────┴────┐ ┌──────┴───────┐ ┌────────┴─────────┐ + │ SKILL 1 │ │ SKILL 2 │ │ SKILL 3 │ + │ triage-setup │ │ triage-poller│ │ issue-worker │ + │ (one-time) │ │ (every 15m) │ │ (per issue) │ + ├───────────────┤ ├──────────────┤ ├──────────────────┤ + │• check gh/git │ │• list_issues │ │• get_issue │ + │• create label │ │ (label=CC, │ │• save_comment │ + │• register │ │ state= │ │ (stage updates) │ + │ RemoteTrigger│ │ triage) │ │• save_issue │ + │ cron */15 │ │• save_issue │ │ (state→InReview)│ + │ │ │ →InProgress │ │• get_user │ + │ │ │• save_comment│ │ (assignee @) │ + │ │ │ "picked up" │ │ │ + │ │ │• spawn worker│──▶│ (one agent per │ + │ │ │ per issue │ │ issue, parallel)│ + └───────┬───────┘ └──────┬───────┘ └────────┬─────────┘ + │ ▲ │ + │ │ cron fires │ git + gh + │ │ │ (local) + ▼ │ ▼ + ┌──────────────────────────────┐ ┌──────────────────────┐ + │ Claude Code RemoteTrigger │ │ Local repo worktree │ + │ */15 * * * * │ │ .worktrees// │ + │ → /linear-triage-poller │ │ branch=gitBranchName│ + └──────────────────────────────┘ │ → gh pr create │ + │ --draft │ + │ → npm test │ + │ → gh pr ready │ + └──────────┬───────────┘ + │ + ▼ + ┌───────────────┐ + │ GitHub PR │ + │ (auto-linked │ + │ to issue) │ + └───────────────┘ +``` + +## Reading it left-to-right + +- **Skill 1 (`linear-triage-setup`)** — runs once. Talks to Linear MCP to create the label, talks to the Claude Code runtime to register the cron. +- **Skill 2 (`linear-triage-poller`)** — runs on the cron. Queries Linear for labeled triage issues, claims each one (state change + intake comment), then **spawns Skill 3 as a sub-agent per issue** (parallel). +- **Skill 3 (`linear-issue-worker`)** — runs once per issue. Reads the full issue from Linear, does all the local `git`/`gh`/test work in a worktree, and posts status comments back to Linear at every stage. Final step flips the issue to "In Review" and pings the assignee. + +## Key invariants + +- **Linear is the queue and the status board.** All three skills share the Linear MCP server: it's how the plugin reads work (labeled triage issues) and reports progress (comments, state transitions). +- **`gh` and `git` only appear in Skill 3.** Setup and the poller never touch code or GitHub — they only orchestrate. +- **One worker per issue, isolated in a worktree.** Concurrent issues never collide because each gets its own `.worktrees//` directory on Linear's `gitBranchName`. +- **The cron is the only thing that makes it "autonomous".** Without `RemoteTrigger`, the plugin is still usable — you'd just invoke `/linear-triage-poller` manually. diff --git a/plugins/linear-triage/settings.json b/plugins/linear-triage/settings.json new file mode 100644 index 0000000..14d4b4a --- /dev/null +++ b/plugins/linear-triage/settings.json @@ -0,0 +1,23 @@ +{ + "permissions": { + "allow": [ + "mcp__claude_ai_Linear__get_issue", + "mcp__claude_ai_Linear__list_issues", + "mcp__claude_ai_Linear__save_issue", + "mcp__claude_ai_Linear__save_comment", + "mcp__claude_ai_Linear__create_issue_label", + "mcp__claude_ai_Linear__list_issue_labels", + "mcp__claude_ai_Linear__list_issue_statuses", + "mcp__claude_ai_Linear__list_teams", + "mcp__claude_ai_Linear__get_user", + "Bash", + "Task", + "Agent", + "Read", + "Edit", + "Write", + "Glob", + "Grep" + ] + } +} diff --git a/plugins/linear-triage/skills/linear-issue-worker/SKILL.md b/plugins/linear-triage/skills/linear-issue-worker/SKILL.md new file mode 100644 index 0000000..a0e8586 --- /dev/null +++ b/plugins/linear-triage/skills/linear-issue-worker/SKILL.md @@ -0,0 +1,280 @@ +--- +name: linear-issue-worker +description: End-to-end worker for a single Linear issue. Reads the issue, plans an implementation, creates an isolated git worktree, implements the solution, opens a draft PR linked to the Linear issue, runs tests, marks the PR ready for review, and notifies the assignee. Invoked by /linear-triage-poller with an issue ID argument. +--- + +You are the Linear issue worker. You handle one issue start to finish. The issue ID is provided as the argument (e.g., `/linear-issue-worker ABC-123`). Work through each stage sequentially. Post a comment to the Linear issue at every stage transition — the issue is the single source of truth for status. + +**Config:** `~/.claude/linear-triage-config.json` + +--- + +## Stage 1 — INTAKE + +Read `~/.claude/linear-triage-config.json`. Extract: +- `repo` — local path to the repository +- `repoUrl` — remote GitHub URL (e.g., `https://github.com/YourOrg/your-repo`) +- `testCommand` (default: `npm test`) +- `statusMap.inProgress` (default: `"In Progress"`) +- `statusMap.inReview` (default: `"In Review"`) + +Use `repo` as the base path for all git operations in this session. If `repo` or `repoUrl` were passed directly as arguments to this skill, those override the config values. Never fall back to any other directory — if `repo` does not exist locally, clone it from `repoUrl` before proceeding (see Stage 3). + +Load the full issue using `mcp__claude_ai_Linear__get_issue`: +- `id`: the issue identifier +- `includeRelations`: true + +Extract and note: title, description, priority, labels, assignee (id + name), team, status, `gitBranchName` (Linear's generated branch name — use this as the branch in Stage 3), any attachments, any related issues. + +Post comment: +``` +🤖 **Claude Code** — Intake Complete + +Reading issue context. Here's what I understand: +**Goal:** [1-sentence summary of what needs to be done] +**Type:** [Bug / Feature / Task / Other] +**Complexity estimate:** [Low / Medium / High] + +Moving to planning next. +``` + +--- + +## Stage 2 — PLAN + +Read the relevant parts of the codebase at the `repo` path from config to understand where changes are needed. Use Glob and Grep to locate relevant files. Do NOT make any changes yet. + +Formulate a concrete implementation plan: what files to touch, what to add/change/remove, any risks. + +Post comment: +``` +🤖 **Claude Code** — Plan + +**Approach:** +[2-4 bullet points describing the implementation plan] + +**Files to modify:** +[list of file paths] + +**Risks / unknowns:** +[any concerns, or "None identified"] + +Starting implementation now. +``` + +--- + +## Stage 3 — BRANCH + +Create an isolated git worktree for this issue: + +Use the `gitBranchName` from the issue (e.g., `shannon/abc-123`) as the branch name exactly — do not slugify or rename it. Linear uses this name to auto-link the PR back to the issue. + +First, ensure the repo exists locally. If `repo` does not exist, clone it: + +```bash +if [ ! -d "[repo]" ]; then + git clone [repoUrl] [repo] +fi +``` + +Then create the worktree: + +```bash +cd [repo] + +# Fetch latest +git fetch origin + +BRANCH="[gitBranchName from issue]" +ISSUE_ID="[issue identifier in lowercase, e.g. abc-123]" + +# Detect the repo's default branch (don't assume "main") +DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') +if [ -z "$DEFAULT_BRANCH" ]; then + # Fallback: ask the remote directly, then cache for future runs + DEFAULT_BRANCH=$(git remote show origin | awk '/HEAD branch/ {print $NF}') + git remote set-head origin "$DEFAULT_BRANCH" 2>/dev/null || true +fi + +# Create worktree using Linear's branch name, based on the default branch +mkdir -p .worktrees +git worktree add ".worktrees/${ISSUE_ID}" -b "${BRANCH}" "origin/${DEFAULT_BRANCH}" +``` + +Post comment: +``` +🤖 **Claude Code** — Branch Created + +Working on isolated branch: `[gitBranchName]` +Worktree: `[repo]/.worktrees/[issue-id]/` + +Implementation starting. +``` + +--- + +## Stage 4 — IMPLEMENT + +Work inside the worktree directory: `[repo]/.worktrees/[issue-id]/` + +Implement the plan from Stage 2. Follow the existing code patterns and conventions you found in the codebase. Make commits as logical units of work: + +```bash +cd [repo]/.worktrees/[issue-id] +git add [files] +git commit -m "[concise description of change]" +``` + +Post a comment after each meaningful commit group (not every commit — group related changes): +``` +🤖 **Claude Code** — Implementing + +[Brief description of what was just implemented] +Commit: `[short hash]` +``` + +When implementation is complete, push the branch: +```bash +cd [repo]/.worktrees/[issue-id] +git push origin [branch name] +``` + +--- + +## Stage 5 — DRAFT PR + +Create a draft pull request: + +Extract `owner/name` from `repoUrl` by stripping `https://github.com/` (e.g., `YourOrg/your-repo`). Pass it as `--repo` to ensure the PR always lands in the correct GitHub repo regardless of what the local git remote is configured as. + +```bash +cd [repo]/.worktrees/[issue-id] +gh pr create \ + --repo [owner/name from repoUrl] \ + --draft \ + --title "[issue title]" \ + --body "$(cat <<'EOF' +Fixes [ISSUE-IDENTIFIER] + +## Summary +[What this PR does — 2-3 sentences] + +## Approach +[Key implementation decisions from Stage 2 plan] + +## Testing +[What was tested and how] + +--- +🤖 Implemented by Claude Code +EOF +)" +``` + +Capture the PR URL from the output. + +Post comment to Linear issue: +``` +🤖 **Claude Code** — Draft PR Created + +[PR Title](PR_URL) + +The PR description includes `Fixes [ISSUE-ID]` so Linear will auto-link it once the GitHub integration syncs. Running tests now. +``` + +--- + +## Stage 6 — TEST + +Run the `testCommand` from config inside the worktree: + +```bash +cd [repo]/.worktrees/[issue-id] +[testCommand] 2>&1 +``` + +**If tests pass:** +Post comment: +``` +🤖 **Claude Code** — Tests Passing ✅ + +All tests pass. Marking PR as ready for review. +``` + +**If tests fail:** +Post comment: +``` +🤖 **Claude Code** — Tests Failing ⚠️ + +Tests did not pass. Human review needed before this can be merged. + +**Failures:** +[paste relevant test output — truncate to 20 lines if long] + +The PR remains as a draft. Labeling this issue `needs-human` for manual follow-up. +``` + +Then call `mcp__claude_ai_Linear__save_issue` to add label `needs-human` (if it exists — if not, skip the label). **Stop here** — do not proceed to Stage 7. + +--- + +## Stage 7 — MARK READY FOR REVIEW + +```bash +gh pr ready [PR number or URL] +``` + +Call `mcp__claude_ai_Linear__save_issue`: +- `id`: issue identifier +- `state`: value of `statusMap.inReview` from config (default: `"In Review"`) + +--- + +## Stage 8 — NOTIFY + +Retrieve the issue's assignees. Compose the final comment. + +**If the issue has an assignee:** +Use `mcp__claude_ai_Linear__get_user` to look up the assignee's name. + +Post comment: +``` +🤖 **Claude Code** — Ready for Review 🎉 + +@[assignee name] — implementation is complete and the PR is ready for your review. + +**PR:** [PR Title](PR_URL) +**Branch:** `[branch name]` + +**What was done:** +[3-5 bullet summary of changes made] + +**To review:** +1. Open the PR link above +2. Check the implementation against the issue description +3. Approve and merge, or request changes +``` + +**If no assignee:** +Post comment: +``` +🤖 **Claude Code** — Ready for Review 🎉 + +Implementation is complete. No assignee found on this issue — please assign someone to review the PR. + +**PR:** [PR Title](PR_URL) +**Branch:** `[branch name]` + +**What was done:** +[3-5 bullet summary of changes made] +``` + +--- + +## Error handling + +- **Git errors** (merge conflict, push rejected): post comment describing the error, set issue label `needs-human`, stop. +- **gh CLI errors**: post comment with the error output, stop. +- **Any unrecoverable error**: always post a final comment explaining what failed and what state the work is in, so a human can pick up from where you left off. Never leave the issue silent. +- **Worktree cleanup on failure**: if you stop early, note the worktree path in the comment so it can be cleaned up manually: `git worktree remove [repo]/.worktrees/[issue-id] --force` diff --git a/plugins/linear-triage/skills/linear-triage-poller/SKILL.md b/plugins/linear-triage/skills/linear-triage-poller/SKILL.md new file mode 100644 index 0000000..4880f00 --- /dev/null +++ b/plugins/linear-triage/skills/linear-triage-poller/SKILL.md @@ -0,0 +1,72 @@ +--- +name: linear-triage-poller +description: Poll the Linear triage queue every 15 minutes for issues labeled "Claude Code" and delegate each one to /linear-issue-worker. Intended to be triggered by the RemoteTrigger registered via /linear-triage-setup. Can also be run manually to trigger immediately. +--- + +You are the Linear triage poller. Your job is to find eligible issues and hand them off to worker agents. Be fast and methodical — this runs on a 15-minute heartbeat. + +## Step 1 — Load config + +Read `~/.claude/linear-triage-config.json`. Extract: +- `label` (default: `"Claude Code"`) +- `team` (default: null — means all teams) +- `maxConcurrentIssues` (default: 3) +- `statusMap.inProgress` (default: `"In Progress"`) +- `repoUrl` (remote repo URL — passed to worker) +- `repo` (local repo path — passed to worker) + +If the config file is missing, stop and tell the user to run `/linear-triage-setup` first. + +## Step 2 — Query the triage queue + +Call `mcp__claude_ai_Linear__list_issues` with: +- `state`: `"Triage"` +- `label`: value of `label` from config +- `team`: value of `team` from config (omit if null) +- `limit`: value of `maxConcurrentIssues` +- `orderBy`: `"createdAt"` + +If zero issues returned: log "No eligible triage issues found." and stop cleanly. + +## Step 3 — Process each issue + +For each issue returned, in order of priority (1=Urgent first, then 2=High, 3=Normal, 4=Low): + +### 3a. Claim the issue (prevent double-pickup) +Call `mcp__claude_ai_Linear__save_issue` with: +- `id`: the issue identifier (e.g., `"ABC-123"`) +- `state`: value of `statusMap.inProgress` from config + +### 3b. Post intake comment +Call `mcp__claude_ai_Linear__save_comment` with: +- `issueId`: the issue identifier +- `body`: +``` +🤖 **Claude Code** — Picked Up + +This issue has been pulled from the triage queue and delegated to Claude Code for implementation. Work is starting now. + +_Priority: [issue priority] · Team: [issue team]_ +``` + +### 3c. Spawn the worker +Use the Agent tool to spawn a new agent with: +- `subagent_type`: `"general-purpose"` +- `description`: `"Linear issue worker: [issue id] — [issue title]"` +- `prompt`: `"Run the /linear-issue-worker skill for Linear issue [issue identifier]. The issue is: [title]. Full context: [description first 500 chars]. Local repo path: [repo from config]. Remote repo URL: [repoUrl from config]. Git branch name: [gitBranchName from the issue]. Use the local repo path for all git operations — clone from the remote URL first if the local path does not exist."` + +Spawn all workers in a single parallel message if there are multiple issues. + +## Step 4 — Log summary + +After spawning all workers, output: +``` +Linear Triage Poller — [timestamp] +Issues picked up: [N] +[list each: ISSUE-ID — title — priority] +``` + +## Error handling + +- If `save_issue` fails for an issue (e.g., state not found): skip it, log the error, continue to next issue. Do NOT spawn a worker for an issue that wasn't successfully claimed. +- If a worker spawn fails: post a comment on the issue: "⚠️ **Claude Code** — Worker spawn failed. Issue returned to triage queue." Then call `save_issue` to set state back to `"Triage"`. diff --git a/plugins/linear-triage/skills/linear-triage-setup/SKILL.md b/plugins/linear-triage/skills/linear-triage-setup/SKILL.md new file mode 100644 index 0000000..45c9295 --- /dev/null +++ b/plugins/linear-triage/skills/linear-triage-setup/SKILL.md @@ -0,0 +1,75 @@ +--- +name: linear-triage-setup +description: One-time setup for the Linear triage automation workflow. Verifies prerequisites, creates the "Claude Code" label in Linear, and registers the RemoteTrigger that polls every 15 minutes. Run this once before using /linear-triage-poller. +--- + +You are setting up the Linear Triage Automation system. Execute each step in order and report the result of each. + +## Step 1 — Verify prerequisites + +Run these checks in parallel: + +```bash +# 1a. gh CLI authenticated? +gh auth status + +# 1b. Config file exists? +cat ~/.claude/linear-triage-config.json 2>/dev/null || echo "CONFIG_MISSING" +``` + +If `gh auth status` fails: stop and tell the user to run `gh auth login` first. + +If `CONFIG_MISSING`: stop and tell the user to copy the example config: + +```bash +cp /config.example.json ~/.claude/linear-triage-config.json +$EDITOR ~/.claude/linear-triage-config.json +``` + +They must set `repo`, `repoUrl`, and `team` before continuing. Re-run `/linear-triage-setup` once the config is in place. + +## Step 2 — Validate config + +Read `~/.claude/linear-triage-config.json` and confirm these fields are set (not the placeholder values from the example): + +- `repo` — exists OR `repoUrl` is set so it can be cloned +- `team` — set to a real Linear team key (not `"YOUR_TEAM_KEY"`) +- `label` — defaults to `"Claude Code"` if absent + +If `repo` does not exist locally and `repoUrl` is unset, stop and ask the user to set `repoUrl`. + +If `repo` exists locally, verify it has a git remote: + +```bash +cd && git remote -v +``` + +If no remote: stop and tell the user to configure one. + +## Step 3 — Create the trigger label in Linear + +Use `mcp__claude_ai_Linear__create_issue_label` to create a label named the value of `label` (default `"Claude Code"`) with color `#7C3AED` (purple). If the tool errors with "already exists", that's fine — skip and continue. + +## Step 4 — Register the RemoteTrigger + +Use the `schedule` skill or `RemoteTrigger` tool to create a scheduled remote agent with: +- **Cron**: `*/15 * * * *` +- **Prompt**: `/linear-triage-poller` +- **Description**: "Poll Linear triage queue and delegate eligible issues to Claude Code" + +## Step 5 — Confirm setup + +Print a summary: +``` +✅ Linear Triage Automation — Setup Complete + +Prerequisites: ✅ gh CLI authenticated / ✅ git remote configured +Config: ~/.claude/linear-triage-config.json +Label created: "