skills/cleanup-feature/SKILL.md
Merge approved PR, migrate open tasks, archive OpenSpec proposal, and cleanup branches
npx skillsauth add jankneumann/agentic-coding-tools cleanup-featureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Merge an approved PR, migrate any open tasks to coordinator issues or a follow-up proposal, archive the OpenSpec proposal, and cleanup branches.
$ARGUMENTS - OpenSpec change-id (optional, will detect from current branch or open PR)
Optional flags:
--post-merge - PR was already merged by /merge-pull-requests; skip PR merge and pre-merge validation stages, then archive/spec-sync/cleanup local remnants.--pr <number> - PR number to verify when using --post-merge./implement-feature first if PR doesn't exist/validate-feature first to verify live deploymentUse OpenSpec-generated runtime assets first, then CLI fallback:
.claude/commands/opsx/*.md or .claude/skills/openspec-*/SKILL.md.codex/skills/openspec-*/SKILL.md.gemini/commands/opsx/*.toml or .gemini/skills/openspec-*/SKILL.mdopenspec CLI commandsUse docs/coordination-detection-template.md as the shared detection preamble.
CAN_* flag is trueAt skill start, run the coordination detection preamble and set:
COORDINATOR_AVAILABLECOORDINATION_TRANSPORT (mcp|http|none)CAN_LOCK, CAN_QUEUE_WORK, CAN_HANDOFF, CAN_MEMORY, CAN_GUARDRAILSIf CAN_HANDOFF=true, read latest handoff context before merge/archive actions:
read_handoff"<skill-base-dir>/../coordination-bridge/scripts/coordination_bridge.py" try_handoff_read(...)On handoff failure/unavailability, continue with standalone cleanup and log informationally.
# From argument or current branch
CHANGE_ID=$ARGUMENTS
# Or: CHANGE_ID=$(git branch --show-current | sed 's/^openspec\///')
# Verify
openspec show $CHANGE_ID
Launcher Invariant: The shared checkout is read-only. Perform all cleanup operations in a worktree:
eval "$(python3 "<skill-base-dir>/../worktree/scripts/worktree.py" setup "$CHANGE_ID" --agent-id cleanup)"
cd "$WORKTREE_PATH"
# The cleanup worktree is on its OWN scratch branch (with the --cleanup suffix),
# so that cleanup operations don't collide with a still-running implementation
# worktree. We need two distinct branch variables:
#
# CLEANUP_BRANCH — this worktree's own branch (openspec/<change-id>--cleanup
# by default, or <override>--cleanup when OPENSPEC_BRANCH_OVERRIDE
# is set). Used for teardown.
# FEATURE_BRANCH — the PARENT feature branch being merged/deleted. This is
# the branch implement-feature pushed and opened a PR against.
# Used for gh pr merge, git branch -d, and lock cleanup.
CLEANUP_BRANCH="$WORKTREE_BRANCH"
eval "$(python3 "<skill-base-dir>/../worktree/scripts/worktree.py" resolve-branch "$CHANGE_ID" --parent)"
FEATURE_BRANCH="$BRANCH"
If --post-merge is present, this skill is being called by /merge-pull-requests after that skill already merged the PR and pulled latest main.
In post-merge mode:
--pr <number> unless the merged PR can be resolved unambiguously from the change-id.gh pr view "$PR_NUMBER" --json number,state,mergedAt,headRefName,baseRefName
state is MERGED.CHANGE_ID; do not force-delete dirty worktrees or unmerged local branches unless the operator explicitly approves that separate action.# Check PR status
gh pr status
# Or check specific PR (use the resolved FEATURE_BRANCH, not a hardcoded prefix)
gh pr view "$FEATURE_BRANCH"
Confirm PR is approved and CI is passing before proceeding.
Check whether Docker-dependent validation has been run. Cloud-created PRs pass environment-safe checks during implementation but may lack deployment-based validation.
If validation-report.md exists at openspec/changes/<change-id>/ with deploy/smoke/security/e2e phases completed, skip this step.
Otherwise, if Docker is available (docker info succeeds), run the missing phases:
/validate-feature <change-id> --phase deploy,smoke,security,e2e
This delegates to the canonical validation skill for service lifecycle, smoke tests, security scanning, and E2E. The resulting validation-report.md is committed to the PR branch.
If Docker is not available, warn the operator that deployment validation was skipped and let them decide whether to proceed.
If any phase fails, present findings and let the operator decide: fix, re-validate, or proceed anyway.
Programmatic enforcement — run the gate check script before merge:
python3 skills/validate-feature/scripts/gate_logic.py \
openspec/changes/<change-id>/validation-report.md
This checks all required phases (smoke tests, security scan, E2E tests) in validation-report.md:
If the gate halts:
/validate-feature <change-id> --phase deploy,smoke,security,e2e--force# Explicit user override (must be requested by user, never autonomous)
python3 skills/validate-feature/scripts/gate_logic.py \
openspec/changes/<change-id>/validation-report.md --force
This is a hard gate — merge is blocked until all required phases pass or the user explicitly overrides.
If openspec/changes/<change-id>/rework-report.json exists, check whether holdout scenario failures block cleanup:
REWORK_REPORT="openspec/changes/$CHANGE_ID/rework-report.json"
if [[ -f "$REWORK_REPORT" ]]; then
python3 -c "
import json, sys
data = json.load(open('$REWORK_REPORT'))
summary = data.get('summary', {})
if summary.get('has_blocking_holdout'):
holdout_ids = [f['scenario_id'] for f in data.get('failures', [])
if f.get('visibility') == 'holdout' and f.get('recommended_action') == 'block-cleanup']
print(f'HALT: Holdout scenario failures block cleanup: {holdout_ids}')
sys.exit(1)
print('Holdout gate: clear')
"
fi
If the holdout gate halts:
/iterate-on-implementation to address holdout failures/validate-feature to regenerate the rework reportThe process-analysis.md artifact, if present, is consumed read-only at this stage for inclusion in the PR description and session log. It is NOT regenerated during cleanup.
These steps run only when coordinator is available with CAN_MERGE_QUEUE and CAN_FEATURE_REGISTRY capabilities.
2.6a. Enqueue: enqueue_merge(feature_id="<change-id>", pr_url="<pr-url>")
2.6b. Pre-merge checks: run_pre_merge_checks(feature_id="<change-id>") -- verifies no new resource conflicts
2.6c. Check merge order: get_next_merge() -- inform user if another feature has higher priority
2.6d. Cross-feature rebase: If other features merged since branching, rebase on origin/main
The merge command integrates the pre-merge gate — it will refuse to merge unless the gate passes:
# Via merge_pr.py with gate enforcement (preferred)
python3 skills/merge-pull-requests/scripts/merge_pr.py merge <pr_number> \
--origin openspec \
--validation-report openspec/changes/<change-id>/validation-report.md
# Direct gh merge (only if gate already passed in step 2.5a)
# Uses the resolved FEATURE_BRANCH, which honors OPENSPEC_BRANCH_OVERRIDE
gh pr merge "$FEATURE_BRANCH" --rebase --delete-branch
Explicit user override (only when user explicitly requests):
python3 skills/merge-pull-requests/scripts/merge_pr.py merge <pr_number> \
--origin openspec \
--validation-report openspec/changes/<change-id>/validation-report.md \
--force
Strategy rationale: OpenSpec PRs use rebase-merge by default because agent-authored commits follow conventional format and encode design intent (interface → implementation → tests). Preserving this history improves git blame and git bisect for future agents. Use squash only if the PR has noisy WIP commits.
If CAN_MERGE_QUEUE=true: mark_merged(feature_id="<change-id>") -- marks feature completed, frees resource claims, removes from merge queue.
# From the cleanup worktree, fetch the merged main
git fetch origin main
After merge, refresh project-global architecture artifacts:
make architecture
If the merged PR bumped any submodule gitlink SHAs (e.g., personas/ mount points), their own main branches must be fast-forwarded to avoid silent divergence.
# Detect changed submodules and sync their main branches
python3 "<skill-base-dir>/scripts/sync_submodules.py" \
--repo-dir "." \
--feature-branch "$FEATURE_BRANCH"
This script:
git diff --raw main@{1} main (mode 160000 = gitlink)origin main inside the submodule--ff-only) to the SHA the parent recordsCredential handling: The same credentials may not work for the submodule's private remote. On auth/push failure, the script:
If no submodules changed, this step is a no-op.
Before archiving, check for incomplete tasks in the proposal. Open tasks must not be silently dropped.
Read openspec/changes/<change-id>/tasks.md and scan for unchecked items (- [ ]).
If all tasks are checked (- [x]), skip to Step 6.
If there are open tasks, collect them with their context:
3.2 Add retry logic for failed requests)### 3. Error Handling)**Dependencies**: line**Files**: lineAsk the user which migration strategy to use:
Option A — Coordinator issues (if coordinator is available):
For each open task group that has unchecked items, use the coordinator's issue tracking MCP tools:
issue_create(
title="<task description>",
description="Followup from OpenSpec <change-id>. File scope: <files>",
issue_type="task",
priority=5,
labels=["followup", "openspec:<change-id>"]
)
# If tasks have dependencies on each other, create with depends_on
issue_create(
title="<dependent task>",
depends_on=["<parent-issue-id>"],
labels=["followup", "openspec:<change-id>"]
)
Include in each issue description:
proposal.md or design.mdOption B — Follow-up OpenSpec proposal (default if coordinator is not available):
Create a new proposal using runtime-native new/continue workflow (or CLI fallback) with:
followup-<original-change-id> (e.g., followup-add-retry-logic)Let the user review and confirm the follow-up proposal before proceeding.
After migration, annotate the original tasks.md to record where open tasks went:
## Migration Notes
Open tasks migrated to [coordinator issues labeled `openspec:<change-id>`] | [follow-up proposal `followup-<change-id>`] on YYYY-MM-DD.
This annotation is preserved in the archive for traceability.
Append a Cleanup phase entry to the session log, capturing merge strategy and task migration decisions. If no session-log.md exists from prior phases, create it and summarize the change from context.
Phase entry template:
---
## Phase: Cleanup (<YYYY-MM-DD>)
**Agent**: <agent-type> | **Session**: <session-id-or-N/A>
### Decisions
1. **<Decision title>** — <rationale>
### Alternatives Considered
- <Alternative>: rejected because <reason>
### Trade-offs
- Accepted <X> over <Y> because <reason>
### Open Questions
- [ ] <unresolved question>
### Context
<2-3 sentences: merge strategy, task migration decisions, archive outcome>
Focus on: Merge strategy (squash vs regular), open task migration decisions, any cleanup issues encountered.
Sanitize-then-verify:
python3 "<skill-base-dir>/../session-log/scripts/sanitize_session_log.py" \
"openspec/changes/<change-id>/session-log.md" \
"openspec/changes/<change-id>/session-log.md"
Read the sanitized output and verify: (1) all sections present, (2) no incorrect [REDACTED:*] markers, (3) markdown intact. If over-redacted, rewrite without secrets, re-sanitize (one attempt max). If sanitization exits non-zero, skip session log and proceed.
git add "openspec/changes/<change-id>/session-log.md"
If session log append or sanitization fails at any point, log a warning and proceed to archiving without the session log. This step is non-blocking.
Before triggering staged rollout, every item below MUST pass. This is a gate, not a suggestion — if any item is red, halt and remediate before promoting traffic.
openspec validate --strict exits zero against the archived changedocs/ and the relevant runbookIf any item fails, do NOT proceed to staged rollout. Open a follow-up issue, mark this change rollout-blocked, and return to the relevant phase (validate, iterate, or doc-update).
Promote the change through fixed traffic stages with a monitoring window between each stage. The rollout sequence is:
| Stage | Traffic | Min monitoring window | Promotion criteria | |-------|---------|----------------------|--------------------| | 1 | 5% | 30 min (or 1 full traffic cycle) | All thresholds below green | | 2 | 25% | 1 hour | All thresholds green AND no Stage 1 anomalies replayed | | 3 | 50% | 2 hours | All thresholds green AND error budget intact | | 4 | 100% | continuous | Steady-state, alerting active |
Reference: This skill assumes the change is shipped behind a feature flag (per the OpenSpec workflow). The flag, not a deploy, gates the traffic split. If the change is NOT flag-gated (e.g., infra-only), use blue/green or weighted DNS instead — the stages still apply.
On rollback: flip the flag to 0%, capture the rollout-state snapshot (metrics, logs, traces from the failure window), file an issue tagged rollback-postmortem, and DO NOT re-promote until root cause is identified.
Preferred path:
opsx:archive equivalent for the active agent).CLI fallback path:
openspec archive <change-id> --yes
openspec validate --strict
This archives the change, merges delta specs, and validates repository integrity.
# Confirm specs updated
openspec list --specs
# Confirm change archived
ls openspec/changes/archive/<change-id>/
# Validate everything
openspec validate --strict
# Delete local feature branch (if not already deleted)
# Uses the resolved FEATURE_BRANCH to honor OPENSPEC_BRANCH_OVERRIDE
git branch -d "$FEATURE_BRANCH" 2>/dev/null || true
# Prune remote tracking branches
git fetch --prune
If CAN_LOCK=true, perform best-effort lock cleanup for files touched on the feature branch:
main...$FEATURE_BRANCH changed filesRemove all worktrees for this feature (including the cleanup worktree).
Submodule handling: worktree.py teardown automatically handles worktrees containing initialized submodules:
git submodule deinit -f --all inside the worktree firstgit worktree removegit worktree remove --force (safe because the worktree's branch is already merged/pushed)# Return to shared checkout first (cleanup worktree is about to be removed)
cd "$(git rev-parse --git-common-dir | sed 's|/.git$||')"
# Remove cleanup worktree
python3 "<skill-base-dir>/../worktree/scripts/worktree.py" teardown "${CHANGE_ID}" --agent-id cleanup
# Remove implementation worktree (if exists from linear-implement-feature)
AGENT_FLAG=""
if [[ -n "${AGENT_ID:-}" ]]; then
AGENT_FLAG="--agent-id ${AGENT_ID}"
fi
python3 "<skill-base-dir>/../worktree/scripts/worktree.py" teardown "${CHANGE_ID}" ${AGENT_FLAG}
# Garbage-collect stale worktrees
python3 "<skill-base-dir>/../worktree/scripts/worktree.py" gc
# Confirm clean state
git status
# Run tests on main
pytest
If CAN_FEATURE_REGISTRY=true, re-analyze conflicts for active features. Features that were PARTIAL or SEQUENTIAL may upgrade to FULL now that this features claims are freed.
CLAUDE.md if applicableCAN_HANDOFF=true, write a final handoff summary with merge status, migration notes, archive outcome, and follow-up referencesopenspec/specs//plan-feature <description> # Create proposal → approval gate
/implement-feature <change-id> # Build + PR → review gate
/validate-feature <change-id> # Deploy + test → validation gate (optional)
/cleanup-feature <change-id> # Merge + archive → done
/cleanup-feature <change-id> --post-merge --pr <number> # Archive after /merge-pull-requests already merged
| Rationalization | Why it's wrong |
|---|---|
| "CI is green, just send 100% — staged rollout is for big changes" | Staged rollout catches regressions CI cannot see (production traffic shape, real data, real concurrency). Skipping it pushes the failure mode onto users instead of synthetics. |
| "We don't have feature flags here, so we'll just merge and watch" | "Watching" without a flip-back path means the only rollback is a revert PR + redeploy, which is minutes-to-hours slow. A flag (or blue/green, or weighted DNS) is what makes rollback fast enough to matter. |
| "The pre-launch checklist is overkill for a small change" | Most rollback incidents come from "small" changes where the team waived a checklist item. The checklist's value is exactly that it doesn't bend for size. |
| "Latency went up 60%, but it's still within SLO — we'll keep going" | A 60% jump is a regression even when it's inside SLO. SLOs are the floor, not the rollback threshold. The rollback threshold is the threshold. |
| "We can skip archiving until tomorrow" | The archive step closes the loop on spec drift. Postponing it leaves openspec/changes/<id>/ and openspec/specs/ out of sync, which silently breaks the next agent's view of the world. |
openspec validate --strict was not re-run after the archive.openspec validate --strict was re-run AFTER openspec archive and exited zero. The output is captured in the cleanup transcript.rollback-postmortem exists and the flag is currently at 0%.tasks.md are accounted for: either all checked, or migrated to coordinator issues / follow-up proposal with a Migration Notes line in the original tasks.md.testing
Create and edit Obsidian Flavored Markdown with wikilinks, embeds, callouts, properties, and other Obsidian-specific syntax. Use when working with .md files in Obsidian, or when the user mentions wikilinks, callouts, frontmatter, tags, embeds, or Obsidian notes.
tools
Interact with Obsidian vaults using the Obsidian CLI to read, create, search, and manage notes, tasks, properties, and more. Also supports plugin and theme development with commands to reload plugins, run JavaScript, capture errors, take screenshots, and inspect the DOM. Use when the user asks to interact with their Obsidian vault, manage notes, search vault content, perform vault operations from the command line, or develop and debug Obsidian plugins and themes.
data-ai
Create and edit Obsidian Bases (.base files) with views, filters, formulas, and summaries. Use when working with .base files, creating database-like views of notes, or when the user mentions Bases, table views, card views, filters, or formulas in Obsidian.
tools
Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections. Use when working with .canvas files, creating visual canvases, mind maps, flowcharts, or when the user mentions Canvas files in Obsidian.