From crucible
Guides finishing development branches by verifying tests pass, running code reviews with git diffs, auditing test alignment, and presenting merge/PR/cleanup options.
npx claudepluginhub raddue/crucibleThis skill uses the workspace's default tool permissions.
<!-- CANONICAL: shared/dispatch-convention.md -->
Guides finishing implementation work after tests pass: verifies full suite, presents git options (local merge, PR creation, defer, discard), executes chosen workflow.
Verifies tests pass on completed feature branch, presents options to merge locally, create GitHub PR, keep as-is or discard; executes choice and cleans up worktree.
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.
Share bugs, ideas, or general feedback.
All subagent dispatches use disk-mediated dispatch. See shared/dispatch-convention.md for the full protocol.
Guide completion of development work by presenting clear options and handling chosen workflow.
Core principle: Verify tests -> Code review -> Red-team -> Present options -> Execute choice -> Clean up.
Announce at start: "I'm using the finish skill to complete this work."
Before presenting options, verify tests pass:
# Run project's test suite
npm test / cargo test / pytest / go test ./...
If tests fail:
Tests failing (<N> failures). Must fix before completing:
[Show failures]
Cannot proceed with merge/PR until tests pass.
Stop. Don't proceed to Step 2.
If tests pass: Continue to Step 2.
Before presenting options, run a full code review.
REQUIRED SUB-SKILL: Use crucible:code-review
BASE_SHA=$(git merge-base HEAD main 2>/dev/null || git merge-base HEAD master)
HEAD_SHA=$(git rev-parse HEAD)
git diff --stat $(git merge-base HEAD main 2>/dev/null || git merge-base HEAD master)...HEAD
Dispatch a code review subagent (general-purpose) using the code-review/code-reviewer.md template with:
--stat summary and key files list, let the reviewer pull targeted diffs rather than receiving the entire diff. Consider splitting into multiple focused reviewers -- one per subsystem.Act on feedback:
If fixes were made, re-run tests to confirm nothing broke.
Do NOT skip this step. The orchestrator did lightweight review during execution -- this is the comprehensive review before integration.
RECOMMENDED SUB-SKILL: Use crucible:test-coverage — audit whether existing tests are still aligned with the changes on this branch. Invoke with:
git diff <base-branch>..HEADThe test-coverage skill handles its own fix dispatch and revert-on-failure logic.
Skip this step when:
.md, .json, .yaml, config files)RECOMMENDED SUB-SKILL: Use crucible:forge (retrospective mode) — capture what happened vs what was planned while execution context is still fresh. Run this BEFORE red-team so the retrospective has access to the full execution state.
After code review passes, red-team the full implementation.
REQUIRED SUB-SKILL: Use crucible:red-team
crucible:red-team on the full implementation:
git diff --stat and key files)Do NOT skip this step. Code review checks quality; red-teaming checks whether the system will actually work and survive real use.
Check for docs/plans/*-noticed.md files matching the current pipeline (date + ticket-slug). If one exists and contains entries, prompt:
Found <N> noticed-but-not-touching entries in <noticed.md path>. Convert any to GitHub issues?
On confirmation, display a numbered list of entries and ask which to convert. For each selected entry, create an issue via gh issue create using the entry's noticed, why it matters, and suggested follow-up fields. Skip silently if no matching -noticed.md file exists.
# Try common base branches
git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null
Or ask: "This branch split from main - is that correct?"
Present exactly these 4 options:
Implementation complete. What would you like to do?
1. Merge back to <base-branch> locally
2. Push and create a Pull Request
3. Keep the branch as-is (I'll handle it later)
4. Discard this work
Which option?
Don't add explanation - keep options concise.
BLOCK semantics: you CANNOT proceed to Option 1 (merge) or Option 2 (push + PR) until local validation passes. A failing check is a hard stop. Do not push "then fix in CI"; do not merge "then fix on main."
Detect the project's toolchain first by reading manifest files at repo root: package.json, Cargo.toml, pyproject.toml / requirements.txt, go.mod, *.csproj / *.sln, Gemfile, build.gradle / pom.xml. Run only the checks that actually apply.
git diff --name-only <base>...HEAD). If ambiguous, ask the user before running the full matrix.Silently-missing tools are NOT a pass — they are an "unknown." Either run the real tool, or narrate the skip ("no type-check configured — skipping"), or ask the user. Never mask a failure with || true or 2>/dev/null.
Validation matrix (run every applicable check; each must exit 0 unless its documented exit-code contract says otherwise):
| Ecosystem | Type-check | Lint | Format | Tests |
|---|---|---|---|---|
| TypeScript/Node | npx tsc --noEmit (if tsconfig.json) | npm run lint / pnpm lint / biome check (whichever the repo configures) | prettier --check . / biome format --check (whichever configured) | npm test / pnpm test / vitest run / jest |
| Rust | cargo check --all-targets --all-features | cargo clippy --all-targets --all-features -- -D warnings | cargo fmt -- --check | cargo test --workspace --all-features |
| Python | mypy or pyright (if configured) | ruff check / flake8 (whichever configured) | ruff format --check / black --check (whichever configured) | pytest |
| Go | (compiler via go build ./...) | go vet ./... (add golangci-lint run if configured) | out=$(gofmt -l .) && [ -z "$out" ] (preserves gofmt exit code AND fails non-zero on drift) | go test ./... |
| .NET | dotnet build -p:TreatWarningsAsErrors=true (portable across Linux/macOS/Windows; covers type + warnings-as-errors) | Roslyn analyzers via the same -p:TreatWarningsAsErrors=true + any configured analyzer package | dotnet format --verify-no-changes | dotnet test |
| Ruby | (runtime only) | bundle exec rubocop (covers Layout + Style + Lint) | covered by Lint (or bundle exec standardrb if configured instead of rubocop) | bundle exec rspec / bundle exec rake test |
| Java/Kotlin | (compiler via ./gradlew build) | ./gradlew checkstyleMain spotbugsMain or ./mvnw spotbugs:check (if configured) | ./gradlew spotlessCheck (if configured) | ./gradlew test or ./mvnw test |
For ecosystems not in this matrix, extend it: manifest → type-check → lint → format-check → test. Do not skip an ecosystem because it isn't listed.
Exit-code interpretation: treat each tool's documented exit-code contract authoritatively, not just 0 vs non-0. Example: gh pr checks exits 8 for "checks pending" — a legitimate non-terminal state, not a failure. The rule is "never mask an exit code without interpreting it," not "non-zero is always failure." If a tool's contract is unclear, treat non-zero as failure and ask the user.
On ANY unhandled non-zero exit (after exit-code interpretation): STOP. Report the failure, dispatch a fix, and re-run the full matrix from scratch. Do not partially re-run — a fix in one layer can regress another.
# Switch to base branch
git checkout <base-branch>
# Pull latest
git pull
# Merge feature branch
git merge <feature-branch>
# Verify tests on merged result
<test command>
# If tests pass
git branch -d <feature-branch>
Then: If using a worktree, clean it up (Step 7)
Repository Safety Check (before push):
# Check if repo is public
IS_PRIVATE=$(gh repo view --json isPrivate -q .isPrivate)
If the repo is public: scan the PR title, body, and commit messages for proprietary company information, internal names, internal URLs, or sensitive data. STOP and confirm with the user if anything looks sensitive. This check is mandatory — a prior incident involved filing proprietary information to a public repo.
# Push branch
git push -u origin <feature-branch>
# Create PR and capture the URL gh emits (the PR it just created).
# This is the only deterministic PR reference — using `gh pr view`
# instead would use branch-to-PR mapping, which breaks on repos
# with multiple open PRs per branch.
PR_URL=$(gh pr create --title "<title>" --body "$(cat <<'EOF'
## Summary
<2-3 bullets of what changed>
## Test Plan
- [ ] <verification steps>
EOF
)")
PR_NUMBER="${PR_URL##*/}"
# Guard: if gh pr create failed (e.g., a PR already exists for this branch),
# PR_URL is empty — surface the real diagnostic instead of falling through
# into CI monitoring and emitting a misleading "CI failed" message.
if [ -z "$PR_URL" ] || [ -z "$PR_NUMBER" ]; then
echo "gh pr create did not return a PR URL — possible duplicate PR. Inspect: gh pr list --head <feature-branch>"
exit 1
fi
Post-Push CI Monitoring (Non-Negotiable): after gh pr create returns, you CANNOT report success to the user until CI has finished AND passed. "Pushed" is not "done." BLOCK on the watch + empty-check assertion below. Fail closed on any non-zero result that isn't the "no CI configured" case:
# Primary: --watch streams status and returns aggregate exit code.
# Exit 0 = all terminal passes OR no checks configured (must disambiguate).
# Non-zero = at least one check failed/cancelled.
gh pr checks "$PR_NUMBER" --watch
WATCH_RC=$?
# Disambiguate the no-CI case from real success.
CHECK_COUNT=$(gh pr checks "$PR_NUMBER" --json bucket --jq 'length')
if [ "$CHECK_COUNT" = "0" ]; then
echo "No CI checks configured — record in final report and recommend adding CI"
elif [ "$WATCH_RC" -ne 0 ]; then
echo "CI failed (watch exit $WATCH_RC)"; exit 1
else
echo "CI green"
fi
Fallback (if --watch cannot run): poll until all checks reach a terminal state, then assert the bucket set is a subset of {pass, skipping} (allow-list, not deny-list — an unknown future bucket value must fail closed). Use gh's normalized bucket field, not raw state values, to avoid missing edge states like NEUTRAL, ACTION_REQUIRED, or lowercase legacy commit-status values.
gh exit-code contract: per gh's own documentation, gh pr checks exits 0 when all checks terminal-passed, 1 when at least one check terminal-failed, 8 when at least one is still pending, and other values for tool errors (auth, network, rate-limit). Both 0 and 1 are terminal — the fallback's assertion block disambiguates pass from fail via the bucket set. 8 means continue polling. Anything else means gh itself couldn't determine state.
while true; do
BUCKETS=$(gh pr checks "$PR_NUMBER" --json bucket --jq '[.[].bucket] | unique')
RC=$?
echo "CI buckets: $BUCKETS"
case "$RC" in
0|1) break ;; # 0 = all pass, 1 = at least one failed; both terminal — assertion classifies
8) sleep 20 ;; # at least one pending — keep polling
*) echo "gh pr checks errored (rc=$RC) — cannot determine CI state"; exit 1 ;;
esac
done
# Three terminal cases: empty (no CI), all allow-list (green), or anything else (red).
if [ "$BUCKETS" = "[]" ]; then
echo "No CI checks configured — record in final report and recommend adding CI"
elif echo "$BUCKETS" | jq -e 'all(. == "pass" or . == "skipping")' >/dev/null; then
echo "CI green"
else
echo "CI not all-green: $BUCKETS"; exit 1
fi
If the exit is non-zero (either via --watch or the explicit assertion): diagnose from CI logs (gh run view <run-id> --log-failed or gh pr checks "$PR_NUMBER"), dispatch a fix, re-run Step 5.5's full validation matrix (the fix can regress local checks), push, and re-watch. Do NOT report success on a red PR. Do NOT leave the watch running while moving on to another task — CI failure is an actionable blocker that takes precedence.
If the block exits with the "No CI checks configured" message, record that in the final report so the user knows local validation was the only gate, and recommend they add CI.
Then: If using a worktree, clean it up (Step 7)
Report: "Keeping branch ."
If using a worktree: "Worktree preserved at ."
Confirm first:
This will permanently delete:
- Branch <name>
- All commits: <commit-list>
Type 'discard' to confirm.
Wait for exact confirmation.
If confirmed:
git checkout <base-branch>
git branch -D <feature-branch>
Then: If using a worktree, clean it up (Step 7)
Skip this step if not using git worktrees.
For Options 1, 2, and 4:
Check if in worktree:
git worktree list | grep $(git branch --show-current)
If yes:
git worktree remove <worktree-path>
For Option 3: Keep worktree.
| Option | Merge | Push | Cleanup Branch | Cleanup Worktree (if applicable) |
|---|---|---|---|---|
| 1. Merge locally | Yes | - | Yes | Yes |
| 2. Create PR | - | Yes | - | Yes |
| 3. Keep as-is | - | - | - | - |
| 4. Discard | - | - | Yes (force) | Yes |
Skipping test verification
Skipping code review
Open-ended questions
Automatic worktree cleanup
No confirmation for discard
Skipping pre-push validation
Silencing validation failures
2>/dev/null || true patterns hide tool failures and make "no output" indistinguishable from "tool missing" — a failing tsc looks identical to a repo without TypeScript.Pushing and moving on
git push returns, gh pr create returns a URL, task feels done. CI runs later, fails, and nobody notices until the next review session.gh pr checks <pr-number> --watch (or equivalent poll loop). Treat a red PR as a hard stop — never report success to the user on a failing PR.Never:
|| true, 2>/dev/null). Known non-failure non-zero codes must be matched to their meaning — gh pr checks exit 8 is "checks pending," not failure. When a tool's contract is unclear, treat non-zero as failure.gh pr create without confirming all CI checks passAlways:
gh pr checks --watch) before declaring Option 2 doneCalled by:
Pairs with:
crucible:red-team directly rather than crucible:quality-gate because it doesn't produce a typed artifact — it's a pre-completion sanity check, not an iterative gate.Recommended:
Before completing this skill, confirm every mandatory checkpoint was executed:
If any checkbox is unchecked, STOP. Go back and execute the missed gate.