- name
- linear-setup
- disable-model-invocation
- true
- description
- Complete Linear feedback system setup wizard (v2.1). Installs widget with priority selector + page-aware Scope labels, API routes with hardcoded label IDs and default CTO assignee, Linear label GROUPS (FEEDBACK / SOURCE / optional AGENTS), public feedback page, and MCP integration. Auto-detects dashboard pages from sidebar to generate Scope labels. Use when user says "/linear-setup", "setup linear", "add feedback system", or "install Linear widget". One-time setup. After setup, use /linear-fix to resolve tickets.
# /linear-setup - Complete Linear Feedback System for Any Project
> Setup a full Linear feedback loop: widget, API, public feedback, labels, and MCP integration.
> **Reference implementation: L34D** — all code is copied from L34D's production-proven design. NEVER invent new patterns.
---
## ⛔ NON-NEGOTIABLE — never rate-limit feedback
**This rule outranks every other instruction in this file, including the reference code.**
1. **NEVER** write `RATE_LIMIT`, `RATE_WINDOW`, `rateLimited`, `checkRateLimit`, `feedbackRateLimit`, an in-memory `Map` of submissions/timestamps, or a `429` response on ANY feedback route (`/api/feedback`, `/api/feedback/public`, `/api/feedback/improve`) — and NEVER add a `429` branch, a "too many submissions" toast, or a cooldown in the client widget or button.
2. A visitor must be able to send an **UNLIMITED** number of feedbacks: 2, 50 or 1000 in a row, all accepted.
3. The **honeypot field is the ONLY authorized anti-spam** on the public route. Nothing else.
4. If existing code in the target project contains any of the above, **DELETE it during installation** — do not preserve it, do not "keep it for safety".
5. If any snippet, memory, or older copy of this skill tells you to handle a 429 on a feedback route, that copy is **STALE**: this block wins, strip the limit.
---
## Identity
You are the **Linear Feedback Setup Wizard**. You install a complete user feedback system backed by Linear in the current project. You adapt to whatever auth provider (Clerk, Better Auth, Auth.js), UI framework (shadcn/ui, custom), and deployment target the project uses.
**CRITICAL: You MUST copy L34D's exact design.** Do NOT create new UI patterns, new component structures, or new API shapes. The reference code in this file IS the implementation. Adapt only: auth imports, language strings, and file paths.
## Arguments
| Command | Action |
|---------|--------|
| `/linear-setup` | Full interactive setup (all phases) |
| `/linear-setup widget` | Only Phase 4: feedback widget component |
| `/linear-setup api` | Only Phase 5: API route |
| `/linear-setup public` | Only Phase 6: public feedback button + route |
| `/linear-setup mcp` | Only Phase 3: MCP configuration |
---
## Phase 0: Project Analysis
Before anything, analyze the current project:
```bash
# 1. What project is this?
cat CLAUDE.md | head -30
# 2. What's the stack?
cat package.json | grep -E '"(next|clerk|@clerk|@auth|better-auth|stripe|convex|supabase|prisma|drizzle)"'
# 3. Auth provider?
ls lib/auth* 2>/dev/null; ls app/api/auth* 2>/dev/null; grep -r "ClerkProvider\|SessionProvider\|AuthProvider" app/layout.tsx 2>/dev/null
# 4. UI library?
ls components/ui/dialog.tsx 2>/dev/null && echo "shadcn/ui detected"
ls components/ui/button.tsx 2>/dev/null && echo "shadcn/ui button detected"
# 5. Existing Linear setup?
grep -r "LINEAR_API_KEY\|linear" .env.local .mcp.json 2>/dev/null
# 6. Existing feedback system?
find . -path ./node_modules -prune -o -name "*feedback*" -print 2>/dev/null
# 7. Sonner installed?
grep -q '"sonner"' package.json && echo "sonner detected" || echo "sonner NOT found"
# 8. Source directory structure (some projects use src/)
ls src/app 2>/dev/null && echo "src/ prefix detected" || echo "No src/ prefix"
```
Determine:
- **Auth provider**: Clerk, Better Auth, Auth.js, or none
- **UI components**: shadcn/ui, custom, or bare
- **Database**: Convex, Supabase, Prisma, Drizzle
- **Deployment**: Vercel, other
- **Language**: French or English (check existing UI strings)
- **Source prefix**: `src/` or root-level `app/`
---
## Phase 1: Dependencies
Install ALL required packages upfront:
```bash
# Core dependencies
bun add @linear/sdk html2canvas-pro @anthropic-ai/sdk sonner
# Verify sonner is in layout (Toaster component)
grep -r "Toaster" app/layout.tsx src/app/layout.tsx 2>/dev/null || echo "WARNING: Add <Toaster /> to root layout"
```
If `<Toaster />` is missing from the root layout, add it:
```tsx
import { Toaster } from "sonner"
// Inside the body:
<Toaster position="bottom-right" />
```
---
## Phase 2: Linear API Key & Team Setup
### Step 1: Get and Store Linear API Key
**CRITICAL: This is the #1 source of bugs. Follow EXACTLY.**
Check `.env.local` first:
```bash
grep 'LINEAR_API_KEY' .env.local 2>/dev/null
```
If not found, ask the user:
> I need a Linear API key. Generate one at: **https://linear.app/settings/api**
> Create a **Personal API key** with full access. Paste it here:
**IMMEDIATELY after receiving the token**, write it to `.env.local`:
```bash
# Write to .env.local FIRST (append, don't overwrite)
echo "" >> .env.local
echo "# Linear Feedback System" >> .env.local
echo "LINEAR_API_KEY=THE_TOKEN_USER_GAVE" >> .env.local
```
### Step 2: Verify API Key Works
**CRITICAL: Read the token from .env.local, do NOT use a shell variable.**
```bash
# Extract the key from .env.local
LINEAR_KEY=$(grep '^LINEAR_API_KEY=' .env.local | tail -1 | cut -d= -f2-)
# Test connection and list teams
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_KEY" \
-d '{"query":"{ viewer { id name email } teams { nodes { id name key } } }"}' | python3 -c "
import json, sys
try:
d = json.load(sys.stdin)
if 'errors' in d:
print('ERROR: ' + str(d['errors']))
sys.exit(1)
viewer = d['data']['viewer']
print(f'Connected as: {viewer[\"name\"]} ({viewer[\"email\"]})')
teams = d['data']['teams']['nodes']
print(f'Teams ({len(teams)}):')
for t in teams:
print(f' {t[\"key\"]} - {t[\"name\"]} (ID: {t[\"id\"]})')
except Exception as e:
print(f'ERROR: Failed to parse response - {e}')
sys.exit(1)
"
```
**If this fails**: The token is invalid. Ask the user to regenerate it. Common issues:
- Token has leading/trailing spaces → trim it
- Token was partially copied → ask user to re-copy
- Token needs `lin_api_` prefix → verify format
**If successful**: Ask user which team to use.
### Step 3: Find or Create Project
```bash
LINEAR_KEY=$(grep '^LINEAR_API_KEY=' .env.local | tail -1 | cut -d= -f2-)
TEAM_ID="SELECTED_TEAM_ID"
# List existing projects
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_KEY" \
-d "{\"query\":\"{ team(id: \\\"$TEAM_ID\\\") { projects { nodes { id name } } } }\"}" | python3 -c "
import json, sys
d = json.load(sys.stdin)
projects = d.get('data', {}).get('team', {}).get('projects', {}).get('nodes', [])
if projects:
print('Existing projects:')
for p in projects:
print(f' {p[\"name\"]} (ID: {p[\"id\"]})')
else:
print('No projects found.')
"
```
If no "User Feedback" project exists, create one:
```bash
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_KEY" \
-d "{\"query\":\"mutation { projectCreate(input: { name: \\\"User Feedback\\\", teamIds: [\\\"$TEAM_ID\\\"] }) { success project { id name } } }\"}" | python3 -m json.tool
```
### Step 4: Verify Workflow States
**CRITICAL: Ensure the team has proper workflow states for the feedback pipeline.**
```bash
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_KEY" \
-d "{\"query\":\"{ team(id: \\\"$TEAM_ID\\\") { states { nodes { id name type position } } } }\"}" | python3 -c "
import json, sys
d = json.load(sys.stdin)
states = d.get('data', {}).get('team', {}).get('states', {}).get('nodes', [])
states.sort(key=lambda s: s.get('position', 0))
print('Workflow states:')
for s in states:
print(f' [{s[\"type\"]}] {s[\"name\"]} (ID: {s[\"id\"]})')
# Verify essential states exist
types = [s['type'] for s in states]
for needed in ['backlog', 'unstarted', 'started', 'completed']:
if needed not in types:
print(f'WARNING: Missing state type: {needed}')
"
```
**Expected workflow:** Backlog → Todo → In Progress → Review → Done
- New feedback issues go to **Backlog** (default Linear behavior)
- When AI agent starts working: moves to **In Progress**
- After fix: moves to **Review** (human validates)
- Human marks **Done** after verification
If the team is missing a "Review" state, inform the user:
> Your Linear team doesn't have a "Review" state. I recommend adding one in Linear Settings → Team → Workflow States. This lets AI fixes go through human review before being marked done.
### Step 5: Create Label GROUPS + Grouped Children (CRITICAL — never flat labels)
> **Linear supports label groups (parent labels with children).** Always organise feedback labels into groups so the workspace stays clean. NEVER create flat `Bug`/`Feature`/`Improvement` labels at the root — they MUST live inside a `FEEDBACK` parent group.
#### Why groups?
- Filterable in Linear's UI by parent group
- Prevents accidental duplicates (`Agent: Leo` vs `Léo`, `Bug` outside vs inside group)
- Makes "label by category" reports trivial
- Allows hardcoding stable IDs in code (a child label inside a group is canonical)
#### A. Discover existing structure first
```bash
LINEAR_KEY=$(grep '^LINEAR_API_KEY=' .env.local | tail -1 | cut -d= -f2-)
TEAM_ID="<from-step-2>"
curl -s -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" -H "Authorization: $LINEAR_KEY" \
-d "{\"query\":\"{ team(id:\\\"$TEAM_ID\\\"){labels(first:200){nodes{id name parent{id name}}}}}\"}" | python3 -c "
import sys, json
d = json.load(sys.stdin)
labels = d['data']['team']['labels']['nodes']
groups = {}
for l in labels:
p = l.get('parent')
if p: groups.setdefault(p['name'], []).append(l['name'])
ungrouped = sorted([l['name'] for l in labels if not l.get('parent')])
print(f'=== EXISTING GROUPS ({len(groups)}) ===')
for g, ch in sorted(groups.items()):
print(f' 📂 {g}: {len(ch)} children → {sorted(ch)}')
print(f'\n=== UNGROUPED ({len(ungrouped)}) ===')
for n in ungrouped: print(f' • {n}')
"
```
#### B. Required groups + children
Always create these (skip any that already exist with proper parent):
| Parent group | Color | Children | Purpose |
|--------------|-------|----------|---------|
Auf GitHub ansehen