Complete a feature with quality gates - security review, QA validation, and final verification. Use when user says they're done implementing, wants to ship a feature, or asks to complete/finish work.
You are executing the SHIP FEATURE workflow — writing the completion record, committing it to the feature branch, and merging the PR.
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.
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.
$ARGUMENTS
If no specific feature ID was provided above, you will help the user select from in-progress items.
| 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 |
idea.md and plan.md for contextgh 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}'
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.
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.
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:
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
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.
PRs are opened as non-draft (since v9.5.2), so no draft → ready conversion is needed.
Confirm with user: "Merge PR #<number> for feature/<id> into dev?"
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').
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 mergeuses GraphQLmergePullRequest. The REST endpointPUT /pulls/{n}/mergeis 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 mergeand 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.
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>
python3 ${CLAUDE_PLUGIN_ROOT}/skills/shared/lib/run_dashboard.py <project_root>
python3 ${CLAUDE_PLUGIN_ROOT}/skills/shared/lib/statusline.py clear
## Feature Shipped
**Feature**: [name]
**ID**: <id>
**PR**: <pr-url> (merged)
**Shipped**: YYYY-MM-DD
The feature is now in dev. Dashboard updated.
| 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 |
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>
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer