/abc:scaffold-sub-issues — Convert PLAN(s) to Linear sub-issues
Take one or more PLAN-*.md files, parse their sub-tasks, propose a Linear parent issue (or add to an existing one) with sub-issues + dependency edges + repo: labels + an optional ## Validation gate, then create them after the user confirms.
The output of this skill is a parent issue ID you can paste straight into /abc:ship-epic (parallel multi-repo) or /abc:ship-issue (serial single-repo loop).
Hard rules
- Never create Linear issues without an explicit confirmation gate. Issue creation is write-heavy and visible to teammates; the user must see the full proposed structure before any
save_issuecall. - Never create new Linear labels silently. If a sub-task references
repo:<name>that doesn't exist as a label, surface it and ask whether to create the label or rename the sub-task. - Never invent sub-tasks not present in the plan. Parse what's there. If the plan is missing acceptance criteria or a repo, ask the user — don't fabricate.
- Sub-issues must be created sequentially, not in parallel. Linear's
createdAtis the fallback sort key whenblocks/blocked byaren't read by the consumer (e.g./abc:ship-issuewalkscreatedAtascending). Parallelsave_issuecalls can collide at sub-second precision and scramble walk order. - When multiple plan files are provided, reconcile conflicts before building the tree. Never silently pick one plan's version over another — surface contradictions via
AskUserQuestion. - The parent issue's description is the full plan markdown (concatenated if multiple), not a summary. Reviewers should be able to read the parent and understand the full context.
Workflow
Phase 0: Parse arguments + locate plans
$ARGUMENTS has three shapes — detect in order, first match wins:
- Empty → auto-detect mode. Look for
PLAN-*.mdin cwd (newest by mtime wins), fall back to~/.claude/plans/PLAN-*.md(newest wins). If multiple candidates, ask the user to pick viaAskUserQuestion. If none, abort with: "No PLAN-*.md found. Run /abc:plan first." - First token fully matches
^[A-Z]+-\d+$(e.g.PROJ-45) and is not an existing file → existing-parent mode. Treat the first token as the parent Linear issue ID. Remaining tokens are plan file paths (all required, all must exist). This skill will add sub-issues to that parent instead of creating a new one. (The full-token anchor + file-existence precedence stop a path that happens to start with capital letters from being misread as a Linear ID — if the token names a file on disk, it's a plan path, shape 3.) - Otherwise → all tokens are plan file paths (one or more). Resolve relative to cwd or as absolute paths. This is new-parent mode with explicit input.
Read all selected plan files in full. Concatenate them in the order provided (Phase 1.5 handles conflicts).
Phase 1: Parse the plan structure
Parse each plan per the canonical grammar in ../plan/plan-format.md — the strict/loose formats, the sub-task block fields, the (none)/(empty)/omitted relations sentinel, and the validation-gate resolution order are all defined there (single-sourced so this skill and scaffold-sub-issues-gh can't drift). If neither format matches, halt with the message that doc specifies.
Linear-specific deltas (everything else follows plan-format.md verbatim):
- The per-sub-task
validation:bullet (or a sub-task-level## Validation) becomes that child's manual-validation gate — the post-mergeblocked-verifyhalt for/abc:ship-issue. A top-level## Validationsection is unattached; Phase 4 asks which sub-issue inherits it. repo:<name>maps to a Linearrepo:<name>label (Linear has a single workspace, so the cross-ownerrepo:<owner>/<name>form has no meaning here — treat the whole token as the label name).
Phase 1.5: Reconcile multiple plans (only if 2+ plans given)
Scan the parsed plans for conflicting decisions:
- Same
ST-NID with different content. - Same
repo:<name>with different scope. - Headings appearing in multiple plans with materially different prose (e.g. one plan says "experiment-gated", another says "unconditional").
For each conflict:
AskUserQuestionwith both options side-by-side. Tag the option from the newer plan (by file mtime) as (Recommended).- Bake the user's choice into the canonical structure used by Phase 2 onward.
If no conflicts detected, fall through silently.
Phase 2: Resolve Linear context
Two branches depending on mode:
Existing-parent mode (parent ID was passed in Phase 0):
mcp__claude_ai_Linear__get_issue id=<parent-id> includeRelations: true. Captureteam,project, existing labels.- Use the parent's team + project as defaults for all new sub-issues.
New-parent mode (no parent ID):
- Linear team —
mcp__claude_ai_Linear__list_teams. If multiple, ask the user. Cache the choice. - Project (optional) —
mcp__claude_ai_Linear__list_projectswith the team filter. If the plan title hints at a project (matches a project name), pre-select it. Otherwise leave blank and ask.
In both modes:
repo:labels —mcp__claude_ai_Linear__list_issue_labels team=<team>. Match each sub-task'srepo:<name>to an existing label. Track missing labels for Phase 4 approval — do not create here.
Phase 2.5: cwd subdirectory advisory (soft warning)
For each unique repo:<name> referenced in the parsed structure, check whether <cwd>/<name>/ exists (ls in cwd). If missing, note it for the Phase 7 advisory — don't halt. The user might invoke /abc:ship-issue from a different cwd than where they're running this skill.
Phase 3: Build the proposed issue tree
Compose the structure in memory:
Parent:
team: <resolved team>
project: <resolved or null>
title: <plan title> # new-parent mode only; existing-parent skips this
description: <full plan markdown, concatenated if multiple>
labels: [<deduped union of all sub-task repo labels>]
Sub-issues (in plan order):
ST-1 (or Sub-issue 1):
title: <ST-1 title>
description: |
## Scope
<scope text>
## Acceptance criteria
- <bullets>
[optional ## Validation block, only on the chosen gate sub-issue]
parent: <parent ID, set on creation>
labels: [repo:<name>]
relations:
blocks: [<ST-IDs>]
blocked by: [<ST-IDs>]
ST-2: …
Existing-parent mode collision check. Also call mcp__claude_ai_Linear__list_issues parentId=<parent-id> limit=50. If the parent already has sub-issues:
AskUserQuestion: "Parent<parent-id>already has N sub-issues. Halt (default), Append more (warning: createdAt order will be sandwiched after the existing ones), or Show me the existing ones before deciding?"- On Halt → exit cleanly, no writes.
- On Append → proceed with Phase 4 but warn in the preview.
Phase 4: Show the user the proposed structure
Print a readable preview:
/abc:scaffold-sub-issues — proposed Linear structure
Mode: new-parent | existing-parent (<PARENT-ID>) | existing-parent (append)
Parent: <Title>
Team: <Team>
Project: <Project or "none">
Labels: repo:web-frontend, repo:analytics-tools
Description: <2000-char preview>
Sub-issues:
[ST-1] Add WidgetRow component to web frontend
Labels: repo:web-frontend
Blocks: ST-3
Blocked by: (none)
[ST-2] Publish shared WidgetRow types in analytics-tools
Labels: repo:analytics-tools
Blocks: (none)
Blocked by: (none)
[ST-3] Wire WidgetRow into dashboard page ← carries ## Validation gate
Labels: repo:web-frontend
Blocks: (none)
Blocked by: ST-1
Dependency graph:
ST-2 → (independent)
ST-1 → ST-3
Missing labels to create: repo:web-frontend (will prompt)
cwd advisory: <cwd>/analytics-tools/ not found (you'll need to invoke /abc:ship-issue from a cwd containing this subdir)
Then ask via AskUserQuestion:
Confirm structure:
- Create everything as shown (Recommended)
- Edit before creating — user replies with adjustments
- Create just the parent, skip sub-issues — for when sub-task scoping isn't ready (new-parent mode only)
- Cancel
Validation gate (only if no
## Validationwas attached in Phase 1, or there's a top-level Validation section that needs an owner):- "Which sub-issue should carry the
## Validationsection to trigger/abc:ship-issue'sblocked-verifyflow?" Default: last UI-touching sub-issue if detectable, else "none".
- "Which sub-issue should carry the
Missing labels (only if Phase 2 found any):
- "These
repo:labels don't exist yet:<list>. Create them, or rename the sub-tasks?"
- "These
If "Edit before creating" — wait for user input, re-render the preview, re-ask.
Phase 5: Create labels, parent (if needed), then sub-issues sequentially
- Missing labels first. For each approved missing label, call
mcp__claude_ai_Linear__create_issue_labelwithteamId=<team-id>. Capture each new label ID. - Parent (new-parent mode only). Call
mcp__claude_ai_Linear__save_issuewithteam,project,title,description(full plan markdown),labels. Capture the new parent ID. - Sub-issues, sequentially. For each sub-task in plan order:
- Build the description: include
## Scope,## Acceptance criteria, plus the## Validationblock on the chosen gate sub-issue. - Call
mcp__claude_ai_Linear__save_issuewithparentId,team,project(if any),title,description,labels. - Wait for the response before issuing the next call. Do not parallelize. Linear's
createdAtordering is the fallback walk order for/abc:ship-issue; parallel creates collide at sub-second precision and scramble order. - Capture each returned sub-issue ID into an ST-N → ID map.
- Build the description: include
- Relations pass. After all sub-issues exist, wire
blocks/blocked byrelations in a second pass. For each sub-issue withblocks: [ST-X, ST-Y], callmcp__claude_ai_Linear__save_issue id=<sub-issue-id> blocks: [<X-id>, <Y-id>]. Same forblocked byviablockedBy:. (These fields are append-only per thesave_issueschema — safe to call after creation.)
Phase 6: Self-check
mcp__claude_ai_Linear__list_issues parentId=<parent-id> orderBy=createdAt limit=50. Don't assume a sort direction — the MCP'sorderBydirection isn't contractually guaranteed. Sort the returned issues by theircreatedAtfield explicitly (ascending) client-side before comparing, rather than relying on the response order or reversing it blindly.- Verify count matches (expected = plan sub-tasks created in this run; in append mode, expected = original count + new count).
- Verify IDs match the ST-N → ID map from Phase 5.
- Verify
createdAttimestamps are strictly increasing among newly created sub-issues. If any two share the same second → log a warning (consumer might still walk in unspecified order); the user can manually reorder by editing in Linear or by re-running with a comma-separated list to/abc:ship-issue.
Phase 7: Print the handoff
✓ Created parent: <PARENT-ID> "<Title>" # new-parent mode
✓ Added 5 sub-issues to <PARENT-ID> "<Title>" # existing-parent mode
✓ Wired 2 dependency links
✓ Created 1 new repo: label
Sub-issues (createdAt order — what /abc:ship-issue will walk):
1. <SUB-ID-1> <repo:foo> <URL>
2. <SUB-ID-2> <repo:bar> <URL>
3. <SUB-ID-3> <repo:baz> <URL> ← carries ## Validation gate
Next:
→ /abc:ship-issue <PARENT-ID> (serial single-loop; recommended)
→ /abc:ship-epic <PARENT-ID> (parallel multi-repo; faster if no relations)
Linear link: <parent URL>
If Phase 2.5 flagged any missing cwd subdirectories, repeat the advisory:
⚠ Heads up: <cwd>/<repo-name>/ is missing.
/abc:ship-issue resolves repo: labels against subdirectories of cwd.
Invoke it from a cwd that has each <repo-name>/ as a subdirectory,
or clone the missing repos first.
Notes on edge cases
- Sub-task with no acceptance criteria: ask the user to add them in the plan file, then re-run. AC is required for
/abc:ship-issueto know when a ticket is done. - Circular blocks (ST-1 blocks ST-2, ST-2 blocks ST-1): abort with a clear cycle description. Linear allows the relations, but the dependency graph is unworkable.
- Plan references a repo that has no sub-task: ignore — the parent's label union covers it.
- Multiple plans in cwd: ask the user to pick (Phase 0 auto-detect).
- Two plans contradict on a structural decision (e.g. one says split CMS into helper+UI, the other says one bundled sub-issue): Phase 1.5 surfaces via
AskUserQuestion, recommends newer plan by mtime. - Parent has existing sub-issues, user picks Append: warn that new sub-issues will land after existing ones in createdAt order, which means
/abc:ship-issuewill walk old → new. If the user wants new ones to go first, they need to manually reorder in Linear (no MCP exposessortOrder). - Loose-format plan with no
repo:tag on a sub-task: halt and ask the user to add it. The skill won't guess. - User wants to update an existing parent's description rather than add sub-issues — out of scope for v1. Tell them to edit in Linear directly.
- createdAt collision (two sub-issues share the same second): log a warning; the consumer's walk order is unspecified between collisions. Suggest comma-separated explicit ordering to
/abc:ship-issueas a workaround.