scaffold-sub-issues — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited scaffold-sub-issues (Agent Skill) and scored it 87/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 3 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 3 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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).
save_issue call.repo:<name> that doesn't exist as a label, surface it and ask whether to create the label or rename the sub-task.createdAt is the fallback sort key when blocks/blocked by aren't read by the consumer (e.g. /abc:ship-issue walks createdAt ascending). Parallel save_issue calls can collide at sub-second precision and scramble walk order.AskUserQuestion.$ARGUMENTS has three shapes — detect in order, first match wins:
PLAN-*.md in cwd (newest by mtime wins), fall back to ~/.claude/plans/PLAN-*.md (newest wins). If multiple candidates, ask the user to pick via AskUserQuestion. If none, abort with: "No PLAN-*.md found. Run /abc:plan first."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.)Read all selected plan files in full. Concatenate them in the order provided (Phase 1.5 handles conflicts).
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):
validation: bullet (or a sub-task-level ## Validation) becomes that child's manual-validation gate — the post-merge blocked-verify halt for /abc:ship-issue. A top-level ## Validation section is unattached; Phase 4 asks which sub-issue inherits it.repo:<name> maps to a Linear repo:<name> label (Linear has a single workspace, so the cross-owner repo:<owner>/<name> form has no meaning here — treat the whole token as the label name).Scan the parsed plans for conflicting decisions:
ST-N ID with different content.repo:<name> with different scope.For each conflict:
AskUserQuestion with both options side-by-side. Tag the option from the newer plan (by file mtime) as (Recommended).If no conflicts detected, fall through silently.
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. Capture team, project, existing labels.New-parent mode (no parent ID):
mcp__claude_ai_Linear__list_teams. If multiple, ask the user. Cache the choice.mcp__claude_ai_Linear__list_projects with 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:
mcp__claude_ai_Linear__list_issue_labels team=<team>. Match each sub-task's repo:<name> to an existing label. Track missing labels for Phase 4 approval — do not create here.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.
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?"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:
## Validation was attached in Phase 1, or there's a top-level Validation section that needs an owner):## Validation section to trigger /abc:ship-issue's blocked-verify flow?" Default: last UI-touching sub-issue if detectable, else "none".repo: labels don't exist yet: <list>. Create them, or rename the sub-tasks?"If "Edit before creating" — wait for user input, re-render the preview, re-ask.
mcp__claude_ai_Linear__create_issue_label with teamId=<team-id>. Capture each new label ID.mcp__claude_ai_Linear__save_issue with team, project, title, description (full plan markdown), labels. Capture the new parent ID.## Scope, ## Acceptance criteria, plus the ## Validation block on the chosen gate sub-issue.mcp__claude_ai_Linear__save_issue with parentId, team, project (if any), title, description, labels.createdAt ordering is the fallback walk order for /abc:ship-issue; parallel creates collide at sub-second precision and scramble order.blocks / blocked by relations in a second pass. For each sub-issue with blocks: [ST-X, ST-Y], call mcp__claude_ai_Linear__save_issue id=<sub-issue-id> blocks: [<X-id>, <Y-id>]. Same for blocked by via blockedBy:. (These fields are append-only per the save_issue schema — safe to call after creation.)mcp__claude_ai_Linear__list_issues parentId=<parent-id> orderBy=createdAt limit=50. Don't assume a sort direction — the MCP's orderBy direction isn't contractually guaranteed. Sort the returned issues by their createdAt field explicitly (ascending) client-side before comparing, rather than relying on the response order or reversing it blindly.createdAt timestamps 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.✓ 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./abc:ship-issue to know when a ticket is done.AskUserQuestion, recommends newer plan by mtime./abc:ship-issue will walk old → new. If the user wants new ones to go first, they need to manually reorder in Linear (no MCP exposes sortOrder)./abc:ship-issue as a workaround.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.