Convert Linear Backlog issues into TDD implementation plans. Use when user says "plan FOO-123", "plan all bugs", "work on backlog", or wants to implement issues from Linear. With no arguments, plans ALL Backlog issues. Moves planned issues to Todo state. Explores codebase for patterns and discovers available MCPs from CLAUDE.md.
Convert Linear Backlog issues into a structured TDD implementation plan written to PLANS.md.
This skill takes one or more Linear issues from the Backlog state and produces a detailed, step-by-step TDD implementation plan. The plan is written to PLANS.md at the project root. After planning, the Linear issues are moved to the "Todo" state.
This skill creates plans. It does NOT implement them.
Before doing anything, verify the git state:
git status
git branch --show-current
Requirements:
main branchIf either check fails, STOP and report the issue to the user. Do not proceed.
Check if PLANS.md exists at the project root:
ls -la PLANS.md
Rules:
PLANS.md does not exist: OK, proceed.PLANS.md exists and its status is COMPLETE: OK, proceed (it will be overwritten).PLANS.md exists and its status is NOT COMPLETE: STOP. Tell the user there is an active plan that must be completed or removed first.To check status, read the file and look for EITHER:
**Status:** COMPLETE in the header (line 3), OR## Status: COMPLETE anywhere in the file (appended by plan-review-implementation)If either marker is found, the plan is complete.
Call mcp__linear__list_teams. If unavailable, STOP and tell the user: "Linear MCP is not connected. Run /mcp to reconnect, then re-run this skill."
Use the Linear MCP to find the requested issues.
First, discover the team name:
Read CLAUDE.md and look for LINEAR INTEGRATION section. Extract the team name from patterns like:
If CLAUDE.md doesn't have a LINEAR INTEGRATION section, call mcp__linear__list_teams to discover the team name dynamically.
Store the discovered team name in a variable for use throughout the skill.
If user specified a specific issue (e.g., "FOO-123"):
mcp__linear__get_issue(issueId: "FOO-123")
Verify the issue exists and is in the "Backlog" state. If not in Backlog, warn the user but continue if they confirm.
If user specified a filter (e.g., "all Bug issues", "the auth issue"):
First, get the team's issue statuses and labels:
mcp__linear__list_issue_statuses(team: [discovered team name])
mcp__linear__list_issue_labels(team: [discovered team name])
Then query for Backlog issues:
mcp__linear__list_issues(team: [discovered team name], state: "Backlog", includeArchived: false)
Filter the results based on the user's criteria (label, title keywords, etc.).
If user said "plan all", "work on backlog", or provided no arguments:
mcp__linear__list_issues(team: [discovered team name], state: "Backlog", includeArchived: false)
Plan ALL returned Backlog issues. No confirmation needed — the triage phase (Phase 3) will filter out invalid ones.
Read the project's CLAUDE.md file to understand:
Read CLAUDE.md
This is critical for generating plans that align with the project's patterns.
Explore the codebase to understand existing patterns using dedicated tools (NOT Bash):
What to find:
Based on what you learned from CLAUDE.md, identify which MCP servers are available. Common ones for this project:
Query relevant MCPs to gather context that will inform the plan. For example:
When planning issues that contain Sentry references in their Linear description (look for **Sentry Issue:** sections), carry those Sentry issue URLs into the PLANS.md **Sentry Issues:** header field. This ensures the downstream plan-review-implementation skill can resolve them when the plan is complete.
Before planning, assess whether each backlog issue is valid and actionable in the current project context. Issues from code audits may flag theoretical problems that don't apply.
For each candidate issue, read the referenced code and ask:
Place each issue in one of two categories:
| Category | Criteria | Action |
|---|---|---|
| Valid | Problem is real, fix is actionable, applies to current context | Include in plan |
| Invalid | Problem doesn't exist, is theoretical, or "fix" would be wrong | Cancel the issue |
For each invalid issue:
mcp__linear__create_comment(issueId: "FOO-xxx", body: "Canceled during triage: [reason]")
The reason should be specific, e.g.:
CRITICAL: Linear MCP same-type state bug. "Duplicate" and "Canceled" are both type: canceled in Linear. Passing state: "Canceled" by name silently no-ops if the issue is already in another canceled-type state. To reliably cancel issues, first fetch the team's statuses to get the Canceled state UUID:
mcp__linear__list_issue_statuses(team: [discovered team name])
Find the status with name: "Canceled" and use its id (UUID) in the update call:
mcp__linear__update_issue(id: "FOO-xxx", state: "<canceled-state-uuid>")
Always use the UUID, never the name, for canceled-type state transitions.
Before proceeding, present the triage results to the user:
## Triage Results
**Valid (will be planned):**
- FOO-123: [title] — [brief reason it's valid]
- FOO-456: [title] — [brief reason it's valid]
**Canceled:**
| Issue | Title | Reason |
|-------|-------|--------|
| FOO-789 | [title] | [specific reason it's invalid] |
| FOO-012 | [title] | [specific reason it's invalid] |
Document canceled issues in the plan's Scope Boundaries → Out of Scope section with the cancellation reason.
If ALL issues are invalid, STOP — inform the user that no issues need planning.
If valid issues remain, proceed to Phase 4.
For each issue being planned:
For each issue:
Write the plan to PLANS.md at the project root using the structure template below.
After writing the plan but before moving issues to Todo, re-read CLAUDE.md and cross-check each task for missing defensive specs:
| Check | What to look for | Example violation |
|---|---|---|
| Error handling | Each task touching external APIs or DB has error handling specs | Plan says "call Fitbit API" with no catch or token-expiry handling spec |
| Timeouts | Any external API call (Fitbit, Anthropic, OAuth) has a timeout value specified | Plan adds new API call with no timeout spec |
| Auth validation | Every new API route specifies which auth middleware to apply | Plan adds src/app/api/ route with no getSession() / validateApiRequest() mention |
| Edge cases | Empty results, null responses, expired tokens, and rate limits are addressed | Plan has no test for empty Fitbit food log or expired OAuth token |
| Conventions | @/ path alias, interface over type, pino logging, api-response.ts format | Plan uses raw console.log or direct src/db/ import in a route handler |
| DB transactions | Multi-step write operations specify transaction boundary | Plan adds two related DB writes with no transaction or rollback spec |
Fix any violations found before proceeding. This step prevents the plan from introducing gaps that become bugs at implementation time.
After writing all tasks, scan the entire plan for these patterns. If a pattern is detected in any task, verify the corresponding specification exists in that task's steps. If missing, add it before finalizing the plan.
| Pattern Detected in Plan | Required Specification |
|---|---|
| External API calls (Fitbit, Anthropic, OAuth endpoints) | Timeout value and error handling behavior (including token expiry and rate limits) |
API route handlers (src/app/api/) | Auth validation via getSession() + validateSession() or validateApiRequest() before any logic |
| Error responses returned to clients | Sanitization — use src/lib/api-response.ts format with ErrorCode; never expose raw errors |
| Database writes or multi-step DB operations | Transaction boundary or rollback behavior on partial failure |
| Client-side data fetching or mutations | Loading and error states in UI; useSWR for reads, not raw useState + fetch |
| User-triggered async actions (form submits, button clicks) | Duplicate submission guard or optimistic update with rollback |
Read references/plans-template.md for the complete template.
Source field: Backlog: FOO-123, FOO-456 (list the issue keys being planned)
Include: Context Gathered (Codebase Analysis + MCP Context + Triage Results), Tasks, Post-Implementation Checklist, Plan Summary. Omit: Investigation subsection.
Weave each issue's acceptance criteria into the relevant task steps — do not create a separate Issues section.
When writing tasks in the plan:
getSession() + validateSession(), or validateApiRequest()) must check credentials first and handle denial.src/db/ directly (use src/lib/ modules). Verify all logging uses pino (server-side) or console.error/console.warn (client-side), never console.log. Verify @/ path alias is used for all imports.Every implementation task MUST follow the Red-Green-Refactor cycle:
When planning, consider how MCPs will be used during implementation:
PLANS.md. It does not create or edit source files.PLANS.md already has an active (non-COMPLETE) plan, do not overwrite it.state: "Canceled" by name — use the UUID from list_issue_statuses. The Linear MCP silently no-ops same-type state transitions by name.if (!session.x) check after the existing !session.y check") are fine.MIGRATIONS.md.This skill:
If the user asks to also implement the plan, tell them to use the plan-implement skill after this one completes.
| Situation | Action |
|---|---|
Not on main branch | STOP. Tell user to switch to main. |
Uncommitted changes on main | STOP. Tell user to commit or stash changes. |
PLANS.md has active plan | STOP. Tell user to complete or remove the existing plan. |
| Linear issue not found | STOP. Tell user the issue ID is invalid. |
| Linear issue not in Backlog | WARN user but continue if they confirm. |
| No CLAUDE.md found | WARN user. Continue with reduced context. |
| MCP server unavailable | WARN user. Continue without that MCP's context. |
| User specifies no issues | Fetch ALL Backlog issues and plan them (default behavior). |
| All issues invalid after triage | STOP. Cancel all issues, inform user no plan needed. |
| Some issues invalid after triage | Cancel invalid issues, plan only valid ones. |
Follow the termination procedure in references/plans-template.md: output the Plan Summary, then create branch, commit (no Co-Authored-By tags), and push.
Do not ask follow-up questions. Do not offer to implement. Output the summary and stop.