Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, update CHANGELOG, commit, push, create PR.
_UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/skills/gstack/bin/gstack-update-check 2>/dev/null || true)
[ -n "$_UPD" ] && echo "$_UPD" || true
mkdir -p ~/.gstack/sessions
touch ~/.gstack/sessions/"$PPID"
_SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ')
find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true
_CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true)
_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
echo "BRANCH: $_BRANCH"If output shows UPGRADE_AVAILABLE <old> <new>: read ~/.claude/skills/gstack/gstack-upgrade/SKILL.md and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If JUST_UPGRADED <from> <to>: tell user "Running gstack v{to} (just updated!)" and continue.
ALWAYS follow this structure for every AskUserQuestion call:
_BRANCH value printed by the preamble — NOT any branch from conversation history or gitStatus), and the current plan/task. (1-2 sentences)RECOMMENDATION: Choose [X] because [one-line reason]A) ... B) ... C) ...Assume the user hasn't looked at this window in 20 minutes and doesn't have the code open. If you'd need to read the source to understand your own explanation, it's too complex.
Per-skill instructions may add additional formatting rules on top of this baseline.
If _CONTRIB is true: you are in contributor mode. You're a gstack user who also helps make it better.
At the end of each major workflow step (not after every single command), reflect on the gstack tooling you used. Rate your experience 0 to 10. If it wasn't a 10, think about why. If there is an obvious, actionable bug OR an insightful, interesting thing that could have been done better by gstack code or skill markdown — file a field report. Maybe our contributor will help make us better!
Calibration — this is the bar: For example, $B js "await fetch(...)" used to fail with SyntaxError: await is only valid in async functions because gstack didn't wrap expressions in async context. Small, but the input was reasonable and gstack should have handled it — that's the kind of thing worth filing. Things less consequential than this, ignore.
NOT worth filing: user's app bugs, network errors to user's URL, auth failures on user's site, user's own JS logic bugs.
To file: write ~/.gstack/contributor-logs/{slug}.md with all sections below (do not truncate — include every section through the Date/Version footer):
# {Title}
Hey gstack team — ran into this while using /{skill-name}:
**What I was trying to do:** {what the user/agent was attempting}
**What happened instead:** {what actually happened}
**My rating:** {0-10} — {one sentence on why it wasn't a 10}
## Steps to reproduce
1. {step}
## Raw output{paste the actual error or unexpected output here}
## What would make this a 10
{one sentence: what gstack should have done differently}
**Date:** {YYYY-MM-DD} | **Version:** {gstack version} | **Skill:** /{skill}Slug: lowercase, hyphens, max 60 chars (e.g. browse-js-no-await). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}"
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
Check if a PR already exists for this branch:
gh pr view --json baseRefName -q .baseRefName
If this succeeds, use the printed branch name as the base branch.
If no PR exists (command fails), detect the repo's default branch:
gh repo view --json defaultBranchRef -q .defaultBranchRef.name
If both commands fail, fall back to main.
Print the detected base branch name. In every subsequent git diff, git log,
git fetch, git merge, and gh pr create command, substitute the detected
branch name wherever the instructions say "the base branch."
You are running the /ship workflow. This is a non-interactive, fully automated workflow. Do NOT ask for confirmation at any step. The user said /ship which means DO IT. Run straight through and output the PR URL at the end.
Only stop for:
Never stop for:
Check the current branch. If on the base branch or the repo's default branch, abort: "You're on the base branch. Ship from a feature branch."
Run git status (never use -uall). Uncommitted changes are always included — no need to ask.
Run git diff <base>...HEAD --stat and git log <base>..HEAD --oneline to understand what's being shipped.
Fetch and merge the base branch into the feature branch so tests run against the merged state:
git fetch origin <base> && git merge origin/<base> --no-editIf there are merge conflicts: Try to auto-resolve if they are simple (VERSION, schema.rb, CHANGELOG ordering). If conflicts are complex or ambiguous, STOP and show them.
If already up to date: Continue silently.
Do NOT run RAILS_ENV=test bin/rails db:migrate — bin/test-lane already calls
db:test:prepare internally, which loads the schema into the correct lane database.
Running bare test migrations without INSTANCE hits an orphan DB and corrupts structure.sql.
Run both test suites in parallel:
bin/test-lane 2>&1 | tee /tmp/ship_tests.txt &
npm run test 2>&1 | tee /tmp/ship_vitest.txt &
waitAfter both complete, read the output files and check pass/fail.
If any test fails: Show the failures and STOP. Do not proceed.
If all pass: Continue silently — just note the counts briefly.
Evals are mandatory when prompt-related files change. Skip this step entirely if no prompt files are in the diff.
1. Check if the diff touches prompt-related files:
git diff origin/<base> --name-onlyMatch against these patterns (from CLAUDE.md):
app/services/*_prompt_builder.rbapp/services/*_generation_service.rb, *_writer_service.rb, *_designer_service.rbapp/services/*_evaluator.rb, *_scorer.rb, *_classifier_service.rb, *_analyzer.rbapp/services/concerns/*voice*.rb, *writing*.rb, *prompt*.rb, *token*.rbapp/services/chat_tools/*.rb, app/services/x_thread_tools/*.rbconfig/system_prompts/*.txttest/evals/**/* (eval infrastructure changes affect all suites)If no matches: Print "No prompt-related files changed — skipping evals." and continue to Step 3.5.
2. Identify affected eval suites:
Each eval runner (test/evals/*_eval_runner.rb) declares PROMPT_SOURCE_FILES listing which source files affect it. Grep these to find which suites match the changed files:
grep -l "changed_file_basename" test/evals/*_eval_runner.rbMap runner → test file: post_generation_eval_runner.rb → post_generation_eval_test.rb.
Special cases:
test/evals/judges/*.rb, test/evals/support/*.rb, or test/evals/fixtures/ affect ALL suites that use those judges/support files. Check imports in the eval test files to determine which.config/system_prompts/*.txt — grep eval runners for the prompt filename to find affected suites.3. Run affected suites at EVAL_JUDGE_TIER=full:
/ship is a pre-merge gate, so always use full tier (Sonnet structural + Opus persona judges).
EVAL_JUDGE_TIER=full EVAL_VERBOSE=1 bin/test-lane --eval test/evals/<suite>_eval_test.rb 2>&1 | tee /tmp/ship_evals.txtIf multiple suites need to run, run them sequentially (each needs a test lane). If the first suite fails, stop immediately — don't burn API cost on remaining suites.
4. Check results:
5. Save eval output — include eval results and cost dashboard in the PR body (Step 8).
Tier reference (for context — /ship always uses full):
| Tier | When | Speed (cached) | Cost |
|---|---|---|---|
fast (Haiku) | Dev iteration, smoke tests | ~5s (14x faster) | ~$0.07/run |
standard (Sonnet) | Default dev, bin/test-lane --eval | ~17s (4x faster) | ~$0.37/run |
full (Opus persona) | /ship and pre-merge | ~72s (baseline) | ~$1.27/run |
Review the diff for structural issues that tests don't catch.
Read .claude/skills/review/checklist.md. If the file cannot be read, STOP and report the error.
Run git diff origin/<base> to get the full diff (scoped to feature changes against the freshly-fetched base branch).
Apply the review checklist in two passes:
Always output ALL findings — both critical and informational. The user must see every issue found.
Output a summary header: Pre-Landing Review: N issues (X critical, Y informational)
If CRITICAL issues found: For EACH critical issue, use a separate AskUserQuestion with:
file:line + description)RECOMMENDATION: Choose A because [one-line reason]git add <fixed-files> && git commit -m "fix: apply pre-landing review fixes"), then STOP and tell the user to run /ship again to re-test with the fixes applied. If the user chose only B (acknowledge) or C (false positive) on all issues, continue with Step 4.If only non-critical issues found: Output them and continue. They will be included in the PR body at Step 8.
If no issues found: Output Pre-Landing Review: No issues found. and continue.
Save the review output — it goes into the PR body in Step 8.
Read .claude/skills/review/greptile-triage.md and follow the fetch, filter, classify, and escalation detection steps.
If no PR exists, gh fails, API returns an error, or there are zero Greptile comments: Skip this step silently. Continue to Step 4.
If Greptile comments are found:
Include a Greptile summary in your output: + N Greptile comments (X valid, Y fixed, Z FP)
Before replying to any comment, run the Escalation Detection algorithm from greptile-triage.md to determine whether to use Tier 1 (friendly) or Tier 2 (firm) reply templates.
For each classified comment:
VALID & ACTIONABLE: Use AskUserQuestion with:
RECOMMENDATION: Choose A because [one-line reason]git add <fixed-files> && git commit -m "fix: address Greptile review — <brief description>"), reply using the Fix reply template from greptile-triage.md (include inline diff + explanation), and save to both per-project and global greptile-history (type: fix).VALID BUT ALREADY FIXED: Reply using the Already Fixed reply template from greptile-triage.md — no AskUserQuestion needed:
FALSE POSITIVE: Use AskUserQuestion:
SUPPRESSED: Skip silently — these are known false positives from previous triage.
After all comments are resolved: If any fixes were applied, the tests from Step 3 are now stale. Re-run tests (Step 3) before continuing to Step 4. If no fixes were applied, continue to Step 4.
Read the current VERSION file (4-digit format: MAJOR.MINOR.PATCH.MICRO)
Auto-decide the bump level based on the diff:
git diff origin/<base>...HEAD --stat | tail -1)Compute the new version:
0.19.1.0 + PATCH → 0.19.2.0Write the new version to the VERSION file.
Read CHANGELOG.md header to know the format.
Auto-generate the entry from ALL commits on the branch (not just recent ones):
git log <base>..HEAD --oneline to see every commit being shippedgit diff <base>...HEAD to see the full diff against the base branch### Added — new features### Changed — changes to existing functionality### Fixed — bug fixes### Removed — removed features## [X.Y.Z.W] - YYYY-MM-DDDo NOT ask the user to describe changes. Infer from the diff and commit history.
Cross-reference the project's TODOS.md against the changes being shipped. Mark completed items automatically; prompt only if the file is missing or disorganized.
Read .claude/skills/review/TODOS-format.md for the canonical format reference.
1. Check if TODOS.md exists in the repository root.
If TODOS.md does not exist: Use AskUserQuestion:
TODOS.md with a skeleton (# TODOS heading + ## Completed section). Continue to step 3.2. Check structure and organization:
Read TODOS.md and verify it follows the recommended structure:
## <Skill/Component> headings**Priority:** field with P0-P4 value## Completed section at the bottomIf disorganized (missing priority fields, no component groupings, no Completed section): Use AskUserQuestion:
3. Detect completed TODOs:
This step is fully automatic — no user interaction.
Use the diff and commit history already gathered in earlier steps:
git diff <base>...HEAD (full diff against the base branch)git log <base>..HEAD --oneline (all commits being shipped)For each TODO item, check if the changes in this PR complete it by:
Be conservative: Only mark a TODO as completed if there is clear evidence in the diff. If uncertain, leave it alone.
4. Move completed items to the ## Completed section at the bottom. Append: **Completed:** vX.Y.Z (YYYY-MM-DD)
5. Output summary:
TODOS.md: N items marked complete (item1, item2, ...). M items remaining.TODOS.md: No completed items detected. M items remaining.TODOS.md: Created. / TODOS.md: Reorganized.6. Defensive: If TODOS.md cannot be written (permission error, disk full), warn the user and continue. Never stop the ship workflow for a TODOS failure.
Save this summary — it goes into the PR body in Step 8.
Goal: Create small, logical commits that work well with git bisect and help LLMs understand what changed.
Analyze the diff and group changes into logical commits. Each commit should represent one coherent change — not one file, but one logical unit.
Commit ordering (earlier commits first):
Rules for splitting:
Each commit must be independently valid — no broken imports, no references to code that doesn't exist yet. Order commits so dependencies come first.
Compose each commit message:
<type>: <summary> (type = feat/fix/chore/refactor/docs)git commit -m "$(cat <<'EOF'
chore: bump version and changelog (vX.Y.Z.W)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"Push to the remote with upstream tracking:
git push -u origin <branch-name>Create a pull request using gh:
gh pr create --base <base> --title "<type>: <summary>" --body "$(cat <<'EOF'
## Summary
<bullet points from CHANGELOG>
## Pre-Landing Review
<findings from Step 3.5, or "No issues found.">
## Eval Results
<If evals ran: suite names, pass/fail counts, cost dashboard summary. If skipped: "No prompt-related files changed — evals skipped.">
## Greptile Review
<If Greptile comments were found: bullet list with [FIXED] / [FALSE POSITIVE] / [ALREADY FIXED] tag + one-line summary per comment>
<If no Greptile comments found: "No Greptile comments.">
<If no PR existed during Step 3.75: omit this section entirely>
## TODOS
<If items marked complete: bullet list of completed items with version>
<If no items completed: "No TODO items completed in this PR.">
<If TODOS.md created or reorganized: note that>
<If TODOS.md doesn't exist and user skipped: omit this section>
## Test plan
- [x] All Rails tests pass (N runs, 0 failures)
- [x] All Vitest tests pass (N tests)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"Output the PR URL — this should be the final output the user sees.
git push only.YYYY-MM-DD/ship, next thing they see is the review + PR URL.c86faa7
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.