T-RDKI-extract-task-close-out-skill
Status: closed/done · Impact: high · Complexity: small
Pull Step 11 of /sdlc:task-work (post-merge close-out) into a standalone, callable skill
/sdlc:task-close-out <task-slug>. The orchestrator invokes it when /sdlc:pr-check returns
MERGED; /sdlc:task-work keeps its Step 11 prose but delegates to the new skill, so the close-out
behavior lives in exactly one place. Today there is no entry point to run Step 11 against an
already-merged task — the user has to re-enter the whole 13-step skill, which is wrong shape for an
orchestrator dispatch.
plugin/skills/task-work/SKILL.md Step 11 documents the close-out procedure inline:
- 11a: edit task file to
status: closed/done, updatelast_reviewed, addcompletion_note: "Shipped via #<pr-number>.", droprelevance_note, clearreadiness_verified_at; commit on main; push. - 11b: tear down worktree (
git worktree remove --force .claude/worktrees/<basename>+ prune). - 11c: delete local + remote branches.
- 11d: verify (log shows close-out commit;
git worktree listclean; branch gone).
The skill body says this should fire “whenever the user reports the PR is merged, or when you notice
the merge as part of any later check.” There is no callable entry point — invoking
/sdlc:task-work <slug> against an already-merged task would re-trigger the pre-flight checks and
fail (Step 2 blocks if a worktree already exists for the task, etc.). Current evidence of the gap:
2026-05-19-add-epic-entity-task-depends-on-dependencies is status: in-progress despite PR #18
being merged on 2026-05-19; no automated path got it to closed/done.
Proposed
Section titled “Proposed”New skill at plugin/skills/task-close-out/SKILL.md invocable as
/sdlc:task-close-out <task-slug-or-path>. Inputs:
- Task slug (basename without
.md) OR absolute path to task file. - Optional
--pr <N>to override PR detection (default: derive from branchfeat/<basename>→ most recent merged PR with that head).
Procedure (mirrors task-work Step 11 with one extension):
-
Resolve task file via the same file-resolution rules as
/sdlc:task-work(useplugin/entities/task/file-resolution.md). -
Verify task is eligible to close:
status: in-progressORstatus: in-progress/blocked. Ifclosed/*, exitALREADY-CLOSED. Ifreadyor earlier, exitNOT-IN-PROGRESS error="task hasn't started". -
Detect the merged PR:
gh pr list --search "head:feat/<basename> is:merged" --state merged --json number,mergedAt,mergeCommit --limit 1If none found and
--prnot passed, exitNO-MERGED-PR. -
Run 11a: edit frontmatter (
status: closed/done,last_reviewed: <today UTC>, addcompletion_notewith PR number, droprelevance_note, clearreadiness_verified_at); commit on main withdocs(tasks): mark <basename> closed/doneand a “Shipped via #.” body; pull —rebase —autostash; push. -
Run 11b: tear down worktree at
.claude/worktrees/<basename>(skip silently if not present — supports “task closed manually but worktree forgotten” case). -
Run 11c: delete
feat/<basename>locally (safe-d, fall back to logged warning if unmerged); deleteorigin/feat/<basename>if present; fetch —prune. -
Run 11d: verify and emit
DONE pr=#<N> worktree=removed branch=deletedon success, orPARTIAL <what-couldn't-be-done>if any teardown step failed.
Refactor of /sdlc:task-work Step 11 to delegate: replace the inline prose with “invoke
/sdlc:task-close-out <basename> and report its return.” This keeps the behavior in one place and
means future close-out tweaks land in one file.
Approach
Section titled “Approach”- Read
plugin/skills/task-work/SKILL.mdStep 11 carefully — capture every side effect. - Scaffold
plugin/skills/task-close-out/SKILL.mdwithallowed-tools: [Bash, Read, Edit]. - Port Step 11 prose, verbatim where possible, into the new skill. Add the PR-detection + pre-flight (steps 2–3 above).
- Edit
plugin/skills/task-work/SKILL.mdStep 11 to delegate. Keep the “when to invoke” guidance (“on user report of merge, or when noticed later”); replace the inline 11a–11d with “invoke/sdlc:task-close-outand report.” - Add
plugin/skills/task-close-out/invariants.yaml: “must verify PR is merged before editing task file”; “must clear readiness_verified_at on close”; “must commit on main, not on feature branch”; “must run pull —rebase —autostash before push to avoid race with concurrent orchestrator ticks.” - Manually run
/sdlc:task-close-out 2026-05-19-add-epic-entity-task-depends-on-dependenciesto reconcile the existing stale state. (This is itself the first orchestrator-reachable cleanup.)
Files to touch
Section titled “Files to touch”plugin/skills/task-close-out/SKILL.md(new) — the extracted close-out procedure.plugin/skills/task-close-out/invariants.yaml(new) — lint invariants.plugin/skills/task-work/SKILL.md— Step 11 prose replaced with delegation to/sdlc:task-close-out.docs/planning/tasks/2026-05-19-add-epic-entity-task-depends-on-dependencies.md— closed out as part of acceptance verification (not in the same commit as the skill addition; treat as a follow-on dry-run).
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
/sdlc:task-close-out <slug>exists and is invocable. (auto) - AC-2: Invoking against a known-merged task
(
2026-05-19-add-epic-entity-task-depends-on-dependencies, PR #18) flips frontmatter tostatus: closed/donewithcompletion_notereferencing #18, removes the worktree at.claude/worktrees/2026-05-19-add-epic-entity-task-depends-on-dependencies, deletes thefeat/...branch local + remote, and emitsDONE pr=#18 .... (agent-manual) - AC-3: Invoking against a non-merged task returns
NO-MERGED-PRand makes no file edits, no commits, no branch deletions. (agent-manual: try on2026-05-19-build-import-planning-skillwhich isin-progresswithout a merged PR) - AC-4: Invoking against an already-closed task returns
ALREADY-CLOSEDand is a no-op. (auto via fixture or agent-manual) - AC-5:
/sdlc:task-workStep 11 prose delegates to/sdlc:task-close-outand no longer contains the inline edit/commit/teardown procedure. The behavior change is captured by following the delegation chain. (auto: greptask-work/SKILL.mdfor the literalgit worktree removeand confirm it’s gone) - AC-6:
lint_skill_prose.pypasses for the new invariants. (auto)
Out of scope
Section titled “Out of scope”- Detecting
closed/wontdo/closed/supersededpaths (where there’s no PR to point at). The orchestrator only calls close-out on theMERGEDverdict; the other terminal states are still set manually by the user or bytask-review. - A “rollback” mode for accidentally closed-out tasks.
- Migration of existing stale worktrees beyond the one we test on. The orchestrator (once shipped) handles the rest naturally.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Phase 1 of epic T-FQCN-self-driving-orchestrator-loop. Identified during conversation
2026-05-19: the orchestrator needs a callable close-out step, and task-work Step 11 is the natural
home for the behavior but the wrong shape for orchestrator dispatch. Extraction is also cleanup —
having close-out logic live in one place is good independently of the orchestrator landing.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-05-20. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”The AC fixtures named in the spec (the add-epic-entity-task-depends-on-dependencies and
build-import-planning-skill tasks) were already closed/done before this run started — they were
the motivating examples back when the spec was written, but the gap they pointed at has since been
reconciled manually. Verification used synthetic-fixture traces instead.
- AC-1: auto —
plugin/skills/task-close-out/SKILL.mdexists and is the documented invocation surface;lint_skill_prose.pyconfirms structural conformance. - AC-2 (happy path): agent-manual — synthetic trace against
feat/2026-05-19-add-pr-check-skill. Step 3’sgh pr list --search "head:feat/... is:merged"returns PR #39 (mergedAt: 2026-05-20T03:36:41Z). With auto-detected--pr 39, Step 4 would edit frontmatter (already done by the prior close-out, no-op), Step 5 would skip worktree (already removed →worktree=absent), Step 6 would skip branch deletion (already gone →branch=deletedvia the empty check). Final marker:DONE pr=#39 worktree=absent branch=deleted. The auto-detection query and the gate-on-no-changes-needed semantics are both demonstrated; the file-edit path itself is structurally identical to the manual close-out that just ran. - AC-3 (NO-MERGED-PR): agent-manual — synthetic trace against this very task’s branch
(
feat/2026-05-19-extract-task-close-out-skill). Query returns[](PR isn’t open yet). Procedure emitsNO-MERGED-PR reason="no merged PR found for head feat/2026-05-19-extract-task-close-out-skill"and exits. Crucially, this trace fires before any file edits, commits, or branch deletions — the invariants linter pins the literal “no file edits, no commits, no branch deletions” phrase in Step 3 to enforce this property going forward. - AC-4 (ALREADY-CLOSED): agent-manual — synthetic trace against
2026-05-19-add-pr-check-skill.md(status: closed/done). Step 2 gate trips onclosed/*prefix, emitsALREADY-CLOSED reason="status is closed/done", exits without reaching Step 3. No state changes. - AC-5: auto —
command grep "git worktree remove\|11a\.\|11b\.\|11c\.\|11d\."againstplugin/skills/task-work/SKILL.mdreturns no matches. The inline 11a–11d substeps are gone; Step 11 is a one-paragraph delegation to/sdlc:task-close-out.docs/skills/task-work.mdflowchart collapsed accordingly to a singleS11node. - AC-6: auto —
lint_skill_prose.py plugin/skills/*/SKILL.mdpasses 18/18 (6 with invariants files: task-work, import-planning, milestones-from-file, pr-check, project-cleanup, task-close-out — all clean).
What worked
Section titled “What worked”- The
forbidden_phrasesmechanism that pinned pr-check’s read-only stance worked again here: invariants pin the verdict enum, the no-mutation property onNO-MERGED-PR, and the autostash race-mitigation as load-bearing prose claims. - The Step 11 delegation refactor in
task-workcollapsed cleanly — one paragraph of prose replaces four substeps with no behavior loss because the close-out skill carries all the detail. - AC verification by synthetic-fixture trace (gh queries + gate-logic walk) was faster and safer than invoking the skill live against the repo state. The traces are reproducible and don’t need real GitHub state mutation to demonstrate.
Friction and automation gaps
Section titled “Friction and automation gaps”- AC-2 and AC-3 fixture rot: the named tasks (
add-epic-entity-task-depends-on-dependencies,build-import-planning-skill) were both alreadyclosed/doneby the time this task was picked up, breaking the literal AC wording. Required a triage step and a user decision (synthetic fixtures) before any code was written. Atask-review-style relevance check on AC fixture references would catch this earlier; broader fix is the orchestrator itself, which will keep fixture references fresh by closing tasks promptly. → T-IXV6-task-review-checks-ac-fixture-relevance - The skill cannot be exercised end-to-end without
/reload-pluginsafter the worktree’splugin/skills/task-close-out/exists, and even then the skill’s mutations (branch delete, worktree teardown) are destructive enough that real live testing requires throwaway fixtures. A--dry-runflag that printed each side effect without executing would let close-out be exercised against any in-progress task safely. → T-Y81J-task-close-out-dry-run-flag - Step 4’s “race against concurrent orchestrator ticks via
pull --rebase --autostash” remains spec-only. There’s no integration test for the race itself; we’re trustinggit’s well-known semantics. Worth a small fixture once the orchestrator is shipping ticks. → T-LN0B-orchestrator-tick-race-fixture gh pr list --search "head:feat/<basename> is:merged"can return false positives if the samefeat/<basename>name was reused across PRs (rare but possible). The skill takes--limit 1which is the most-recent — fine for now, but a sanity-check that the returned PR’smergeCommitis actually reachable fromorigin/mainwould harden against this. → T-RNW9-task-close-out-verifies-pr-on-main
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-IXV6-task-review-checks-ac-fixture-relevance — task-review verifies AC fixture references are still relevant, created.
- T-Y81J-task-close-out-dry-run-flag — adds
--dry-runto/sdlc:task-close-outfor safe rehearsal, created. - T-LN0B-orchestrator-tick-race-fixture — integration fixture for the concurrent-tick race on task-close-out, created.
- T-RNW9-task-close-out-verifies-pr-on-main — task-close-out verifies merged PR’s mergeCommit is reachable from origin/main, created.