Skip to content

Make entire runner setup tailoring the default action - #2272

Merged
Soph merged 3 commits into
mainfrom
soph/runner-setup-modes
Sep 7, 2026
Merged

Make entire runner setup tailoring the default action#2272
Soph merged 3 commits into
mainfrom
soph/runner-setup-modes

Conversation

@Soph

@Soph Soph commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

https://entire.io/gh/entireio/cli/trails/1239

Why

entire 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 what it was for at the very bottom, after the wall. Going through the Trails enablement instructions, that reads as something having gone wrong.

-y also answered only one of the command's two confirmations. It skipped create-defaults; the re-tune confirmation still prompted, so -y on 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:

behaviour
setup (TTY) asks once: create the defaults and tailor them / create the generic defaults only
setup -y creates the defaults if missing, then tailors them — the full action
setup --defaults-only creates the generic defaults and stops; no provider call
setup --print-prompt the old default: prints the prompt for your own agent to run
setup --dry-run tailors and shows a diff, writes nothing
setup --run deprecated alias for -y, hidden from help, warns

--defaults-only, --print-prompt and --dry-run are mutually exclusive; an explicit mode outranks -y, so -y --dry-run still writes nothing.

resolveRunnerSetupMode is the only place consent is taken, which is what makes -y answer everything — the two huh confirmations collapse into one select, and createDefaultRunners (was ensureRunnersPresent) 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 to applyTunedRunners. exhaustive now 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-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-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 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 via agent.Get + agent.AsTextGeneratorresolveCheckpointSummaryProvider already returns one on provider.TextGenerator, guaranteed non-nil by its constructor. Dropped two unreachable error branches and the agent import from runner_apply.go.
  • createDefaultRunners stopped re-deriving the existence check its caller had already made, so that invariant has one home.
  • Proposals are processed in sorted order (slices.Sorted(maps.Keys(...))), so skip messages and the diff are deterministic rather than in Go's randomized map order.
  • The on-disk and embedded runner loaders share one parser.
  • The picker routes cancellation through the shared handleFormCancellation, so Ctrl+C prints "Runner setup cancelled." and exits 0 like the other 17 pickers in the CLI.
  • One sentence, one place for "created N defaults" — the three copies had already drifted to "default" / "generic default" / "working default".

Testing

mise run check clean: fmt, lint (0 issues), and test:ci — 10,509 unit tests, the integration leg under -race, and both E2E canary legs (56 vogon + 4 roger-roger). No new mise run dup hits in the touched files.

18 tests across runner_setup_test.go (new), runner_init_test.go and runner_prompt_test.go. The behavioural pair that pins the distinction this PR is about: --dry-run creates no .entire/runners, and -y creates the full set before the provider is involved, so a provider failure still leaves a working generic set behind. TestClassifyTuneProposals covers accept / reject / verbatim-no-op against canned proposals — the accept/reject rules are now testable without a provider call, which is what the runTuning / classifyTuneProposals / applyTunedRunners split bought.

Every test that could reach provider resolution stubs loadSummarySettings, getSummaryAgent and both discovery seams. That last part matters: an unresolvable provider name reaches external.DiscoverAndRegisterAlways, which globs the real $PATH for entire-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 0 no longer gates it), the --print-prompt stdout/stderr split, --run's deprecation warning, that the provider error now arrives with no gather spinner ahead of it, and that --dry-run leaves an existing set byte-identical.

Not in this PR

  • The actual GenerateText call is still never exercised end-to-end. That was true before this change too; testing it means a real model call. Worth someone running entire runner setup --dry-run against a live provider before this is leaned on.
  • No changelog entryCHANGELOG.md has no Unreleased section, so this belongs in the next release's pass.
  • Two subcommands (runner init + runner tune) would arguably be the deeper fix. The file layout already found that seam, --defaults-only inherits 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's gh and trail I/O is still sequential — two subprocesses and up to nine independent HTTP round trips. The obvious remaining win in this command, but runner_gather.go is 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 setup now treats repo tailoring as the primary path instead of dumping a huge prompt unless you passed --run. -y / --yes creates missing default runners and runs the summary provider to rewrite prompts in place; --run is 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 + classifyTuneProposals handle the model call and per-runner accept/reject rules; applyTunedRunners writes .entire/runners, while --dry-run uses previewTunedRunners with a collapsed line diff on prompt.template (no files created, including on empty repos via embedded defaults). Provider resolution and --limit / --sources validation happen only when the chosen mode actually gathers or calls the model.

Tests cover flag precedence, dry-run vs -y disk behavior, proposal classification, and diff rendering.

Reviewed by Cursor Bugbot for commit 5335faf. Configure here.

`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
@Soph
Soph requested a review from a team as a code owner September 4, 2026 14:52
Copilot AI lite review requested due to automatic review settings September 4, 2026 14:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 runnerSetupMode and a single resolveRunnerSetupMode decision point so -y/--yes answers the full flow and non-interactive runs without an explicit mode fail closed.
  • Adds --defaults-only, --print-prompt, and --dry-run modes; deprecates --run as an alias of --yes; validates --sources/--limit only 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.

Comment thread cmd/entire/cli/runner_setup.go

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ 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.

Comment thread cmd/entire/cli/runner_setup.go Outdated
Comment thread cmd/entire/cli/runner_init.go
Soph and others added 2 commits September 4, 2026 17:27
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
@Soph
Soph merged commit 385a0a7 into main Sep 7, 2026
13 checks passed
@Soph
Soph deleted the soph/runner-setup-modes branch September 7, 2026 11:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants