From capstone
Spec-gated merge: validates branch strategy, runs build/test/lint gates, walks acceptance criteria from the spec file, launches a reviewer on the diff, and only merges when all gates pass. Prevents regression with post-merge test runs. Enforces feature → dev → main flow. Use when the user says "/merge", "/merge <branch>", "/merge --to-main", or "/merge --dry-run".
npx claudepluginhub kelsi-andrewss/capstone-toolkit --plugin capstoneThis skill uses the workspace's default tool permissions.
User has requested: `/merge $ARGUMENTS`
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Share bugs, ideas, or general feedback.
User has requested: /merge $ARGUMENTS
Continuous execution. Steps 0 through 9 execute as one uninterrupted flow. Do not pause, narrate, or ask for confirmation between steps. The only legitimate stops are:
--dry-run mode (report what would happen, don't execute)Parse $ARGUMENTS into:
Auto-detect branch: If no branch arg, use current branch:
CURRENT=$(git branch --show-current)
If CURRENT is main or dev, stop: "You're on $CURRENT — switch to a feature branch or specify one: /merge feature/my-branch"
Check the source branch name:
feature/<name>, fix/<name>, <name> (any lowercase-hyphenated name that isn't main or dev)| Mode | Source | Target | Rule |
|---|---|---|---|
| Default | feature branch | dev | All feature work merges to dev first |
--to-main | dev | main | Only dev merges to main |
Enforcement rules:
main. If --to-main is set but source is not dev, stop:
BLOCKED: Only dev can merge to main. Merge your feature to dev first:
/merge <branch>
Then promote dev to main:
/merge --to-main
git fetch origin <target> && git checkout -b <target> origin/<target>. If that fails too, stop with actionable error.git status --porcelain
If non-empty on the target branch, stop: "Target branch <target> has uncommitted changes. Commit or stash them first."
git fetch origin <target>
LOCAL=$(git rev-parse <target>)
REMOTE=$(git rev-parse origin/<target> 2>/dev/null)
If $LOCAL != $REMOTE and remote exists, warn: "Local <target> is behind origin/<target>. Pulling latest."
git checkout <target> && git pull --rebase origin <target>
The spec is the source of truth for what this branch should deliver. Find it:
If --spec <path> was provided, read that file. If it doesn't exist, stop: "Spec file not found: <path>"
Extract the feature name from the branch:
feature/contacts → contactsfix/login-bug → login-bugadd-user-dashboard → add-user-dashboardSearch for matching spec in priority order:
specs/<feature-name>.mdspecs/<feature-name>-product.md.md file in specs/ whose ## Objective heading mentions the feature name (fuzzy match — Read each file's first 20 lines)If no spec discovered:
spec_found = false.
WARNING: No spec found in specs/ for this branch. Acceptance criteria verification will be skipped.
Consider creating one: /capstone:spec <product> <feature description>
--to-main): this is a promotion gate. No spec required — the spec gates were enforced on feature → dev merges.Store spec_path and parsed spec content for later steps.
All gates run against the SOURCE branch. Switch to it:
git checkout <source-branch>
Detect project type and run build:
| File | Type | Build command |
|---|---|---|
package.json | Node/TS | npm install && npm run build (or npx tsc --noEmit if no build script) |
pubspec.yaml | Flutter | flutter pub get && flutter build |
Cargo.toml | Rust | cargo build |
go.mod | Go | go build ./... |
pyproject.toml | Python | pip install -e . 2>/dev/null; python -m py_compile <changed .py files> |
build_result = "pass". Continue.build_result = "FAIL". Capture last 30 lines of stderr. STOP — do not merge broken code.build_result = "skipped". Continue with warning.Run the project's test suite:
| Type | Test command |
|---|---|
| Node/TS | npm test (only if test script exists and is not the default error stub) |
| Flutter | flutter test |
| Rust | cargo test |
| Go | go test ./... |
| Python | python -m pytest -x -q --tb=short (if pytest available) |
test_result = "pass (N tests)". Continue.test_result = "FAIL". STOP — failing tests are a hard gate.test_result = "skipped". Continue with warning.Detect and run linter if configured:
package.json with lint script → npm run lint
.eslintrc* or eslint.config.* → npx eslint --max-warnings 0 <changed files>
ruff.toml or pyproject.toml with [tool.ruff] → ruff check <changed files>
.golangci.yml → golangci-lint run
Pass: lint_result = "clean".
Fail: lint_result = "N issues". Non-blocking warning — log issues but don't stop.
No linter: lint_result = "skipped".
Generate the diff between source and target:
git diff <target>...<source-branch> --stat
git diff <target>...<source-branch>
Store:
files_changed: list of modified fileslines_added, lines_removed: from --statdiff_content: full diff (for reviewer)| Size | Threshold | Action |
|---|---|---|
| Small | < 200 lines changed | Proceed normally |
| Medium | 200-1000 lines | Note in report |
| Large | > 1000 lines | Warn: "Large diff (N lines). Consider splitting into smaller PRs." Non-blocking. |
Skip if spec_found = false or --to-main mode.
Read the spec file and extract the ## Acceptance Criteria section. Parse each criterion.
For each criterion, attempt verification:
| Criterion type | Detection signal | Verification |
|---|---|---|
| File exists | "file exists", "creates", file path | Check file exists on source branch |
| API endpoint | URL pattern, HTTP method | Check route exists in code via Grep |
| Code pattern | "uses", "implements", function/class name | Grep for the pattern in changed files |
| Test coverage | "tested", "test exists" | Check test files exist for the feature |
| UI/visual | "displays", "renders", "shows" | Skip — "manual verification needed" |
| Behavioral | Everything else | Skip — "manual verification needed" |
Also check the ## Requirements section. For each requirement, verify at least one changed file addresses it (fuzzy match — the requirement's key nouns appear in the diff).
Track:
verified: criteria checked and passingfailed: criteria checked and failingmanual: criteria requiring human verificationcoverage: verified / (verified + failed + manual) as percentageGate logic:
failed criterion → STOP: "Acceptance criteria not met. Fix these before merging."coverage < 50% and manual > 0 → WARNING: "Most acceptance criteria need manual verification."Skip if --skip-review flag is set.
Launch a foreground reviewer agent (Sonnet) with the diff:
You are reviewing a feature branch diff before merge.
Branch: <source-branch> → <target>
Spec: <spec_path or "none">
Files changed: <files_changed count>
Check for:
1. BLOCKING issues: broken imports, type errors, undefined references, security vulnerabilities (injection, XSS, exposed secrets)
2. REGRESSION risks: deleted tests, removed error handling, weakened validation, changed public API signatures without updating callers
3. SPEC DRIFT: if a spec is provided, check whether the changes implement what the spec describes — flag additions that aren't in the spec (scope creep) or spec requirements with no corresponding code
Classify each finding as BLOCKING (must fix before merge) or WARNING (should fix, non-blocking).
For BLOCKING: explain what breaks and how to fix it.
For REGRESSION risks: explain what behavior changes and who is affected.
For SPEC DRIFT: quote the relevant spec section.
DIFF:
<diff_content>
<if spec exists>
SPEC:
<spec file content>
</if>
Parse results:
review_result = "clean".Skip if --dry-run.
Skip if any gate STOPPED the process.
git checkout <target>
git merge --no-ff <source-branch> -m "merge: <source-branch> → <target>"
Conflict handling: If merge conflicts occur:
git merge --abortOn success:
MERGE_HASH=$(git rev-parse --short HEAD)
Skip if --dry-run or merge didn't execute.
Run the test suite on the TARGET branch after merge to catch regressions:
# Same test command from Step 3b, but now on the merged target
<test-command>
regression_result = "clean". The merge didn't break anything.regression_result = "REGRESSION DETECTED". Critical alert:
REGRESSION: Tests pass on <source-branch> but fail after merge to <target>.
Failing tests:
<test names and output>
This merge introduced a regression. Consider:
1. git revert <MERGE_HASH> — undo the merge
2. Fix the regression on <source-branch> and re-merge
Do NOT auto-revert. Surface the issue and let the user decide.Merge: <source-branch> → <target>
Branch strategy: <valid | BLOCKED: reason>
Spec: <spec_path | not found (warning) | n/a (--to-main)>
Build: <pass | FAIL | skipped>
Tests: <pass (N tests) | FAIL | skipped>
Lint: <clean | N issues (warning) | skipped>
Diff: <N files, +M/-K lines> (<small|medium|large>)
Acceptance: <N/M verified, J manual | skipped (no spec)>
Review: <clean | N warnings | BLOCKING: description | skipped>
Merge: <commit HASH | dry-run | blocked>
Regression: <clean | DETECTED | skipped>
If --dry-run:
Dry run complete. All gates <passed | would block>:
<list any blocking issues>
Run without --dry-run to execute the merge.
If blocked at any gate:
Merge BLOCKED at: <gate name>
<reason and how to fix>
Fix the issue and run /merge again.
git branch -a suggestiongit merge-base --is-ancestor. If source is ancestor of target: "Already merged."<source> and <target>. Nothing to merge."--to-main) with failing tests: hard stop, same as feature → dev. Main must always be clean.--force only overrides non-blocking warnings. BLOCKING issues (build fail, test fail, reviewer blocking findings, regression) cannot be forced.This skill enforces one principle: the spec is the contract, and the merge is the enforcement point.
Every gate has a clear fix action. No gate blocks without telling you how to unblock.