Claude
Skills
Sign in
Back

feature-ship

Included with Lifetime
$97 forever

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.

Writing & Docsscripts

What this skill does


# 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](../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](../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:
   ```bash
   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:
   ```bash
   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:

```markdown
---
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):
   ```bash
   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:
   ```bash
   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:
```bash
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 #<number> for feature/<id> 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:
   ```bash
   # 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:
   ```bash
   # <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:
   ```bash
   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):
   ```bash
   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):
   ```bash
   python3 ${CLAUDE_PLUGIN_ROOT}/skills/shared/lib/run_dashboard.py <project_root>
   ```
2. Clear the statusline:
   ```bash
   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 

Related in Writing & Docs