Skip to content

/sdlc:consolidate-task-prs

Generated from solutions/ontological/skills/consolidate-task-prs/SKILL.md.

Collapse every open single-task-file PR — each carrying one new draft task file under docs/planning/tasks/ — into a single consolidation PR, deduping the folded tasks against the present task list, then close the superseded PRs and delete their branches. Explicit cleanup only, never auto-invoked: run it to fold an accumulation of one-task-each PRs back into one reviewable PR.

  • Read
  • Bash
  • Edit
  • AskUserQuestion

Invoke explicitly to clean up an accumulation of one-task-each PRs:

/sdlc:consolidate-task-prs

Project context (don’t re-derive every run):

  • A “single-task-file PR” is an open PR whose entire diff is exactly one new .md file under docs/planning/tasks/ — an addition, no other files touched. That is the shape /sdlc:spawn-task-pr produces on a meta-task/<slug> branch, and the shape a hand-made docs/<basename> task-spec PR usually has. See ${CLAUDE_PLUGIN_ROOT}conventions/branch-naming.md.
  • This skill is the inverse of /sdlc:spawn-task-pr: that skill fans one PR out per post-mortem friction bullet; this skill folds the accumulation back into one PR.
  • The consolidation PR is opened and confirmed before any original PR is closed. Closing a folded PR with --delete-branch removes its only copy of the draft task, so the consolidation PR must already carry that file first.
  • Draft task files are at status: planning/draft and are never under a task-lifecycle lease (a lease is only taken when /sdlc:task-work starts implementation). This skill therefore does no lease handling.
  • Confirm gh is authenticated (gh auth status); if not, stop and tell the user to authenticate. Every PR query and mutation goes through gh.
  • Resolve the repo root (git rev-parse --show-toplevel) and the <owner>/<name> from git remote get-url origin (strip .git; handle SSH and HTTPS shapes). All gh calls run against this repo.

Operate from the repo root, not whatever worktree you were invoked in — the consolidation worktree and every git worktree op target the primary checkout. The base branch is main.

List open PRs against main, then classify each by its file diff.

gh pr list --state open --base main \
--json number,title,headRefName,url,isDraft,reviewDecision,mergeable,author \
--limit 200

For each PR, fetch its changed files and classify it:

gh pr view <N> --json files -q '.files'
  • fold — the diff is exactly one file, that file is a new addition matching docs/planning/tasks/*.md, and the PR has no blocking signal (not isDraft, reviewDecision is empty/null, mergeable is not CONFLICTING).
  • needs-decision — the diff matches the single-task-file shape but a blocking signal is present: isDraft is true, a review exists (reviewDecision is APPROVED / CHANGES_REQUESTED / REVIEW_REQUIRED), or mergeable is CONFLICTING. List these for the user but leave them alone — do not auto-fold them.
  • skip — the diff touches more than one file, modifies an existing file, or the single file is not a docs/planning/tasks/*.md addition. Not a single-task-file PR.

Never classify the skill’s own consolidation branch (chore/consolidate-task-prs-*) as a candidate.

If there are zero fold PRs, print Nothing to consolidate. and stop.

3. Dedup each candidate against the present task list

Section titled “3. Dedup each candidate against the present task list”

Create the consolidation worktree off origin/main so the present task list is clean, then dedup before materializing anything.

git -C <repo-root> fetch origin main --quiet
git -C <repo-root> worktree add <repo-root>/.sdlc/worktrees/consolidate-task-prs-<today-UTC> \
-B chore/consolidate-task-prs-<today-UTC> origin/main

For each fold candidate, read the draft task it carries from the PR head without checking it out:

git -C <repo-root> fetch origin <headRefName> --quiet
git -C <repo-root> show origin/<headRefName>:docs/planning/tasks/<file>.md

From that content take the headline — the body’s first # heading, NOT a headline: frontmatter key; the task schema has no such key, and grepping for one yields an empty bullet that scores against nothing — plus the ## Goal body text, and score the pair against the present task list (the worktree’s clean docs/planning/tasks/, which does not yet contain the candidate):

bun run ${CLAUDE_PLUGIN_ROOT}skills/task-work/dedup_search.ts search \
--bullet "<headline> — <goal text>" \
--tasks-dir <worktree>/docs/planning/tasks/ \
--json

A cheap way to get both fields without hand-parsing is the companion op, which already extracts them from every open PR head:

${CLAUDE_PLUGIN_ROOT}cli/sdlc task list-unmerged --output json

Its tasks[] rows carry basename, status, headline and the full text for exactly these candidates.

Branch on the script’s decision (see its module docstring for the scoring rule and threshold defaults):

  • SPAWNED — no present task is a near-duplicate. Mark the candidate fold: write its task file into the worktree’s docs/planning/tasks/<file>.md.
  • LINKED-EXISTING — the candidate duplicates the present task named in link_to. Mark it dropped-as-dup: do not add the file. Edit the existing present task — append the candidate’s originating basenames to its related: array (de-duplicated) and add one ## Discovery context line recording the dropped PR (e.g. Duplicate draft proposed in #<N>; folded by /sdlc:consolidate-task-prs.).

Guard against candidate-vs-candidate collisions: if two fold candidates resolve to the same target filename, keep the first and re-mark the second needs-decision with reason “same filename as #“.

Render one markdown plan, grouped by action so review is one read:

# Consolidation plan
## Will fold into the consolidation PR (<N>)
- #<pr> <title> → docs/planning/tasks/<file>.md
## Dropped as duplicate (<M>)
- #<pr> <title> → duplicate of [[<existing-basename>]]; will link, not add
## Needs your decision — left alone (<K>)
- #<pr> <title> — <reason: draft / has review / conflicts / same filename as #X>
## Will close + delete branch on approval (<N+M>)
- #<pr> (head <headRefName>) — folded | dropped-as-dup
## Leaving alone (skipped)
- #<pr> <title> — <why it is not a single-task-file PR>

State plainly that close + branch deletion happens only after the consolidation PR is confirmed open, and that --delete-branch removes the remote head branch (and the local branch if present).

Closing PRs and deleting branches is destructive and outward-facing, so get one confirmation. Ask one AskUserQuestion with the plan above shown:

  • Proceed (Recommended) — open the consolidation PR (Step 6), then close + delete every folded and dropped-as-dup PR (Step 7).
  • Cancel — change nothing — remove the consolidation worktree and branch and exit without mutating any PR.

Land every folded task file plus the dropped-as-dup link edits on the consolidation branch as one commit, then open one PR.

Validate every file you wrote or edited (no pipe — the exit code must propagate so a bad file blocks the commit):

${CLAUDE_PLUGIN_ROOT}cli/sdlc entities validate <path>

Stage only the task files you touched, then commit through sdlc commit create --message - so the parens-bearing docs(tasks): subject reaches git through a tempfile (see ${CLAUDE_PLUGIN_ROOT}conventions/commit-messages.md):

git -C <worktree> add docs/planning/tasks/
${CLAUDE_PLUGIN_ROOT}cli/sdlc commit create \
--project-root <worktree> --message - <<'EOF'
docs(tasks): consolidate <N> single-task PRs into one
Folds the draft task files from #<a>, #<b>, ... onto one PR and drops
<M> as duplicates of existing tasks. Superseded PRs are closed and
their branches deleted by /sdlc:consolidate-task-prs.
EOF

Push the branch and open exactly one PR against main, its body enumerating every folded PR (with links) and every dropped-as-dup decision:

git -C <worktree> push -u origin chore/consolidate-task-prs-<today-UTC>
gh pr create --repo <owner/name> --base main \
--head chore/consolidate-task-prs-<today-UTC> \
--title "docs(tasks): consolidate <N> single-task PRs" \
--body "<enumerated body>"

Capture the consolidation PR number and URL. If the push or gh pr create fails, stop and surface the error — do not proceed to Step 7. No original PR has been closed at this point, so the repo is recoverable.

7. Close superseded PRs and delete branches

Section titled “7. Close superseded PRs and delete branches”

Run this step only after the consolidation PR is confirmed open (Step 6 returned a PR number).

For each folded and dropped-as-dup PR, close it with a comment pointing at the consolidation PR and delete its branch in one call:

gh pr close <N> --repo <owner/name> --delete-branch \
--comment "Superseded by #<consolidation> — its draft task was <folded into|dropped as a duplicate of [[<existing>]] in> the consolidation PR."

--delete-branch deletes the remote head branch and the local branch if it exists. If a close fails (protected branch, race, auth), do not silently continue: print the failure and the remaining queue, then ask the user via AskUserQuestion whether to continue with the rest, retry, or stop (default: stop).

Confirm the end state and print a summary.

  • gh pr view <consolidation> --json state shows OPEN.
  • For each closed PR, gh pr view <N> --json state shows CLOSED.
  • For each deleted branch, git -C <repo-root> ls-remote --heads origin <headRefName> is empty.

Print one summary: the consolidation PR URL, the count folded, the count dropped-as-dup (with link targets), the count of PRs closed + branches deleted, and any needs-decision / skipped PRs left untouched. If any verification check failed, surface it rather than reporting clean.

  • Read-only until Step 6. Discovery (Step 2) and dedup (Step 3) only read PR/branch state and write into a throwaway worktree. No PR is mutated and no branch is deleted until the user confirms in Step 5.
  • Open before close. The ordering in Steps 6→7 is load-bearing: a folded PR’s branch holds the only copy of its draft task until the consolidation PR carries it. Never reorder these.
  • Dedup is borrowed from task-work. This skill reuses ${CLAUDE_PLUGIN_ROOT}skills/task-work/dedup_search.ts — the same keyword-scoring dedup /sdlc:spawn-from-post-mortem uses — so a duplicate is dropped-and-linked the same way on both the spawn and the consolidate side. If a third caller needs it, promote it to an sdlc verb (${CLAUDE_PLUGIN_ROOT}conventions/typescript-runtime.md, “co-locate first, promote when shared”).
  • Branch naming. The consolidation branch is chore/consolidate-task-prs-<today-UTC> — the bulk-work chore/ namespace, distinct from task/ and meta-task/. See ${CLAUDE_PLUGIN_ROOT}conventions/branch-naming.md.
  • Committing model-generated messages. The consolidation commit body and the PR body are model-authored; route the commit through sdlc commit create --message - per ${CLAUDE_PLUGIN_ROOT}conventions/commit-messages.md.
  • What this skill does NOT do. It does not promote folded drafts off planning/draft, does not regenerate entity rosters (draft task additions don’t change them; run /sdlc:docs if a later promotion needs it), and does not implement any task. It only collapses single-task-file PRs into one and cleans up the originals.