testdouble/work-items-to-linear
> Turn a work-items.md file (produced by /plan-work-items) into Linear issues, one per slice, in a single target Linear team. Use when you want to publish work items as Linear issues, create implementation tickets to track in Linear, or push a broken-down plan into a Linear team. Requires a configured Linear MCP server and a target team. Reads the team's real workflow states, labels, Projects, and members and resolves every option against them before creating anything; defaults each issue to the team's initial state, unassigned, uncategorized, with no parent or Project unless you ask. Links within-file `Depends on` relationships as native Linear "blocked by" relations and annotates the source file so re-runs resume cleanly. Does not produce the work-items file itself — use plan-work-items first. Does not post to Jira — use work-items-to-jira. Does not post to GitHub — use work-items-to-issues.
npx skills add https://github.com/testdouble/han --skill work-items-to-linear
Take an already-broken-down work-items.md file (produced by /plan-work-items) and publish each slice as a Linear issue in a single target team.
The breakdown work — drafting slices, assigning symbolic IDs, specifying dependencies, inventorying references — has already been done upstream. This skill validates the format, confirms the target against the live team, creates one issue per slice through the Linear MCP server, links the within-file dependencies as native "blocked by" relations, and reports.
work-items.md that names multiple code repos still produces issues in the single team you name; the repo prose is informational only.Depends on line must resolve to another slice in the same file. A Depends on that names an unknown SYM, names the slice itself, or forms a cycle is a format error to surface for repair, never published.W-N, V2-N, EV-N, ...); the prefix has no effect on team placement.artifacts/ subfolder of the plan that is not a contract or design reference.This skill cannot run without a configured and connected Linear MCP server. Confirm it is reachable by calling mcp__plugin_linear_linear__list_teams. If the tool is unavailable, the call errors, or no workspace is accessible, stop immediately and tell the user the skill requires the Linear MCP server to be installed, configured, and authenticated. Do not fall back to any other publishing target.
If the integration exposes more than one Linear workspace, note which are available and confirm which one to use before resolving the team.
If the path is not provided, ask for it. The input is a single work-items.md produced by /plan-work-items. Read it. Its format is described in references/work-items-file-format.md.
Read these from the arguments and conversation; do not guess defaults the user did not ask for:
--team <name or key>. Required. If absent, ask for it in Step 3.--project <name or ID>. Optional. Groups every created issue under a Linear Project.--parent <issue id>. Optional. Nests every created issue as a sub-issue under the named parent.--state <name>. Optional; defaults to the team's initial/default workflow state.--label <name>, repeatable. Optional; resolved against the team's real labels.--assignee <name/email/me>. Optional; defaults to unassigned.Resolve everything concretely now so failures surface before any issue is created. This is a strict, fail-before-write sequence:
mcp__plugin_linear_linear__list_teams. If none is named, ask. If the name matches more than one team, present the matches and ask which one. Do not proceed without exactly one team.mcp__plugin_linear_linear__list_issue_statuses, mcp__plugin_linear_linear__list_issue_labels, and mcp__plugin_linear_linear__list_users. These reads are independent and may run together.--state was given, match it against the team's real states. The default is the team's initial/default state. If a named state does not exist, present the team's real states and ask.--labels were given, match each against the team's labels. When categorization was not specified, present the team's real labels and let the user choose one, several, or none. If the team defines no labels, say so and proceed without categorization.mcp__plugin_linear_linear__get_user: the literal token me resolves to the authenticated Linear identity, and a name or email resolves to that member. If unset, leave issues unassigned. The creator is recorded automatically by Linear as the authenticated user; never set it.mcp__plugin_linear_linear__list_projects (Projects are not strictly team-scoped), and confirm the target team participates in it.mcp__plugin_linear_linear__get_issue and confirm it belongs to the target team.For any option that cannot be resolved, do not silently drop or invent it. Distinguish "no such option exists in the team" (present the team's real options for that field) from "it exists but belongs to a different team" (name that team). Ask the user to pick or correct before continuing.
Check the work-items file against the invariants in references/work-items-file-format.md and references/linear-issue-template.md:
## <SYM-N> — <title> with an em-dash separator (already-published headings annotated as ## <SYM-N> (<LINEAR-ID>) — <title> are valid too).Depends on line. Literal bold marker Depends on., trailing period, None. or comma-separated SYMs.Depends on line resolves to another slice in this file. A SYM that names the slice itself (self-block) or that forms a dependency cycle with other slices is a format error.artifacts/ subfolder that is not a contract or design reference.When a check fails, attempt evidence-based repair. Pull evidence from the source work-items.md, the parent plan referenced in its intro, the feature spec in the same folder, sibling files in the plan folder, and the relevant repo's ADRs / coding standards / docs:
Depends on line — propose None. if no blockers are evident. Cite the absence.None., or breaking the cycle. Cite the SYM list this file defines.See plan: D-N. Cite the include/exclude list.After validation, report findings in plain language. For each: (1) what is wrong — slice SYM, line reference, failing invariant; (2) the proposed fill — corrected line, new bullet, removed link; (3) the evidence — file path with line number, document section, or named source.
Then give the user three actions: Continue with fills (apply the repairs to the source work-items.md and proceed), Correct the fills (user provides the right values; apply those and proceed), or Stop (exit without creating issues). If validation passes with no findings, proceed to Step 5.
Creating Linear issues writes to a shared system, so confirm before doing it. Present a summary and wait for an explicit yes:
(<LINEAR-ID>) annotation.| SYM | Title | Depends on |
| --- | --- | --- |
| W-1 | ... | None |
| W-2 | ... | W-1 |
State the total count of issues to create and how many slices are being skipped because they already carry an identifier. Do not create anything until the user confirms.
Walk the slices in file order. Skip any slice whose heading already carries a (<LINEAR-ID>) annotation so a re-run resumes cleanly. Creation order does not affect correctness, because dependency relations are made in Step 7 once every issue exists. For each remaining slice, call mcp__plugin_linear_linear__save_issue with:
— in the heading),blockedBy here — relations are made in Step 7.After each successful create, capture the returned Linear identifier and rewrite that slice's heading in place from ## <SYM-N> — <title> to ## <SYM-N> (<LINEAR-ID>) — <title> using Edit, so dependencies resolve and re-runs skip it. Report each creation as created: <SYM-N> -> <LINEAR-ID>.
If a create succeeds but the heading annotation fails, stop. Report the orphaned Linear identifier so the user can annotate the heading by hand or delete the issue. Do not continue creating, and do not run the link pass — the file state is inconsistent until the user resolves it.
Once every slice has a Linear identifier, build the SYM-to-identifier map from the annotated headings. This pass runs over the whole annotated file on every run (including identifiers carried over from a prior run), not only over newly created issues, so a link step interrupted earlier completes on the next run.
Relations are made after all issues exist because a blocked by relation needs both endpoints to exist, and file order is not guaranteed to be blocker-first.
mcp__plugin_linear_linear__get_issue. Surface any that do not resolve to the user before making any relation; never link to a missing or wrong issue.Depends on. line (skip None.), call mcp__plugin_linear_linear__save_issue on the dependent issue with blockedBy set to each blocker's identifier. Relations are append-only and de-duplicated, so a re-run does not duplicate them; no per-relation pre-read is needed.Report each as linked: <SYM-A>(<LINEAR-A>) blocked_by <SYM-B>(<LINEAR-B>).
Summarize: the team, the Project and parent (if any), the workflow state, the labels and assignee (or "none" / "unassigned"). List every created issue as <SYM-N> — <LINEAR-ID> with its URL, the count of native "blocked by" relations created, and any slices skipped because they already carried an identifier. If any step failed, report the error and confirm the source work-items.md annotations reflect exactly which issues were created, so the user can re-run safely.
Take testdouble/work-items-to-linear from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.