feature-ship

star 13

Complete a feature by writing shipped.md, committing to the feature branch, and merging the PR. Use when external reviews are done and the feature is ready to ship.

schuettc By schuettc schedule Updated 6/8/2026

name: feature-ship description: Complete a feature by writing shipped.md, committing to the feature branch, and merging the PR. Use when external reviews are done and the feature is ready to ship. user-invocable: true

Ship Feature

You are executing the SHIP FEATURE workflow — writing the completion record, committing it to the feature branch, and merging the PR.

Branch Configuration

Before doing anything else, read .feature-workflow.yml in the project root for branch settings. See ../shared/config.md for details.

Multi-repo workspace? If the root has a .feature-workspace.yml, ship from inside the member repo — cd <member> first, so the branch/PR/merge target that member's own remote and config. See ../shared/workspace.md.

Setting Default Used for
branch.prefix feature/ Branch naming: <prefix><id>
branch.target dev Merge target, checkout after merge
merge_method merge How Phase 4 merges the PR: squash / merge / rebase. Ignored when the base branch requires a merge queue — the queue's own configured method wins, so keep them aligned.

Throughout this skill, replace feature/<id> with <prefix><id> and dev with <target> based on the config.

First Step (Do This Now)

Read the file at path: docs/features/DASHBOARD.md

This file shows in-progress features. Look at the "In Progress" section to find features ready to ship.

Feature Target

$ARGUMENTS

If no specific feature ID was provided above, you will help the user select from in-progress items.


Workflow Overview

Phase Name Purpose
1 Pre-flight Verify feature is in-progress and has a PR
2 Write shipped.md Create completion record on the feature branch
3 Prepare PR Remove review labels + commit and push shipped.md
4 Merge PR Mark PR ready, merge into dev, clean up branches
5 Update Dashboard Regenerate dashboard and clear statusline

Phase 1: Pre-flight

  1. Read the feature's idea.md and plan.md for context
  2. Verify feature is in-progress (has plan.md, no shipped.md)
  3. Check for the PR:
    gh api "repos/{owner}/{repo}/pulls?state=open&per_page=100" \
      --jq '[.[] | select(.head.ref == "feature/<id>")] | .[0] | {number, url: .html_url, state, draft, base_ref: .base.ref}'
    
  4. Verify you're on the feature/<id> branch — if not, switch to it:
    git checkout feature/<id>
    

If there's no PR, warn the user — they may want to run /feature-review-impl first, or proceed with a local merge.


Phase 2: Write shipped.md

Write docs/features/<id>/shipped.md with the following format:

---
shipped: YYYY-MM-DD
---

# Shipped: [Feature Name]

## Summary
Brief summary of what was delivered...

## Key Changes
- Change 1
- Change 2
- Change 3

## Files Changed
- `path/to/file1.ts`
- `path/to/file2.ts`

## Testing
How the feature was tested and verified...

## Notes
Any follow-up items, known limitations, or context for future maintainers...

Populate this from the plan.md, commit messages, and git diff.


Phase 3: Prepare PR (labels first, then commit + push)

Order matters here. The review workflow (feature-review.yml) triggers on pull_request: types: [labeled, synchronize]. If review labels (plan-review, impl-review) are still on the PR when the shipped.md push fires a synchronize event, the workflow runs a pointless extra review on a docs-only commit, spending API quota and posting irrelevant review comments on a PR that's about to merge.

Remove the labels before pushing shipped.md:

  1. Remove review labels (must be first — prevents the push-triggered re-review):

    gh pr edit <pr-number> --remove-label plan-review --remove-label impl-review 2>/dev/null || true
    
  2. Commit shipped.md to the feature branch and push:

    git add docs/features/<id>/shipped.md
    git commit -m "docs(<id>): mark feature as shipped"
    git push
    

After writing shipped.md, regenerate the dashboard by running:

python3 ${CLAUDE_PLUGIN_ROOT}/skills/shared/lib/run_dashboard.py <project_root>

DASHBOARD.md is auto-resolved on merge via CI — no need to commit it from feature branches.


Phase 4: Merge PR

PRs are opened as non-draft (since v9.5.2), so no draft → ready conversion is needed.

  1. Confirm with user: "Merge PR # for feature/ into dev?"

  2. If you encounter a draft PR (legacy / opened externally): convert with gh pr ready <pr-number> once. This is a GraphQL mutation, used at most once per stuck PR. Don't retry on rate-limit failure — wait for the GraphQL window to reset (gh api rate_limit --jq '.resources.graphql.reset').

  3. Detect whether the base branch requires a merge queue, then merge accordingly. A queue-protected branch rejects a direct merge — the only way in is the queue — so the merge path forks on this:

    # true when the base branch has a merge_queue rule (via ruleset).
    QUEUED=$(gh api "repos/{owner}/{repo}/rules/branches/<base>" \
      --jq 'any(.[]; .type == "merge_queue")' 2>/dev/null || echo false)
    

    No queue (QUEUED = false) — direct REST merge (the common path). Use the merge_method read from .feature-workflow.yml (default merge if unset). For example maxwell sets merge_method: squash so features land as one clean commit:

    # <merge_method> = the merge_method from .feature-workflow.yml (squash | merge | rebase; default merge)
    gh api "repos/{owner}/{repo}/pulls/<pr-number>/merge" \
      --method PUT \
      --field merge_method=<merge_method>
    
    # Delete the remote branch (REST):
    gh api "repos/{owner}/{repo}/git/refs/heads/feature/<id>" --method DELETE
    

    Why REST merge: gh pr merge uses GraphQL mergePullRequest. The REST endpoint PUT /pulls/{n}/merge is functionally equivalent, doesn't count against the GraphQL points budget, and isn't subject to the secondary mutation rate limit. The 405 "still a draft" failure mode no longer applies because we never open as draft.

    Queue required (QUEUED = true) — enqueue, then wait for the queue to merge. A direct REST merge returns 405 here; gh pr merge enters the queue natively (no merge-strategy flag — the queue's own configured method governs the merge, so the .feature-workflow.yml merge_method is ignored on this path; keep the two in sync). If required checks haven't passed yet it enables auto-merge instead, so the same command is correct either way:

    gh pr merge <pr-number>   # enqueues (GraphQL, once); queue's merge method applies
    
    # The queue re-runs the required checks against the queued branch and merges
    # asynchronously. Poll until the PR is MERGED before declaring shipped:
    until [ "$(gh pr view <pr-number> --json state -q .state)" = "MERGED" ]; do
      sleep 30
    done
    
    # The queue deletes the head branch on merge; tolerate an already-gone ref.
    gh api "repos/{owner}/{repo}/git/refs/heads/feature/<id>" --method DELETE 2>/dev/null || true
    

    If the queue is slow and you don't want to block, you may stop after gh pr merge and report the PR as enqueued — it merges when the queue drains. Phase 5 cleanup (dashboard, local branch delete) only applies once it has actually merged.

  4. Switch to the base branch, pull, and delete the local feature branch (after the merge has landed — for a queued PR that's after the poll above):

    git checkout dev && git pull && git branch -d feature/<id>
    

Phase 5: Update Dashboard and Cleanup

  1. Regenerate the dashboard on dev (shipped.md is now merged):
    python3 ${CLAUDE_PLUGIN_ROOT}/skills/shared/lib/run_dashboard.py <project_root>
    
  2. Clear the statusline:
    python3 ${CLAUDE_PLUGIN_ROOT}/skills/shared/lib/statusline.py clear
    
  3. Display completion summary:
## Feature Shipped

**Feature**: [name]
**ID**: <id>
**PR**: <pr-url> (merged)
**Shipped**: YYYY-MM-DD

The feature is now in dev. Dashboard updated.

Error Handling

Error Resolution
Feature not in-progress Direct user to correct command or status
No PR exists Suggest /feature-review-impl first, or offer local merge
Not on feature branch Switch to feature/<id>
Already completed Feature has shipped.md — nothing to do
Direct merge returns 405 / "merge queue required" The base branch requires a merge queue — use the queue path in Phase 4 step 3 (gh pr merge <pr>, then poll for MERGED) instead of the REST merge

Fallback: Manual Ship

If shipped.md wasn't created, you can use the ship script:

python3 ${CLAUDE_PLUGIN_ROOT}/skills/feature-ship/scripts/ship_feature.py <project_root> <feature-id> "Summary message"

After creating shipped.md, regenerate the dashboard:

python3 ${CLAUDE_PLUGIN_ROOT}/skills/shared/lib/run_dashboard.py <project_root>
Install via CLI
npx skills add https://github.com/schuettc/claude-code-plugins --skill feature-ship
Repository Details
star Stars 13
call_split Forks 3
navigation Branch main
article Path SKILL.md
More from Creator