/sdlc:spawn-task-pr
Generated from solutions/ontological/skills/spawn-task-pr/SKILL.md.
Description
Section titled “Description”Open a PR carrying a single scaffolded follow-up task file on a
target repo. /sdlc:spawn-from-post-mortem (Step 4) dispatches one
invocation per classified friction bullet, so each follow-up lands
as its own PR. For Upstream-plugin / Cross-project-request bullets
the PR goes against the resolved foreign repo; for Local bullets it
goes against this repo. Idempotent and non-interactive: takes the
target-repo path, slug, headline, classification, and tag; reuses or
creates a worktree on that repo; branches from origin/main
(—target-base default main, —target-remote default origin);
scaffolds the task via sdlc task create at the fallback status
(default planning/draft); optionally drives it to open/ready via
auto-define + ensure-ready when —drive-to-ready is set; commits on
—branch (default meta-task/
Allowed tools
Section titled “Allowed tools”BashReadEditWriteAgent
Source
Section titled “Source”Usage:
/sdlc:spawn-task-pr \ --target-repo <absolute-path-to-target-repo-root> \ --slug <kebab-slug> \ --headline "<one-line headline>" \ --classification <Local|Upstream-plugin|Cross-project-request> \ [--tag <classification-tag>] # e.g. sdlc-meta or cross-project-request; omit for Local --originating-task <originating-basename> \ [--brief "<short body brief>"] # passed into the spawned task's Goal section [--drive-to-ready true|false] # default false; true attempts open/ready before the PR [--fallback-status <planning/...>] # default planning/draft; landing status when not driven [--branch <name>] # default meta-task/<slug>; the branch to scaffold onto [--pr open|rolling] # default open (one fresh PR); rolling appends to the branch's open PR [--pr-title "<title>"] # PR title when creating; default is the headline [--target-remote <name>] # default: origin [--target-base <branch>] # default: main [--no-push] # rehearse: skip push and PR creation--drive-to-ready and --fallback-status come from the caller’s resolved
task.execution.spawn_from_post_mortem policy (see
${CLAUDE_PLUGIN_ROOT}conventions/sdlc-yaml.md); /sdlc:spawn-from-post-mortem
passes them through. Omitted, they default to the pre-drive behavior: land the
task at planning/draft, no readiness drive.
--branch / --pr / --pr-title implement the PR-grouping policy
(target.pr_grouping + target.pr_title_pattern). The caller resolves the
branch, PR mode, and final title per grouping mode and passes them here; this
skill just honors them. Omitted, they default to today’s per-task behavior:
branch meta-task/<slug>, one fresh PR, title = the headline.
Acting on the result — recording the PR URL in the originating post-mortem, reporting back to a parent sub-agent — is the caller’s job.
Output contract — deterministic markers
Section titled “Output contract — deterministic markers”Exactly one line on stdout, of the form:
<MARKER> ...Where MARKER is one of:
SPAWN-TASK-PR-DONE pr=<pr-url> target=<owner/name> branch=<branch> status=<final-status> action=<created|appended>— the spawned task landed on<branch>and is carried by an open PR. The PR URL is the fullhttps://github.com/...link.status=is the spawned task’s final status:open/readywhen the readiness drive succeeded, otherwise the fallback status.action=createdwhen this call opened the PR,action=appendedwhen it added the task to a branch whose PR was already open (--pr rolling). Keep theSPAWN-TASK-PR-prefix: a bareDONE pr=collides withtask-work’sTASK-WORK-DONE pr=#<N>and short-circuits an outer task-work LLM (2026-05-28 incident).SPAWN-TASK-PR-REHEARSED branch=<meta-task/...>—--no-pushwas supplied; the target worktree exists, the task file was scaffolded and committed on a local meta-task branch, but nothing was pushed and no PR was opened. The branch is left in place for human inspection.SPAWN-TASK-PR-EXISTING pr=<pr-url>— a PR with the same head branch already exists on the target. The skill is a no-op: branch, commits, and PR are left untouched. The caller should treat this as success and reuse the URL.ERROR reason="<one-line reason>"— could not proceed (target repo missing, push rejected,gh pr createfailed, etc.). Side effects may be partial; the message names the failing step.ERRORis the one un-prefixed marker (shared by convention across all skills).
Procedure
Section titled “Procedure”1. Resolve and validate inputs
Section titled “1. Resolve and validate inputs”Required: --target-repo, --slug, --headline, --classification,
--originating-task. --tag is required when --classification is
Upstream-plugin or Cross-project-request, and omitted (or empty)
for Local. If any required flag is missing, emit
ERROR reason="missing required flag <name>" and exit 2.
--classification MUST be one of Local, Upstream-plugin,
Cross-project-request. Reject any other value with
ERROR reason="unknown classification: <value>" and exit 2.
--drive-to-ready, if present, MUST be true or false (default false).
--fallback-status, if present, MUST be a planning/* status
(planning/draft, planning/needs-definition, planning/proposed,
planning/backlog; default planning/draft). Reject other values with
ERROR reason="bad fallback-status: <value>" and exit 2 — a spawned follow-up
never lands at a ready/in-progress/closed status.
--pr, if present, MUST be open or rolling (default open). Reject other
values with ERROR reason="bad pr mode: <value>" and exit 2.
Resolve the branch and worktree leaf (used by Steps 2, 3, 7):
<branch>=--branchif given, elsemeta-task/<slug>.<wt-leaf>=<branch>with a leadingmeta-task/stripped and any remaining/replaced by-(so the defaultmeta-task/<slug>→<slug>, unchanged; a sharedmeta-task/followups-<orig>→followups-<orig>).<pr-title>=--pr-titleif given, else<headline>. Used only when Step 7 CREATES a PR.
For a shared branch (--pr rolling), multiple tasks reuse the SAME <branch>
and <wt-leaf> across dispatches — reusing the worktree/branch is expected,
not an error.
Resolve the target repo:
--target-repoMUST be an absolute path to a directory.- Confirm it is a git working tree (
git -C <target-repo> rev-parse --show-toplevelsucceeds). If not, emitERROR reason="not a git working tree: <path>"and exit 1. - Read the target’s
originURL (git remote get-url <target-remote>, defaultorigin); if absent, emitERROR reason="target has no <remote> remote"and exit 1. - Derive the
<owner>/<name>forgh pr create --repofrom the origin URL (strip.git; handle both SSHgit@github.com:o/n.gitand HTTPShttps://github.com/o/n.gitshapes).
2. Ensure a clean worktree on the target
Section titled “2. Ensure a clean worktree on the target”Always operate inside a dedicated worktree on the target repo, never in its main checkout — the main checkout may hold in-flight work. This applies to the foreign-repo case (Upstream-plugin / Cross-project-request) and the same-repo case (Local), where the target IS this project. The worktree path convention is:
<target-repo>/.sdlc/worktrees/<wt-leaf>Procedure:
-
Inside the target repo,
git fetch <target-remote> <target-base>so the branch off-point is up to date. -
If
<target-repo>/.sdlc/worktrees/<wt-leaf>already exists AND its HEAD branch matches<branch>, reuse it (expected for a shared--pr rollingbranch). Otherwise, if it exists with a mismatched branch, emitERROR reason="target worktree path occupied: <path>"and exit 1. -
Otherwise create the worktree. For
--pr rollingon a shared branch, base it on the branch’s remote tip if that branch already exists on the remote (so the rolling branch keeps its accumulated tasks), else on<target-remote>/<target-base>:# fresh branch (per-task, or first task of a rolling branch):git -C <target-repo> worktree add \<target-repo>/.sdlc/worktrees/<wt-leaf> \-b <branch> <target-remote>/<target-base># rolling branch that already exists on the remote:git -C <target-repo> worktree add \<target-repo>/.sdlc/worktrees/<wt-leaf> \-b <branch> <target-remote>/<branch> -
If a
<branch>branch already exists locally but is NOT checked out anywhere, preferworktree add -B <branch>so it is reset to the correct base (the remote branch tip for a rolling branch that exists remotely, else<target-remote>/<target-base>).
3. Check for an existing PR before doing anything destructive
Section titled “3. Check for an existing PR before doing anything destructive”Before scaffolding or committing, ask GitHub whether a PR with this
head branch is already open, and record <pr-url> if so:
gh pr list --repo <owner/name> --head <branch> --json url,state --limit 1Branch on --pr:
--pr open(default, per-task): if a result returns withstate == "OPEN", emitSPAWN-TASK-PR-EXISTING pr=<url>and exit 0 — the caller reuses it (today’s idempotency). The worktree may or may not exist locally; do not tear it down.--pr rolling(shared branch): do NOT exit — an open PR is the expected append target. Instead:- Record
pr_exists=true, pr_url=<url>(orpr_exists=false) for Step 7. - Per-slug idempotency: check whether this task already exists on the
branch —
git -C <target-worktree> ls-files docs/planning/tasks/ | command grep -F "<slug>"(also check the working tree). If a file for<slug>is already present, this task was added on a prior run: skip Steps 4–6a entirely and go to Step 7 to report the existing rolling PR (action=appended). Otherwise continue to Step 4 to add it.
- Record
4. Scaffold the task file on the target
Section titled “4. Scaffold the task file on the target”Invoke sdlc task create against the target’s project-root. The plugin
root for this skill is ${CLAUDE_PLUGIN_ROOT} (the calling skill’s
plugin install, which is where this SKILL.md lives).
${CLAUDE_PLUGIN_ROOT}cli/sdlc task create \ <slug> \ --headline "<headline>" \ --status <fallback-status> \ --impact medium \ --complexity small \ --related <originating-basename> \ [--tags <classification-tag>] \ --project-root <target-repo>--status is the resolved --fallback-status (default planning/draft). The
task is always scaffolded at this non-ready status; the optional readiness drive
(Step 6a) is what promotes it to open/ready.
Pass --tags <classification-tag> only when --classification is
Upstream-plugin or Cross-project-request. Omit --tags for
Local — local follow-ups don’t need a classification tag.
The op prints the new file’s absolute path on stdout (inside the
target’s docs/planning/tasks/ directory). Capture it for the next
step.
5. Flesh out the body
Section titled “5. Flesh out the body”Edit the new task file to fill at minimum:
## Goal— one paragraph: what gap on this repo, originating from[[<originating-basename>]]on<originating-repo-url>, needs closing. If--briefwas supplied, splice its text in here verbatim as a quote block, attributed to the originating task.## Discovery context— one line:Spawned by /sdlc:spawn-task-pr on <YYYY-MM-DD UTC> from [[<originating-basename>]] in <originating-repo-url>.
Other body sections (Today / Proposed / Approach / Acceptance
criteria / Out of scope / Dependencies) get placeholder stubs. The
placeholder convention is one line per section:
_TBD — receiver to fill before promoting from planning/draft._
These stubs are the landing state when the task is NOT driven to ready.
When --drive-to-ready true, Step 6a runs /sdlc:task-auto-define, which
best-effort replaces these stubs with synthesized content and promotes the
task to open/ready; if it cannot, the stubs remain and the receiver fills
them before promoting from the fallback status.
Run the frontmatter validator:
${CLAUDE_PLUGIN_ROOT}cli/sdlc entities validate <new-task-path>If it fails, emit ERROR reason="frontmatter validation failed: <stderr line>"
and exit 1.
6. Commit on the target worktree
Section titled “6. Commit on the target worktree”Stage only the new task file:
git -C <target-worktree> add docs/planning/tasks/<file>.mdCommit through sdlc commit create --message - (per
${CLAUDE_PLUGIN_ROOT}conventions/commit-messages.md) so the message
reaches git through a tempfile and survives shell expansion on the
parens-heavy docs(tasks): subject. --project-root <target-worktree>
targets the worktree; the op commits the already-staged index, so the
git add above narrows the scope.
${CLAUDE_PLUGIN_ROOT}cli/sdlc commit create \ --project-root <target-worktree> --message - <<'EOF'docs(tasks): spawn <slug> from <originating-basename> post-mortem
Follow-up spawned from [[<originating-basename>]] in<originating-repo-url>. Classification: <classification>.EOF6a. Drive to ready (only when —drive-to-ready true)
Section titled “6a. Drive to ready (only when —drive-to-ready true)”Skip this step entirely when --drive-to-ready is absent or false — the
task’s final status is the fallback status from Step 4, and you proceed to
Step 7.
When true, best-effort drive the scaffolded task to open/ready before the
PR opens, so the follow-up lands pickable instead of as a draft. The commit
from Step 6 gives the drive a clean tree to work from (auto-define reads a
committed body; ensure-ready refuses to stamp over uncommitted edits).
Dispatch ONE sub-agent (Agent tool, subagent_type: general-purpose) that
runs the two readiness skills in the target worktree and reports the outcome.
Brief it:
Drive a freshly-scaffolded follow-up task to open/ready, best-effort.Working directory / repo: <target-worktree>Plugin root: ${CLAUDE_PLUGIN_ROOT}Task file: <absolute path to the new task file in the worktree>
Do, in order:1. Follow /sdlc:task-auto-define for this one task with --set-ready true: read ${CLAUDE_PLUGIN_ROOT}skills/task-auto-define/SKILL.md and execute it against the task file. It synthesizes the missing implementation-ready sections from the task's prose + this repo's code, commits on the current branch, and flips status to open/ready — OR bails with TASK-AUTO-DEFINE-INSUFFICIENT if the spec can't be synthesized.2. If (and only if) auto-define reported TASK-AUTO-DEFINE-DEFINED, follow /sdlc:task-ensure-ready for the same task to verify and stamp readiness_verified_at (read ${CLAUDE_PLUGIN_ROOT}skills/task-ensure-ready/SKILL.md).Do not push and do not open a PR — the parent skill owns that.Report exactly one line: DRIVE-RESULT status=<final-status> where final-statusis open/ready on success, or the task's current fallback status if auto-definewas INSUFFICIENT or ensure-ready found a gap.Read the sub-agent’s DRIVE-RESULT status=<...> line and carry <final-status>
into Steps 7–8. If the sub-agent errored or returned no parseable line, treat
the drive as unsuccessful: the final status is the fallback status (the Step 6
commit already landed the valid draft), and you continue to Step 7 — a failed
drive never fails the spawn. If Agent is unavailable, inline the two skills’
procedures yourself against the task file, holding the same order and the
never-fabricate discipline.
The drive’s commits (synthesized body, status flip, readiness stamp) are on
<branch> and ride the push in Step 7.
7. Push and open or update the PR (unless —no-push)
Section titled “7. Push and open or update the PR (unless —no-push)”If --no-push was supplied, emit SPAWN-TASK-PR-REHEARSED branch=<branch>
and exit 0. The branch is left in place for human inspection; no
remote state changes.
Otherwise push <branch> (this both creates the remote branch and, when a PR
already tracks it, updates that PR with the new commits):
git -C <target-worktree> push -u <target-remote> <branch>If the push fails (auth, protected branch, etc.), emit
ERROR reason="push failed: <stderr first line>" and exit 1.
Then decide create-vs-append:
-
A PR is already open for
<branch>— the--pr rollingappend case (pr_exists=truefrom Step 3). The push above already updated it. Do NOT create a second PR. Setaction=appendedand<pr-url>to the recorded URL, and skip to Step 8. -
No PR open for
<branch>— create one (--pr open, or the first task of a rolling branch). Setaction=created. The body shape varies by classification so the reviewer sees the right framing:gh pr create --repo <owner/name> \--base <target-base> \--head <branch> \--title "<pr-title>" \--body "$(cat <<'EOF'## Summary- Follow-up(s) spawned from a /sdlc:task-work post-mortem.- Originating task: [[<originating-basename>]] in <originating-repo-url>.- Classification: <classification>.## What this PR delivers- A task file at `docs/planning/tasks/<file>.md` (status `<final-status>`).- <If final-status is open/ready:> The spec was best-effort machine-authoredby `/sdlc:task-auto-define` and carries an `AUTO-DEFINED:` note — review theGoal, Approach, Today, Files-to-touch, and Acceptance-criteria beforetrusting it.- <If final-status is a planning/* status:> The body sections are stubs — thereceiver should fill them before promoting to `open/ready`.<!-- include exactly one of the two bullets above, matching final-status -->- <If this is a rolling branch (--pr rolling):> Further follow-ups append tothis PR as later runs spawn them.<!-- include only for --pr rolling -->## Notes- <For --pr open:> Each spawned follow-up gets its own PR so the reviewer canaccept or reject this gap on its own merits, independent of the originatingtask's PR.- <Tagged `<classification-tag>` so this repo's task review can filterfor inbound cross-repo asks.> <!-- include this bullet only when --tag was supplied -->EOF)"Capture the URL
gh pr createprints on stdout.
8. Report
Section titled “8. Report”Emit on stdout:
SPAWN-TASK-PR-DONE pr=<pr-url> target=<owner/name> branch=<branch> status=<final-status> action=<created|appended><final-status> is open/ready when Step 6a’s drive succeeded, otherwise the
fallback status. Exit 0.
Idempotency
Section titled “Idempotency”--pr open— re-invoking with identical arguments after a successful run returnsSPAWN-TASK-PR-EXISTING pr=<url>(caught in Step 3), never re-push or re-create.--pr rolling— re-invoking for a<slug>already present on<branch>is a no-op that reports the open rolling PR withaction=appended(Step 3’s per-slug idempotency check); it never double-adds the task.
The local worktree and branch are left in place — the caller (typically
/sdlc:spawn-from-post-mortem) decides whether to keep or tear them down.
Failure modes
Section titled “Failure modes”Capture context, do not silently abandon:
- Target is dirty. If
git -C <target-worktree> status --porcelainis non-empty before step 4, emitERROR reason="target worktree dirty: <first line>"and exit 1. Never auto-stash on the target repo — the dirty state may be the user’s in-progress work (true for both foreign-repo and Local dispatch). sdlc task createerrors. Surface the op’s stderr verbatim in the ERROR reason; do not invent fallback content.- Validator failure. Surface the first failing field; do not attempt to auto-fix frontmatter on the target.
gh pr createrate-limited or auth-denied. Surface as ERROR; the branch IS pushed at that point. Idempotency in step 3 means a manual re-invocation will find no open PR (push without PR is fine) and proceed to a secondgh pr createcall.- Readiness drive fails (Step 6a). Never an ERROR — the Step 6 commit already landed a valid draft. The drive is best-effort: on INSUFFICIENT, a gate gap, or a sub-agent error, the task simply keeps the fallback status and the spawn proceeds to push + PR.
- Drive to ready is best-effort and off by default.
--drive-to-ready true(Step 6a) runs/sdlc:task-auto-define --set-ready true+/sdlc:task-ensure-readyso the follow-up can landopen/readyinstead of a draft; when the spec can’t be synthesized or fails the gate, it lands at--fallback-status(defaultplanning/draft). Both flags come from the caller’s resolvedtask.execution.spawn_from_post_mortempolicy — see${CLAUDE_PLUGIN_ROOT}conventions/sdlc-yaml.md. - Commit messages. See
${CLAUDE_PLUGIN_ROOT}conventions/commit-messages.md— route the commit throughsdlc commit create --message -(Step 6) so the parens-bearingdocs(tasks):subject reaches git through a tempfile; single-line-mmis-quotes the parens-bearing classification tokens in the subject. - Branch naming. See
${CLAUDE_PLUGIN_ROOT}/conventions/branch-naming.md— this skill uses themeta-task/<...>namespace on the target repo, distinct from thetask/<slug>branch a futuretask-workrun creates for the same slug. Themeta-task/prefix also keeps these PRs out of the orchestrator’s in-flight task count. Per-task usesmeta-task/<slug>; shared modes use a caller-chosen shared branch (e.g.meta-task/followups-<orig>per-execution,meta-task/spawn-followupsper-project). - PR grouping.
--branch+--pr open|rollingare the mechanism; the CALLER (/sdlc:spawn-from-post-mortem) mapstarget.pr_grouping/target.pr_title_pattern(see${CLAUDE_PLUGIN_ROOT}conventions/sdlc-yaml.md) to a branch, PR mode, and resolved title. This skill stays a pure mechanism — it never reads config or resolves title placeholders itself.