iru-split-github-issue
/iru-split-github-issue <issue-number> (stays the root) or /iru-split-github-issue draft:<path> (root created from the draft); optional force: yes skips the size assessment, label: <name> sets the lab
SKILL.md.claude/skills/iru-split-github-issue/SKILL.md
GitHubSplit GitHub Issue
Turn one oversized issue into a root issue plus small, self-sufficient sub-issues ordered by dependency, tracked by a
milestone. Body templates are in reference/split-templates.md (relative to this skill's directory); read it before
Step 5.
Nothing is created, edited, linked or commented before the explicit confirmation in Step 6. Steps 1-5 are read-only.
Step 1 — Resolve the input and fetch
Parse the arguments:
<issue-number>(optionally#-prefixed): existing issue. It stays the root; see "Existing issue" below.draft:<path>: local Markdown draft (first line# <title>, rest is the body). Read it. If missing or without a title line, tell the user and stop.- optional
force: yes. - optional
label: <name>(passed byiru-draft-github-issue): the label for the root and the sub-issues. Use it only if it exists ingh label list --json name; otherwise tell the user and continue without it. Without the argument, an existing root keeps its own labels and sub-issues inherit them; a draft root gets none unless the user picks one from the actual list in Step 6. Adocumentation-kind sub-issue uses thedocumentationlabel instead when that label exists. - Neither form: ask which issue number or draft path to split.
Confirm the repository is hosted on GitHub (git remote get-url origin matches github.com or a GitHub Enterprise
Server domain). Otherwise say this skill only splits GitHub issues and stop. Resolve owner and repo with
gh repo view --json owner,name.
For an existing issue run gh issue view <n> --json number,title,body,labels,comments,state,url. If gh is missing,
search for GitHub MCP tools (ToolSearch "github issue"); if none exist, tell the user and stop.
Step 2 — Assess size
Judge (do not compute) whether the issue is large, using these signals:
- more than about 7 acceptance criteria or functional requirements;
- more than one independently deliverable capability;
- more than one module or area touched (verify against the code with
Read/grep when files are named); - estimated difficulty hard or above.
If no signal fires and force: yes was not given, say the issue is not large enough to split, name the signals
checked, and stop. If any signal fires (or force: yes), continue.
Step 3 — Propose the sub-issues
Cut the issue into slices that each deliver something testable. For each sub-issue give: title, scope, the subset of
requirements and acceptance criteria it owns (every original requirement and criterion must land in exactly one
sub-issue; nothing may be dropped or invented), and kind (functional or documentation). Aim for at least 2 and
rarely more than about 8 sub-issues.
Step 4 — Compute dependencies and groups
For each sub-issue decide which others it truly needs finished first (Blocked by). Assign group numbers:
- a sub-issue with no blockers is in group 1;
- otherwise its group is one more than the highest group among its blockers;
- issues in the same group are independent and can run in parallel; ordered groups are dependencies.
A dependency cycle means the slicing is wrong: re-cut the slices until the graph is acyclic.
Step 5 — Review each sub-issue draft
Compose each sub-issue body from the sub-issue template, using placeholders for issue numbers that do not exist yet.
Write each draft to a temp file in the scratchpad (or temp directory) and run
Skill({skill: "iru-review-github-issue", args: "draft:<path>"}). The review is report-only. Consume its block
unchanged: resolve findings from the original issue text or the codebase when possible; otherwise put the
finding's question to the user. Do not proceed to confirmation with unresolved gap or pending findings unless
the user explicitly accepts them.
Step 6 — Confirm
Show the user:
- the sub-issue table (group, title, blocked by, kind, requirements/criteria owned);
- the Mermaid
flowchartof the dependencies (as in the root template); - the milestone that will be created (or reused, when one with the root title already exists), the label(s) to apply, and, for an existing issue, that the root body will be rewritten.
Ask via AskUserQuestion: Create everything now / Adjust the split first (take edits, redo Steps 3-6) /
Don't create anything (stop; the proposal is the deliverable). Only the first option permits Step 7.
Step 7 — Create
Write every body to a temp file and use --body-file (avoids quoting problems). Use gh api and the gh issue
commands; if gh is missing or unauthenticated, use GitHub MCP tools for the same operations.
- Look up an existing milestone with the root title. Milestones are listed open-only by default, so ask for all
states and match the title exactly:
Creating a milestone whose title already exists fails withgh api --method GET repos/{owner}/{repo}/milestones -f state=all -f per_page=100 \ --jq '.[] | select(.title == "<root title>") | {number, state, html_url}'422 Validation Failed, so never POST without this lookup. A match is reused after telling the user (a closed one is reopened withgh api --method PATCH repos/{owner}/{repo}/milestones/<number> -f state=open). - Root issue first, so the milestone can link it. Draft input:
gh issue create --title "<title>" --body-file <path> [--label "<label>"], with the group table holding placeholders; capture the root number and URL. Existing issue: the root is#N(see "Existing issue" below). - Milestone (title = root title, description linking the now-known root), unless step 1 found one:
Then put the root on it:gh api --method POST repos/{owner}/{repo}/milestones -f title="<root title>" \ -f description="Tracks #<root> (<root url>) and its sub-issues." --jq '{number, html_url}'gh issue edit <root> --milestone "<root title>". - Sub-issues in dependency order (group 1 first), so
Blocked bycan use real numbers:
Put the realgh issue create --title "<title>" --body-file <path> --milestone "<root title>" [--label "<label>"]Parent: #<root>,Blocked by: ...andParallel group: <n>lines in each body. - Link each as a sub-issue of the root. The REST
sub_issue_idis the child's numericid, not itsnumber:id=$(gh api repos/{owner}/{repo}/issues/<child-number> --jq .id) gh api repos/{owner}/{repo}/issues/<root>/sub_issues -F sub_issue_id=$id - Update the root with the final group table and diagram (real numbers) using
gh issue edit <root> --body-file <path>.
Fallbacks:
- If the sub-issues endpoint is unavailable (GitHub Enterprise Server without the feature, or missing permissions),
do not fail: add a task list
- [ ] #<n> <title>per sub-issue to the root body, and tell the user plainly that native sub-issue links were not created. - If a step fails midway, stop, report exactly what was created (numbers and URLs) and what is left, and do not retry blindly or delete anything.
Existing issue
When the input is an issue #N:
#Nstays the root; no new root is created.- Rewrite its body from the root template, preserving the original text verbatim under Original description.
- Show the user the rewritten body and obtain explicit confirmation (it is part of the Step 6 question, and ask
again if the body changed afterwards) before running
gh issue edit <N> --body-file <path>. The original body is never discarded; keep a copy in the report so it can be restored. - The milestone is looked up or created in Step 7 (steps 1 and 3) and added with
gh issue edit <N> --milestone, in the same call as the body edit or a separate one.
Step 8 — Report
Give the user the milestone URL, the root issue number and URL, and a table of sub-issues (number, title, group,
blocked by). Mention any fallback used. Suggest /iru-issue <n> for a group-1 sub-issue to start; issues in the
same group can be worked in parallel.
Verification
Read-only calls verified against albertoirurueta/ai-catalog:
gh label listprints tab-separatedname, description, color(only the GitHub default labels exist there).gh api repos/{owner}/{repo}/milestonesreturns[](HTTP 200) when there are no open milestones; it lists open milestones only by default, so pass-f state=all(as a query, with--method GET) to also detect closed ones before reusing a title.gh api repos/{owner}/{repo}/issues/<n>/sub_issuesreturns[](HTTP 200) for an existing issue without sub-issues, and404 Not Foundonly when the issue itself does not exist. An empty array therefore means "no sub-issues", not "feature unavailable"; a 404 on an existing issue number would be the unavailable signal.gh issue view <n> --json idreturns the GraphQL node id (string), whilegh api repos/{owner}/{repo}/issues/<n>returns the numericid(e.g. 5866905130) required assub_issue_id; always use the REST.idfor linking.gh issue view <n> --json ...on a nonexistent number fails with a GraphQL "Could not resolve" error.
Unverified (written from the GitHub documentation; no scratch repository was available): gh issue create, the
milestone POST, the sub-issue POST and gh issue edit / gh issue comment.