Make entire runner setup tailoring the default action - #2272
Conversation
`runner setup` needed `--run` to finish its job. Without it, it scaffolded the six generic runners and then printed a ~40KB tailoring prompt to stdout — machine-facing text on a human's terminal, with the two lines explaining it at the bottom, after the wall. And `-y` answered only one of the command's two confirmations: the create-defaults one. The re-tune confirmation still prompted, so `-y` on an already-configured repo asked a question anyway, and on a non-TTY printed "re-run with --run". Setup now has four modes, resolved in one place: setup (TTY) ask once: generic defaults, or defaults tailored to this repo setup -y create the defaults if missing, then tailor them setup --defaults-only create the generic defaults and stop; no provider call setup --print-prompt the old default: print the prompt for your own agent setup --dry-run tailor and show a diff, writing nothing setup --run deprecated alias for -y, hidden from help, warns `resolveRunnerSetupMode` is the only place consent is taken, which is what makes `-y` answer everything; the two huh confirmations collapse into one select. Non-interactive with no mode flag names the four choices and exits 1 rather than half-completing. `runnerSetupMode.writesRunnerFiles()` is the consent question as a fail-closed predicate. The zero value previously authorized the most invasive path: `mode == ""` passed `!= setupModeDryRun`, scaffolded, and fell through to the apply. `exhaustive` now makes a new mode a build failure at both predicates and the dispatch. `--dry-run` writes nothing at all, so in a repo with no runners it previews the tailoring of the embedded set it would otherwise have created. It diffs each `prompt.template` rather than the JSON file — the template is the only field that changes and it is stored as one long JSON string, so a file diff would be a single unreadable line — with unchanged runs over 6 lines collapsed so the preview does not become its own wall of text. The summary provider is now resolved before `gatherTuningContext` rather than after. Resolution can fail outright or stop to ask which provider to use, and both were happening after two `gh` subprocesses, a git walk, and up to nine trail API round trips. `--sources` and `--limit` moved below the mode decision too, so `--defaults-only --limit 0` is no longer rejected for flags it never reads. Also in passing: `runTuning` stopped re-deriving a TextGenerator that `resolveCheckpointSummaryProvider` already returns; `createDefaultRunners` (was `ensureRunnersPresent`) stopped re-deriving the existence check its caller had already made; proposals are processed in sorted order so messages and diffs are deterministic; and the two runner loaders share one parser. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M1PEEEG5N8WPF0ZZTS5AEYG7
There was a problem hiding this comment.
🟡 Changes recommended
--run is described as hidden from help in the PR, but the implementation only deprecates it and does not hide it from --help output.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR reworks the experimental entire runner setup command so that “tailor the default runners to this repo” is the primary path, rather than printing a large prompt by default unless --run is passed. It centralizes consent/mode selection, adds explicit setup modes (defaults-only / print-prompt / dry-run), and refactors the tuning pipeline to support an apply vs preview split with deterministic output and improved testability.
Changes:
- Introduces
runnerSetupModeand a singleresolveRunnerSetupModedecision point so-y/--yesanswers the full flow and non-interactive runs without an explicit mode fail closed. - Adds
--defaults-only,--print-prompt, and--dry-runmodes; deprecates--runas an alias of--yes; validates--sources/--limitonly when the selected mode actually gathers. - Refactors tuning into
runTuning+classifyTuneProposals+applyTunedRunners/previewTunedRunners, plus a shared parser for embedded vs on-disk runner configs; adds focused unit tests for the new semantics and diff rendering.
File summaries
| File | Description |
|---|---|
| cmd/entire/cli/runner_setup.go | Implements mode resolution/consent, new flags, fail-closed non-interactive behavior, and the updated setup flow. |
| cmd/entire/cli/runner_setup_test.go | Adds new tests covering mode precedence, non-interactive behavior, and dry-run/yes/defaults-only semantics. |
| cmd/entire/cli/runner_prompt.go | Makes embedded and on-disk runner loading share a parser; adds defaultTuneRunners for dry-run on empty repos. |
| cmd/entire/cli/runner_prompt_test.go | Updates tests for deferred limit validation and ensures defaults-only isn’t gated by --limit. |
| cmd/entire/cli/runner_init.go | Renames/scopes onboarding to createDefaultRunners (no prompting) and adds the interactive mode picker. |
| cmd/entire/cli/runner_init_test.go | Updates onboarding tests and pins runnerConfigsExist as the single gating predicate for scaffolding. |
| cmd/entire/cli/runner_apply.go | Extracts provider call + accept/reject logic; adds dry-run preview diff rendering and deterministic proposal processing. |
Review details
- Files reviewed: 7/7 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 5335faf. Configure here.
Two review findings from Bugbot. `--sources` and `--limit` were validated after the scaffold, so `-y --limit 0` on a repo with no runners created `.entire/runners` and then failed — a usage error left the repo modified. They are now checked before anything is written, still only for the modes that actually read repository signal, so `--defaults-only --limit 0` remains unaffected by flags it never reads. That is a third capability predicate on the mode, `gathersSignal()`, alongside `writesRunnerFiles()` and `needsProvider()`. The setup picker ran under `form.Run()`, so the command's context could not abort it and `handleFormCancellation` could never see `context.Canceled`. It now uses `RunWithContext`, matching `confirmPluginAction`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01M1PGGFEJTBBJG3N0NR67G7NZ

https://entire.io/gh/entireio/cli/trails/1239
Why
entire runner setupneeded--runto finish its job. Without it, it scaffolded the six generic runners and then printed a ~40KB tailoring prompt to stdout — machine-facing text on a human's terminal, with the two lines explaining what it was for at the very bottom, after the wall. Going through the Trails enablement instructions, that reads as something having gone wrong.-yalso answered only one of the command's two confirmations. It skipped create-defaults; the re-tune confirmation still prompted, so-yon an already-configured repo asked a question anyway, and on a non-TTY told you to "re-run with--run".What changed
Four modes, resolved in one place:
setup(TTY)setup -ysetup --defaults-onlysetup --print-promptsetup --dry-runsetup --run-y, hidden from help, warns--defaults-only,--print-promptand--dry-runare mutually exclusive; an explicit mode outranks-y, so-y --dry-runstill writes nothing.resolveRunnerSetupModeis the only place consent is taken, which is what makes-yanswer everything — the twohuhconfirmations collapse into one select, andcreateDefaultRunners(wasensureRunnersPresent) no longer asks at all. Non-interactive with no mode flag names the four choices and exits 1 instead of half-completing.Consent is now fail-closed
runnerSetupMode.writesRunnerFiles()is the consent question as a predicate over a total switch. The zero value previously authorized the most invasive path:mode == ""passed!= setupModeDryRun, scaffolded, and fell through toapplyTunedRunners.exhaustivenow makes a new mode a build failure at both predicates and at the dispatch — it caught an incomplete switch while I was writing this.--dry-runWrites nothing at all, so in a repo with no runners it previews the tailoring of the embedded set it would otherwise have created. It diffs each
prompt.templaterather than the JSON file: the template is the only field that changes and it is stored as one long JSON string, so a file-level diff would be a single unreadable line. Unchanged runs over 6 lines collapse to@@ N unchanged line(s) @@so the preview doesn't become its own wall of text.Ordering fixes
The summary provider is resolved before
gatherTuningContextrather than after. Resolution can fail outright, or stop to ask which provider to use, and both were happening after twoghsubprocesses, a git walk, and up to nine trail API round trips.--sourcesand--limitmoved below the mode decision too, so--defaults-only --limit 0is no longer rejected for flags it never reads.Also in passing
runTuningstopped re-deriving aTextGeneratorviaagent.Get+agent.AsTextGenerator—resolveCheckpointSummaryProvideralready returns one onprovider.TextGenerator, guaranteed non-nil by its constructor. Dropped two unreachable error branches and theagentimport fromrunner_apply.go.createDefaultRunnersstopped re-deriving the existence check its caller had already made, so that invariant has one home.slices.Sorted(maps.Keys(...))), so skip messages and the diff are deterministic rather than in Go's randomized map order.handleFormCancellation, so Ctrl+C prints "Runner setup cancelled." and exits 0 like the other 17 pickers in the CLI.Testing
mise run checkclean: fmt, lint (0 issues), andtest:ci— 10,509 unit tests, the integration leg under-race, and both E2E canary legs (56 vogon + 4 roger-roger). No newmise run duphits in the touched files.18 tests across
runner_setup_test.go(new),runner_init_test.goandrunner_prompt_test.go. The behavioural pair that pins the distinction this PR is about:--dry-runcreates no.entire/runners, and-ycreates the full set before the provider is involved, so a provider failure still leaves a working generic set behind.TestClassifyTuneProposalscovers accept / reject / verbatim-no-op against canned proposals — the accept/reject rules are now testable without a provider call, which is what therunTuning/classifyTuneProposals/applyTunedRunnerssplit bought.Every test that could reach provider resolution stubs
loadSummarySettings,getSummaryAgentand both discovery seams. That last part matters: an unresolvable provider name reachesexternal.DiscoverAndRegisterAlways, which globs the real$PATHforentire-agent-*binaries and execs each match, so without the stubs these tests would depend on the developer's machine.Also smoke-tested the real binary in throwaway repos: help text, the no-flag error, mutual exclusion,
--defaults-only(and its no-op re-run, and that--limit 0no longer gates it), the--print-promptstdout/stderr split,--run's deprecation warning, that the provider error now arrives with no gather spinner ahead of it, and that--dry-runleaves an existing set byte-identical.Not in this PR
GenerateTextcall is still never exercised end-to-end. That was true before this change too; testing it means a real model call. Worth someone runningentire runner setup --dry-runagainst a live provider before this is leaned on.CHANGELOG.mdhas noUnreleasedsection, so this belongs in the next release's pass.runner init+runner tune) would arguably be the deeper fix. The file layout already found that seam,--defaults-onlyinherits four flags it cannot use, and the interactive picker only ever offers 2 of the 4 modes. I kept one command because that is the shape asked for and the group is still hidden/experimental, but it is worth revisiting if runners ever go customer-facing.gatherTuningContext'sghand trail I/O is still sequential — two subprocesses and up to nine independent HTTP round trips. The obvious remaining win in this command, butrunner_gather.gois untouched here.Note on blast radius
Runners are still internal-only, so the population that would notice the flag-semantics change is a handful of Entire repos. That is why it seemed worth doing now rather than after they are exposed.
🤖 Generated with Claude Code
Note
Medium Risk
Changes CLI flags and consent flow for a command that writes runner configs and invokes the configured summary provider; blast radius is limited because runners remain internal/experimental.
Overview
entire runner setupnow treats repo tailoring as the primary path instead of dumping a huge prompt unless you passed--run.-y/--yescreates missing default runners and runs the summary provider to rewrite prompts in place;--runis a deprecated alias for-y.Consent and modes are centralized in
resolveRunnerSetupMode: one interactive choice (tailor vs generic defaults only), explicit flags (--defaults-only,--print-prompt,--dry-run), and a non-interactive error when no mode is given.writesRunnerFiles()/needsProvider()gate disk writes and provider resolution so an unset mode cannot silently apply changes.Tailoring is split for apply vs preview:
runTuning+classifyTuneProposalshandle the model call and per-runner accept/reject rules;applyTunedRunnerswrites.entire/runners, while--dry-runusespreviewTunedRunnerswith a collapsed line diff onprompt.template(no files created, including on empty repos via embedded defaults). Provider resolution and--limit/--sourcesvalidation happen only when the chosen mode actually gathers or calls the model.Tests cover flag precedence, dry-run vs
-ydisk behavior, proposal classification, and diff rendering.Reviewed by Cursor Bugbot for commit 5335faf. Configure here.