## Step 4: Test Framework Bootstrap {{TEST_BOOTSTRAP}} --- ## Step 5: Run tests (on merged code) **Run it:** `~/.claude/skills/gstack/bin/gstack-ship-preflight --tests-only --base --lane =...` (or the Step 1 call with lanes). It runs the free pre-gate first, before any lane, eval or remote gate (`gstack-pregate --allow-repo-commands`: stage 1 regen, secrets, strays, literals; stage 2 the selected lanes under pipefail), then every lane in parallel inside the evidence ledger, and prints `PREGATE:` and one `LANE: exit= tests_ran= log=` line each. `fail` or `incomplete` blocks: fix, rerun. `requires-remote` (other platforms; the remote gate under 8 cores) needs `gstack-pregate clear` or, under a user cap, `--remote --spend-cap `. Rerun after the final stamp (`gstack-ship-release finish` does); Step 18 refuses a stale one. Use the project's test commands discovered in Step 4 or documented in CLAUDE.md/AGENTS.md. Run every applicable suite; do not assume Rails or Vitest. Pass each as `--lane =`; use the same lane labels and exact commands again in Step 16. **If no applicable test suite exists:** Name the untested scope. AskUserQuestion: A) Add tests (recommended), B) Ship with this named testing gap, or C) Stop. Reuse an actual prior B answer only for the same scope and content; declining bootstrap alone is not that approval. B continues with the gap recorded, not passing tests. Independent build, eval, review and QA gates still apply. A declared but unavailable suite is a blocker, not an absent suite. A runs Step 4 with this new bootstrap choice, then returns here to run the tests. C stops this attempt. **For Rails projects using `bin/test-lane`, 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. Each lane runs as `gstack-evidence run --label -- ''`: transparent (exit code passes through) and recorded as `{command, exit, working-tree fingerprint, log path}` in `$GSTACK_STATE_ROOT/projects//-evidence.jsonl`, which Step 16 cites instead of re-running when the content hasn't changed. Each lane has its own log (no shared /tmp collisions between concurrent ships); read it for failure detail. **A lane passes on its runner summary line, not on exit 0 alone.** Read each lane's `tests_ran` from its `LANE:` line (or from the runner's own summary in the log) and keep the three states apart; never merge them: 1. `tests_ran: N` (N > 0) with exit 0 — the lane passed; record N for the PR body. 2. `tests_ran: 0` — **ZERO-RUN**: bun `0 pass 0 fail`, jest "No tests found", pytest "no tests ran", rspec "0 examples", `go test` with only `[no test files]`, or an empty selection after a filter. Exit 0 here is not a pass, so triage a ZERO-RUN lane below as a failed lane: a bad selector, a missing file or a skipped `describe` is an in-branch failure until proven otherwise. 3. `tests_ran: unknown` — the runner's summary was not recognised. Keep today's exit-0 pass and record `count unavailable` for that lane in the PR body. **If any test fails:** Do NOT immediately stop. When a free-suite shard failed, first Read `~/.claude/skills/gstack/ship/sections/measure.md` and rerun that shard's file list as it says (diagnostic, not a verdict). Then apply the Test Failure Ownership Triage: {{TEST_FAILURE_TRIAGE}} **After triage:** If any in-branch failures remain unfixed, **STOP**. Do not proceed. If all failures were pre-existing and handled (fixed, TODOed, assigned, or skipped), continue to Step 6. **If all pass:** Report each lane's pass count (or `count unavailable`) in one line and continue to Step 6. --- ## Step 6: Eval Suites (conditional) Evals are mandatory when prompt-related files change. Select from the full diff, including uncommitted changes, before deciding whether to skip. **1. Select affected suites using the project's contract.** **Project-native path:** Read CLAUDE.md/AGENTS.md, package scripts and the eval dependency map. Include changed prompts, skill templates, judges and harness code. Use the documented selector and pre-merge command. If it reports no affected suites, record that result and continue to Step 7. If prompt-related files changed but selection or the command is unknown, report the validation gap and ask before shipping. A missing Rails-pattern match is not a skip signal for another stack. **Rails example only — when this repository provides `bin/test-lane` and `test/evals/*_eval_runner.rb`:** - Match the diff against the project's documented prompt paths, such as `app/services/*_prompt_builder.rb`, generation/writer/designer services, evaluator/scorer/classifier/analyzer services, voice/writing/prompt/token concerns, chat tools, `config/system_prompts/*.txt` and `test/evals/**/*`. - Match changed files to each runner's `PROMPT_SOURCE_FILES`; follow shared judge/support/fixture imports to all affected suites. A runner such as `post_generation_eval_runner.rb` maps to `post_generation_eval_test.rb`. - Use the project's full pre-merge tier (`EVAL_JUDGE_TIER=full` for this runner). Do not substitute a cheaper development tier. If selection remains uncertain, include every plausibly affected suite. **2. Run the selected command and preserve its exit status.** For the Rails example: ```bash set -o pipefail EVAL_JUDGE_TIER=full EVAL_VERBOSE=1 bin/test-lane --eval test/evals/_eval_test.rb 2>&1 | tee /tmp/ship_evals.txt ``` Use the native command for other stacks. Respect the project's concurrency and retry policy. Rails suites sharing a test lane run sequentially; stop on the first failure before starting another paid suite. **Long eval suites (30+ min): launch detached so a turn boundary can't kill them.** Use the detached runner and eval lock; set its outer timeout to cover the project's declared suite duration and retries. Do not change individual eval limits. For a suite whose full bound fits 5400 seconds: ```bash ~/.claude/skills/gstack/bin/gstack-detach --label ship-evals --lock gstack-evals --timeout 5400 -- ``` Poll the printed log for `### gstack-detach EXIT= ###`. Silence is not success. Retain every configured attempt; skipped or unstarted cases do not satisfy coverage. **3. Check results and save evidence for Step 19.** - **If any eval fails:** Do not rerun the full gate. Show failures and available costs, then Read `~/.claude/skills/gstack/ship/sections/measure.md` and run its loop for each red case: classify, measure alone, fix at the cause, re-measure, then gate once. **STOP** only at its named-red stop. - **If all selected evals pass:** Record actual counts, any reused evidence and its source, and available costs. Continue to Step 7. ---
###`. Silence is not success. Retain every configured attempt; skipped or unstarted cases do not satisfy coverage. **3. Check results and save evidence for Step 19.** - **If any eval fails:** Do not rerun the full gate. Show failures and available costs, then Read `~/.claude/skills/gstack/ship/sections/measure.md` and run its loop for each red case: classify, measure alone, fix at the cause, re-measure, then gate once. **STOP** only at its named-red stop. - **If all selected evals pass:** Record actual counts, any reused evidence and its source, and available costs. Continue to Step 7. ---