Minitest by plan, not by luck — and a W2.B baseline that can see - #48
Merged
Conversation
Production project 40 asked for "all business logic covered by automated tests" and got RSpec: spec/ files in every revision prompt, rspec-rails in the Gemfile, and 62 examples the verification step never ran (rails_test is gated on test/**/*_test.rb). The framework was decided in the plan — the code agent named the conflict with its own "Minitest, not RSpec" rule and followed the task's file paths anyway. Nothing told the planner. Five dry runs of the creation planner with that project's exact intent produced RSpec in 3; one Minitest plan invented SimpleCov. So the rule goes where the choice is made, in the positive-only register both prompts already use: Minitest under test/, the Rails default, with the file layout named and the test stack closed against extra gems. Measured after: 5/5 creation runs and 2/2 modification runs name test/ only, no spec/, no RSpec, no coverage tooling. The tests extend the never-names regex with rspec|spec/|factory_bot|simplecov so the framing cannot slip. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RJNJrvTLzXwvxpgDVXEnKp
…llowing it "Minitest, not RSpec" was one line in the rules, and it lost to a file path: in production project 40 the agent said out loud that the conventions and the task conflicted, then followed the task and wrote spec/. A rule that only states the preference gives no answer when the plan disagrees with it. Both places the prompt already uses for this class of problem now cover tests. The anti-reflex list names the reflex the way it names Devise and Sidekiq, and the rule says which side wins and what to do with the other: write the equivalent Minitest tests under test/, add no test gem. Verified in dev on project 37 with a hand-made revision whose prompt names spec/models/score_spec.rb: the commit carries test/models/score_test.rb (ActiveSupport::TestCase, both validations plus the zero boundary), no spec/ directory, no rspec in the Gemfile — and W2.4 ran rails test, which is the check project 40 never reached. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RJNJrvTLzXwvxpgDVXEnKp
Every W2.B in production project 40 after revision 106 died in ~240 ms with
Bundler::GemNotFound and failing_routes: [] — no per-route data, which is
the one thing the baseline exists to produce. Each revision runs in a fresh
`docker run --rm` that mounts the workspace and nothing else, so the
`bundle install` an earlier revision's agent ran went into a container that
no longer exists: the lockfile names rspec-rails, this BUNDLE_PATH does not
have it, and W2.B is the first step to walk into the hole. It was blind
exactly when a gem had been added, and the agent then spent a turn
rediscovering the same thing, every revision.
AutoRemediate.ensure_bundle runs `bundle check` and, on failure, the recipe
W2.AR already applies after a failed W2.4 — the same install, moved before
the baseline instead of after the damage. W2.B smokes only once the bundle
is whole and says so in the sentinel when it is not; VerifyRevision.tally
gives the three callers that build {checks, passed, failed} one place to
do it.
Verified in dev on a scratch copy of project 43 whose lockfile names an
uninstalled gem: bundle check FAIL, ensure_bundle installs, both checks
PASS afterwards with the smoke booting Rails (1 891 ms, 5 runs). E2E green
at 652 s with both checks in every revision's baseline and no `applied`;
the 29-workspace sweep is unchanged at 26/29.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RJNJrvTLzXwvxpgDVXEnKp
W2.B in the canon now says it checks the bundle and installs what is missing before it smokes, with the reason (the sandbox container is throwaway, so an earlier revision's gems are in the lockfile and nowhere else). The W2.4 as-built note gains the project-40 finding that motivated the planner change: the test framework is decided in the plan, and the code agent follows a task's file paths over its own rules. Follow-ups records the three things this work deliberately did not build: the per-project gem volume (with its two open questions — lifecycle on project deletion, behaviour across image upgrades), the mechanical Minitest guard the measurement did not justify, and the fact that a revision writing no tests leaves no trace in metrics["verify"] at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RJNJrvTLzXwvxpgDVXEnKp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Production project 40 (2026-09-06) was asked for "all business logic covered by automated tests" and got an RSpec suite:
spec/paths in all six revision prompts,rspec-railsin the Gemfile, and 62 examples that verification never ran —rails_testis gated ontest/**/*_test.rb, and there were none. The same runs showed the W2.B route-smoke baseline dying in ~240 ms withBundler::GemNotFoundon every revision after the gem was added, so the baseline was blind exactly when it mattered.Four commits, ordered by where the decision is actually made.
1. The planner picks the framework (
1cca8b2)The code agent's rules already said "Minitest, not RSpec". The log shows it naming the conflict — "The conventions say 'Minitest, not RSpec' but the task says to create
spec/models/user_spec.rb— this is a conflict" — and then following the task. So the rule goes where the choice is made, in the positive-only register both planner prompts already use: Minitest undertest/, the file layout spelled out, and the test stack declared complete so the planner stops inventing SimpleCov.Measured with
bin/inspect-plan-application-creationon project 40's exact intent, same model (anthropic/claude-haiku-4.5). Baseline was 3 of 5 runs producing RSpec:spec/test/Modification planner (project 43, ×2):
test/only,spec/and RSpec absent from both. 7 of 7 clean, no escalation needed. The five plans still read concrete and file-level — the rule did not make the planner terse.2. The code agent translates rather than follows (
7443727)RevisionPromptgains the anti-reflex line (Tests → Minitest + fixtures in test/, NOT RSpec/FactoryBot) and a rule that answers the conflict instead of restating the preference: write the equivalent Minitest tests undertest/, the task's wording does not override this, add no test gem.Verified with a real dev revision (project 37, instruction 61) whose prompt names
spec/models/score_spec.rb. The commit carriestest/models/score_test.rb—ScoreTest < ActiveSupport::TestCase, five cases covering both validations plus the zero boundary — nospec/directory, norspecin the Gemfile, and W2.4 ran all five checks includingrails test. This time the agent did not report a conflict; it just wrotetest/.3. W2.B repairs the bundle before it smokes (
9986ea3)Each revision runs in a fresh
docker run --rmthat mounts the workspace and nothing else, andBUNDLE_PATH=/usr/local/bundledies with the container. A gem an earlier revision's agent installed is therefore in the lockfile and nowhere else, and W2.B — which runs before the agent — is the first step to walk into the hole: no per-route data, which is the one thing the baseline exists to produce, plus a re-download and a wasted agent turn every revision.AutoRemediate.ensure_bundlerunsbundle checkand, on failure, the recipe W2.AR already applies after a failed W2.4 — the same install, moved before the damage instead of after it. W2.B smokes only once the bundle is whole, and says so in the sentinel when it is not.VerifyRevision.tallygives the three callers that build{checks, passed, failed}one place to do it.Verified on a scratch copy of project 43 whose lockfile names an uninstalled gem:
bundle checkFAIL (174 ms) →ensure_bundle→false, ["bundler missing gems: ran \bundle install`"]→bundle checkPASS (225 ms) →route smokePASS (1 891 ms, 5 runs — Rails booted, which it could not have done before). The install-failed branch was exercised too, by accident: a Gemfile/lockfile mismatch under frozen mode makesbundle installrefuse,ensure_bundlereturns[]fixes, and W2.B skips the smoke and records that rather than running it intoBundler::GemNotFound`.4. Docs (
bc25098)W2.B in the canon, both CLAUDE.md bullets, and three follow-ups: the per-project gem volume (with its two open decisions — lifecycle on project deletion, behaviour across image upgrades), the mechanical Minitest guard the measurement did not justify building, and the fact that a revision writing no tests leaves no trace in
metrics["verify"]at all.Verification
bin/rails test— 700 runs, 2 706 assertions, 0 failuresbin/rubocop— 187 files, no offensesE2E_GENERATE=1 bin/rails test test/integration/generate_todo_list_test.rb— green, 652 s (budget 1 200), 42 assertions. Every revision:W2.B: PASS bundle check, PASS route smoke | W2.4: PASS bundle check, PASS db:prepare, PASS zeitwerk check, PASS route smoke, PASS rails test,appliedabsent as assertedbin/verify-workspace ~/projects/hifumi-dev-workspaces/project_*— 26/29, unchanged; the same three pre-existing failures (27, 39, 7)Not in this PR
spec/guard — its trigger never fired. Recorded in follow-ups as the fallback if the prompt rule regresses.Still open after merge
The production proof: start a project whose plan legitimately adds a gem, then check that the first revision after it shows
bundle check=false,applied=["bundler missing gems: ran \bundle install`"]androute smoke=trueabove 800 ms, with noFetching ` inside the agent's turn.Plan:
thoughts/shared/plans/2026-09-07/minitest-planning-and-baseline-bundle.md🤖 Generated with Claude Code
https://claude.ai/code/session_01RJNJrvTLzXwvxpgDVXEnKp