From pds
Prepares git branches for merge: verifies completeness with /pds:verify, rebases onto target, cleans commits to conventional format, re-runs tests, audits .claude permissions, archives swarm reports.
npx claudepluginhub rmzi/portable-dev-system --plugin pdsThis skill uses the workspace's default tool permissions.
The gap between "code works" and "branch is ready" is where quality lives. This protocol ensures branches are clean, tested, and reviewable.
Finishes Git development branches end-to-end: verifies working tree/tests/lint, reviews diffs, picks merge/PR/squash/stacked strategy, writes messages, pushes, cleans up. Use when ready to ship.
Guides finishing development branches by verifying tests pass, running code reviews with git diffs, auditing test alignment, and presenting merge/PR/cleanup options.
Finalizes development branches: verifies tests pass, presents merge/PR/keep/discard options, executes git workflows including GitHub PR creation, and cleans up worktrees.
Share bugs, ideas, or general feedback.
The gap between "code works" and "branch is ready" is where quality lives. This protocol ensures branches are clean, tested, and reviewable.
/finish patch [target-branch] # Verify, clean, bump patch, ship
/finish minor [target-branch] # Verify, clean, bump minor, ship
/finish major [target-branch] # Verify, clean, bump major, ship
Default target: main.
Run /pds:verify first. Do not proceed until it passes.
Ensure your branch is current with the target:
git fetch origin
git rebase origin/main # or target branch
Resolve any conflicts. Each conflict resolution should maintain both sides' intent — don't blindly accept one side.
Review your commits:
git log --oneline main..HEAD
If there are fixup commits or WIP entries, squash them non-interactively:
# Squash all branch commits into one clean commit
git reset --soft origin/main
git commit -m "feat(scope): descriptive message"
# Or use autosquash for commits prefixed with fixup!/squash!
git rebase --autosquash origin/main
Each commit should be atomic and meaningful. Use conventional commit format: <type>(<scope>): <subject>. Types: feat, fix, refactor, test, docs, chore, perf. Subject in imperative mood, max 72 chars. Body explains what and why, not how.
Do NOT use git rebase -i — interactive rebase requires terminal input and will hang in agent contexts.
Tests must pass after rebase, not just before:
# Run full test suite again
npm test # or equivalent
Rebasing can introduce subtle breakage — verify.
Review .claude/settings.local.json for permission patterns that should be promoted to .claude/settings.json:
Bash(git *:*), Bash(gh *:*), tool names) that aren't already in project settingsrm commands, temp artifacts).claude/settings.json permissions.allow and remove from settings.local.jsonThis ensures permission improvements ship with the code rather than accumulating silently in local settings.
If .claude/swarm/ exists in the current worktree, preserve ephemeral state as its own commit before shipping:
*.md files from .claude/swarm/ to docs/swarm-reports/<YYYY-MM-DD-HHmm>/:
REPORT_DIR="docs/swarm-reports/$(date +%Y-%m-%d-%H%M)"
mkdir -p "$REPORT_DIR"
cp .claude/swarm/*.md "$REPORT_DIR/"
git add "$REPORT_DIR"
git commit -m "chore: archive swarm artifacts to docs/swarm-reports"
Auto-memory writes happen outside git (under ~/.claude/projects/) and survive worktree removal automatically.
Extraction lives here — after the branch is clean and audited, before ship — so the archive commit is atomic and reviewable in the PR, never mixed into a verify/rebase/clean step. If .claude/swarm/ does not exist, skip to Step 7.
Bump, commit, push, and create/update PR.
Protected branch check. Before pushing, check if the target branch is protected:
Protected Branches section listing branch patterns (e.g., main, release/*)Protected Branches section exists in CLAUDE.md, no branches are protected — push freelyIf uncommitted changes exist (staged or unstaged):
git add relevant files (not -A — be deliberate)<type>(<scope>): <subject>If working tree is clean, skip to 7b.
If no bump type was passed, scan git log since the last version tag:
git log $(git describe --tags --abbrev=0 2>/dev/null)..HEAD --oneline 2>/dev/null || git log --oneline
Apply the highest-precedence rule found:
| Commit prefix | Bump type |
|---|---|
BREAKING CHANGE in body, or ! after type (e.g. feat!:) | major |
feat: | minor |
fix:, perf:, refactor:, etc. | patch |
Default to patch if no conventional commits found.
Follow /pds:bump protocol:
chore: bump version to X.Y.Zgit push origin HEAD
gh pr create --fill # Create if none exists
gh pr view # Show existing PR
Work commit is separate from bump commit — clean git history.
Experimental — off by default. Steps 7e and 7f only run when
PDS_DIARY=1(orPDS_DIARY=on) is set in the environment. Without the flag, skip straight to Cleanup — the diary pipeline will not fire and legacy branches will not be prompted for renames. Enable per-invocation withPDS_DIARY=1 /pds:finish ..., or export in your shell rc to keep it on.Also fires automatically at session end. When
PDS_DIARY=1is set, aSessionEndhook (hooks/scripts/diary-session-end.sh) posts the diary for you using the canonicaltranscript_pathhanded down by Claude Code. Manual invocation from/pds:finishremains supported for ship-time diaries — both paths end up editing the same canonical comment keyed off the<!-- pds:diary -->marker, so re-fires are idempotent.
case "${PDS_DIARY:-}" in
1|on|true|yes) ;;
*) echo "dev-diary disabled (set PDS_DIARY=1 to enable)"; exit 0 ;;
esac
Parse the issue number from the branch name. The canonical pattern is <type>/<issue>-<slug> (see /pds:worktree issue-tied creation):
BRANCH="$(git branch --show-current)"
ISSUE="$(echo "$BRANCH" | sed -nE 's|^[a-z]+/([0-9]+)-.*|\1|p')"
Legacy branches (no issue encoded). If $ISSUE is empty:
skip?"NEW_BRANCH="<type>/<N>-$(echo "$BRANCH" | sed -E 's|^[a-z]+/||' | tr '/ ' '--')"
git branch -m "$NEW_BRANCH"
git push origin -u "$NEW_BRANCH" :"$BRANCH" 2>/dev/null || git push origin -u "$NEW_BRANCH"
BRANCH="$NEW_BRANCH"; ISSUE="<N>"
skip, proceed to 7f with an empty $ISSUE; the diary step will be skipped with a note.If $ISSUE is set, invoke the diary assembler:
BRANCH="$BRANCH" ISSUE="$ISSUE" MODE=post bash "$CLAUDE_PLUGIN_ROOT/scripts/assemble-diary.sh"
The script:
export-session.sh output inside a collapsed <details> block.<!-- pds:diary --> marker. If found, edits in place; otherwise posts a new comment (single canonical comment per issue).gh failure, writes the assembled document to $TMPDIR/pds-diary-<issue>-<ts>.md and surfaces the path. Never silently swallows.If $ISSUE is empty (user chose skip), skip this step and report it plainly.
After the branch is merged:
git worktree remove .worktrees/branch-namegit branch -d branch-name| Situation | Skill |
|---|---|
| Formal ship: verify, rebase, clean, bump, push | /finish |
| Quick ship: bump, commit, push | /pds:checkpoint |
| Version bump only (no push) | /pds:bump |
| Verify before shipping | /pds:verify then /finish |
After shipping, consider running /pds:pause — shipping is a natural break point. It saves session state so you can resume cleanly in the next session.
/pds:bump — version bump details/pds:verify — completion self-check (step 1)/pds:pause — save session state before stepping away