Bare `entire scout` requires a profile selection (no silent default) · Entire

Bare entire scout requires a profile selection (no silent default)

7ebae08→main·

dipree·1mo ago·4 files·+132 added/-19 removed

A bare invocation no longer auto-runs the default crew:
- interactive: a chooser lists profiles (pre-selecting the default) and
the picked one runs;
- non-interactive: it prints the configured profiles and errors, so
automation must name a profile (entire scout <name>) rather than
silently spawning a default crew.

Explicit entire scout <name> and first-run setup are unchanged. Added a
regression test; updated existing smoke/dispatch tests to name the
profile explicitly.

Sessions

6e10ecd1aadfView transcript

Changes

4

660 unmodified lines

661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
29 unmodified lines

716
717
718
719
720
721
722
723
724
6 unmodified lines

731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
660 unmodified lines

if s == nil {
    s = &settings.EntireSettings{}
}

profileOverride = strings.TrimSpace(profileOverride)
interactiveTTY := interactive.IsTerminalWriter(out) && interactive.CanPromptInteractively()

// Bare `entire scout` never auto-runs a profile. Without a TTY we cannot
// prompt, so list the profiles (or point at setup) and require an explicit
// selection rather than silently spawning a default crew.
if profileOverride == "" && !interactiveTTY {
    cmd.SilenceUsage = true
    eo := cmd.ErrOrStderr()
    if profs := nonZeroProfiles(s.ReviewProfiles); len(profs) > 0 {
        ns := sortedProfileNames(profs)
        fmt.Fprintf(eo, "Specify a profile to scout, e.g. `entire scout %s`.
", ns[0])
        fmt.Fprintf(eo, "Configured profiles: %s\n", strings.Join(ns, ", "))
    } else {
        fmt.Fprintln(eo, "No scout profiles configured. Run `entire scout --configure` in a terminal first.")
    }
    return silentErr(errors.New("no profile specified"))
}

// Trigger first-run setup when no usable profile exists. Counting only
// non-zero profiles means a placeholder/empty entry (e.g. an empty
// `general` profile in a hand-edited preferences file) still routes through
29 unmodified lines

}

// Interactive bare `entire scout` with existing profiles: require a choice
// instead of defaulting silently.
if profileOverride == "" {
    picked, pickErr := promptForProfileToRun(ctx, s)
    if pickErr != nil {
        return handlePickerError(cmd, silentErr, pickErr)
    }
    profileOverride = picked
}

profileName, profile, err := selectReviewProfile(s, profileOverride)
if err != nil {
    cmd.SilenceUsage = true
}

Mcmd/entire/cli/review/cmd.go+33

231 unmodified lines

232 233 234 235 235 236 237 238 37 unmodified lines

276 277 278 279 279 280 281 282 30 unmodified lines

313 314 315 316 316 317 318 319 25 unmodified lines

345 346 347 348 348 349 350 351 6 unmodified lines

358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 11 unmodified lines

411 412 413 378 414 415 416 417 24 unmodified lines

442 443 444 409 445 446 447 448 116 unmodified lines

565 566 567 532 568 569 570 571 42 unmodified lines

614 615 616 581 617 618 619 620 44 unmodified lines

665 666 667 632 668 669 670 671 59 unmodified lines

731 732 733 698 734 735 736 737 36 unmodified lines

774 775 776 741 777 778 779 780 29 unmodified lines

810 811 812 777 813 814 815 816 25 unmodified lines

842 843 844 809 845 846 847 848 274 unmodified lines

1123 1124 1125 1090 1126 1127 1128 1129 29 unmodified lines

1159 1160 1161 1126 1162 1163 1164 1165

231 unmodified lines

rootCmd := cli.NewRootCmd() errBuf := &bytes.Buffer{} rootCmd.SetErr(errBuf) rootCmd.SetArgs([]string{"review"}) rootCmd.SetArgs([]string{"review", "general"}) err := rootCmd.Execute() if err == nil { t.Fatal("expected error when hooks are not installed") } 37 unmodified lines

rootCmd := cli.NewRootCmd() buf := &bytes.Buffer{} rootCmd.SetOut(buf) rootCmd.SetArgs([]string{"review"}) rootCmd.SetArgs([]string{"review", "general"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("execute: %v", err) } 30 unmodified lines

rootCmd := cli.NewRootCmd() errBuf := &bytes.Buffer{} rootCmd.SetErr(errBuf) rootCmd.SetArgs([]string{"review"}) rootCmd.SetArgs([]string{"review", "general"}) err := rootCmd.Execute() if err == nil { t.Fatal("expected error when configured skill not installed") } 25 unmodified lines

rootCmd := cli.NewRootCmd() buf := &bytes.Buffer{} rootCmd.SetOut(buf) rootCmd.SetArgs([]string{"review"}) rootCmd.SetArgs([]string{"review", "general"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("unexpected error: %v", err) } 6 unmodified lines

}

// TestRunReview_BareNonInteractiveRequiresProfile verifies that entire scout // with no profile, in a non-interactive context (the test has no TTY), never // auto-runs a default crew — it errors and lists the configured profiles. func TestRunReview_BareNonInteractiveRequiresProfile(t *testing.T) { setupCmdTestRepo(t) installHooksForCmdTest(t, "cursor") if err := seedReviewConfig(context.Background(), map[string]settings.ReviewConfig{ "cursor": {Prompt: "review the diff"}, }); err != nil { t.Fatal(err) }

rootCmd := cli.NewRootCmd() outBuf := &bytes.Buffer{} errBuf := &bytes.Buffer{} rootCmd.SetOut(outBuf) rootCmd.SetErr(errBuf) rootCmd.SetArgs([]string{"review"}) // bare, no profile

err := rootCmd.Execute() if err == nil { t.Fatal("bare non-interactive scout should require a profile, got nil error") } if !strings.Contains(errBuf.String(), "Specify a profile") { t.Errorf("stderr should ask for a profile, got:\n%s", errBuf.String()) } if !strings.Contains(errBuf.String(), "general") { t.Errorf("stderr should list the configured profile, got:\n%s", errBuf.String()) } // It must not have spawned a crew: no pending marker written. _, exists, markerErr := review.ReadPendingReviewMarker(context.Background()) if markerErr == nil && exists { t.Error("bare non-interactive scout should not have started a review") } }

// TestRunReview_FlagOverrideSkipsPicker verifies that --agent flag bypasses // the interactive picker even when multiple eligible agents are configured. func TestRunReview_FlagOverrideSkipsPicker(t *testing.T) { 11 unmodified lines

rootCmd := cli.NewRootCmd() buf := &bytes.Buffer{} rootCmd.SetOut(buf) rootCmd.SetArgs([]string{"review", "--agent", "opencode"}) rootCmd.SetArgs([]string{"review", "general", "--agent", "opencode"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("unexpected error: %v", err) } 24 unmodified lines

rootCmd := cli.NewRootCmd() errBuf := &bytes.Buffer{} rootCmd.SetErr(errBuf) rootCmd.SetArgs([]string{"review", "--agent", "opencode"}) rootCmd.SetArgs([]string{"review", "general", "--agent", "opencode"}) err := rootCmd.Execute() if err == nil { t.Fatal("expected error when --agent points at hookless agent") } 116 unmodified lines

cmd := review.NewCommand(deps) cmd.SetOut(out) cmd.SetErr(&bytes.Buffer{}) cmd.SetArgs([]string{}) cmd.SetArgs([]string{"general"})

if err := cmd.Execute(); err != nil { t.Fatalf("unexpected error: %v", err) } 42 unmodified lines

cmd := review.NewCommand(deps) cmd.SetOut(buf) cmd.SetErr(&bytes.Buffer{}) cmd.SetArgs([]string{}) cmd.SetArgs([]string{"general"})

if err := cmd.Execute(); err != nil { t.Fatalf("unexpected error: %v", err) } 44 unmodified lines

cmd := review.NewCommand(deps) cmd.SetOut(&bytes.Buffer{}) cmd.SetErr(&bytes.Buffer{}) cmd.SetArgs([]string{"--prompt", "Focus this run on regressions."}) cmd.SetArgs([]string{"general", "--prompt", "Focus this run on regressions."})

if err := cmd.Execute(); err != nil { t.Fatalf("unexpected error: %v", err) } 59 unmodified lines

cmd := review.NewCommand(deps) cmd.SetOut(&bytes.Buffer{}) cmd.SetErr(&bytes.Buffer{}) cmd.SetArgs([]string{}) cmd.SetArgs([]string{"general"})

executeErr := cmd.Execute() // may error (agent-a not a real agent); we only care about picker routing _ = executeErr // intentionally ignored: this test only asserts picker routing 36 unmodified lines

cmd := review.NewCommand(deps) cmd.SetOut(buf) cmd.SetErr(&bytes.Buffer{}) cmd.SetArgs([]string{"--agent", "cursor"}) cmd.SetArgs([]string{"general", "--agent", "cursor"})

// cursor is not launchable in our stub (reviewerFor returns nil), so it // falls through to RunMarkerFallback. That's fine — we only care that 29 unmodified lines

cmd := review.NewCommand(deps) cmd.SetOut(&bytes.Buffer{}) cmd.SetErr(errBuf) cmd.SetArgs([]string{}) cmd.SetArgs([]string{"general"})

err := cmd.Execute() if err != nil { 25 unmodified lines

cmd := review.NewCommand(deps) cmd.SetOut(&bytes.Buffer{}) cmd.SetErr(&bytes.Buffer{}) cmd.SetArgs([]string{}) cmd.SetArgs([]string{"general"})

if err := cmd.Execute(); err != nil { t.Fatalf("unexpected error: %v", err) } 274 unmodified lines

cmd := review.NewCommand(deps) cmd.SetOut(buf) cmd.SetErr(&bytes.Buffer{}) cmd.SetArgs([]string{}) cmd.SetArgs([]string{"general"})

if err := cmd.Execute(); err != nil { t.Fatalf("unexpected error: %v", err) } 29 unmodified lines

cmd := review.NewCommand(deps) cmd.SetOut(buf) cmd.SetErr(&bytes.Buffer{}) cmd.SetArgs([]string{}) cmd.SetArgs([]string{"general"})

if err := cmd.Execute(); err != nil { t.Fatalf("unexpected error: %v", err) }