# review: preserve default profile on configure edits

`e0d56ac`→[main](/content/gh/entireio/cli/commits/main/index.html)·  dipree·3w ago·4 files·+147 added/-7 removed

## Sessions

bb4e188948d8View transcript

## Changes

4

- cmd/entire/cli/review

- Mcmd.go+2/-2

- Mconfigure_test.go+105

- Mpicker.go+1/-1

- Mprofile.go+39/-4

```
300 unmodified lines

301
302
303
304
304
305
306
307
14 unmodified lines

322
323
324
325
325
326
327
328

300 unmodified lines

if opts.Local {
		scope = reviewScopeLocal
	}
	if err := saveReviewProfile(ctx, profileName, profile, true, scope); err != nil {
	if err := saveReviewProfile(ctx, profileName, profile, false, scope); err != nil {
		return err
	}
	fmt.Fprintf(out, "Review profile %q saved to %s with %s.\n", profileName, scope.file(), strings.Join(sortedMapKeys(profile.Agents), ", "))
	14 unmodified lines

if scopeErr != nil {
		return handlePickerError(cmd, silentErr, scopeErr)
	}
	if err := saveReviewProfile(ctx, name, profile, true, scope); err != nil {
	if err := saveReviewProfile(ctx, name, profile, false, scope); err != nil {
		return err
	}
	fmt.Fprintf(out, "Review profile %q saved to %s. Run `entire review`, or `entire review %s`, to start.\n", name, scope.file(), name)
```

Mcmd/entire/cli/review/cmd.go+2/-2

```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
9 unmodified lines

26
27
28
29
30
31
32
33
34
35
36
37
38
39
320 unmodified lines

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
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459

package review

import (
	"bytes"
	"context"
	"strings"
	"testing"

agenttypes "github.com/entireio/cli/cmd/entire/cli/agent/types"
	reviewtypes "github.com/entireio/cli/cmd/entire/cli/review/types"
	"github.com/entireio/cli/cmd/entire/cli/settings"
	"github.com/entireio/cli/cmd/entire/cli/testutil"
	"github.com/spf13/cobra"
)

const (
9 unmodified lines

set[a] = struct{}{}
}
return Deps{
	GetAgentsWithHooksInstalled: func(context.Context) []agenttypes.AgentName {
		out := make([]agenttypes.AgentName, 0, len(set))
		for name := range set {
			out = append(out, agenttypes.AgentName(name))
		}
		return out
	},
	NewSilentError: func(err error) error { return err },
	ReviewerFor: func(name string) reviewtypes.AgentReviewer {
		if _, ok := set[name]; ok {
			return &stubReviewer{name: name}
	},
	
}

func TestRunReviewConfigureScriptedPreservesProjectDefault(t *testing.T) {
	tmp := t.TempDir()
	testutil.InitRepo(t, tmp)
	t.Chdir(tmp)
	ctx := context.Background()

general := settings.ReviewProfileConfig{
		Task:   "General task.",
		Agents: map[string]settings.ReviewConfig{tAgentClaude: {Agent: tAgentClaude}},
	}
	if err := saveReviewProfile(ctx, DefaultProfileName, general, true, reviewScopeProject); err != nil {
		t.Fatalf("seed general profile: %v", err)
	}

var out bytes.Buffer
	cmd := &cobra.Command{}
	cmd.SetOut(&out)
	deps := configureTestDeps(tAgentClaude, tAgentCodex)
	if err := runReviewConfigure(ctx, cmd, "security", reviewConfigureOptions{Agents: []string{tAgentCodex}}, deps); err != nil {
		t.Fatalf("runReviewConfigure: %v", err)
	}

s, err := settings.Load(ctx)
	if err != nil {
		t.Fatalf("load settings: %v", err)
	}
	if s.ReviewDefaultProfile != DefaultProfileName {
		t.Fatalf("default profile = %q, want %s", s.ReviewDefaultProfile, DefaultProfileName)
	}
	if _, ok := s.ReviewProfiles["security"]; !ok {
		t.Fatalf("security profile was not saved: %#v", s.ReviewProfiles)
	}
}

func TestRunReviewConfigureScriptedLocalPreservesProjectDefault(t *testing.T) {
	tmp := t.TempDir()
	testutil.InitRepo(t, tmp)
	t.Chdir(tmp)
	ctx := context.Background()

var out bytes.Buffer
	cmd := &cobra.Command{}
	cmd.SetOut(&out)
	deps := configureTestDeps(tAgentClaude, tAgentCodex)
	if err := runReviewConfigure(ctx, cmd, "security", reviewConfigureOptions{Agents: []string{tAgentCodex}, Local: true}, deps); err != nil {
		t.Fatalf("runReviewConfigure: %v", err)
	}

s, err := settings.Load(ctx)
	if err != nil {
		t.Fatalf("load settings: %v", err)
	}
	if s.ReviewDefaultProfile != DefaultProfileName {
		t.Fatalf("effective default profile = %q, want %s", s.ReviewDefaultProfile, DefaultProfileName)
	}
	_, localRaw, localExists, err := settings.LoadLocalRaw(ctx)
	if err != nil || !localExists {
		t.Fatalf("local raw: exists=%v err=%v", localExists, err)
	}
	if got := decodeRawReviewDefault(localRaw); got != "" {
		t.Fatalf("local review_default_profile = %q, want empty so project default remains effective", got)
	}
}

func TestSaveReviewProfileFirstProfileSetsDefault(t *testing.T) {
	tmp := t.TempDir()
	testutil.InitRepo(t, tmp)
	t.Chdir(tmp)
	ctx := context.Background()

profile := settings.ReviewProfileConfig{
		Task:   "First task.",
		Agents: map[string]settings.ReviewConfig{tAgentClaude: {Agent: tAgentClaude}},
	}
	if err := saveReviewProfile(ctx, DefaultProfileName, profile, false, reviewScopeProject); err != nil {
		t.Fatalf("save first profile: %v", err)
	}
	s, err := settings.Load(ctx)
	if err != nil {
		t.Fatalf("load settings: %v", err)
	}
	if s.ReviewDefaultProfile != DefaultProfileName {
		t.Fatalf("default profile = %q, want %s", s.ReviewDefaultProfile, DefaultProfileName)
	}
}

func TestProfileJudge_ResolvesWorkerAlias(t *testing.T) {
	t.Parallel()
	// Judge names a worker alias; it must resolve to the underlying agent the

```

Mcmd/entire/cli/review/configure_test.go+105

```
962 unmodified lines

963
964
965
966
966
967
968
969

962 unmodified lines

profile.Task = profileTask(profileName, settings.ReviewProfileConfig{})
	}
	profiles[profileName] = profile
	defaultName := decodeRawString(raw, "review_default_profile")
defaultName := decodeRawReviewDefault(raw)
	if strings.TrimSpace(defaultName) == "" {
		defaultName = profileName
	}
```

Mcmd/entire/cli/review/picker.go+1/-1

```
425 unmodified lines

426
427
428
429
430
430
431
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
28 unmodified lines

503
504
505
471
472
506
507
508
509
510

425 unmodified lines

if err != nil {
		return err
	}
	hadProfiles := len(profiles) > 0
	profiles[profileName] = profile
	defaultName := decodeRawString(raw, "review_default_profile")
	if makeDefault || strings.TrimSpace(defaultName) == "" {
defaultName := decodeRawReviewDefault(raw)
switch {
case makeDefault:
		defaultName = profileName
case strings.TrimSpace(defaultName) == "" && !hadProfiles:
		hasLower, err := lowerReviewDefaultOrProfiles(ctx, scope)
		if err != nil {
			return err
		}
		if !hasLower {
			defaultName = profileName
		}
	}
return writeRawReviewProfiles(path, raw, profiles, defaultName)
}

func lowerReviewDefaultOrProfiles(ctx context.Context, scope reviewSettingsScope) (bool, error) {
	if scope != reviewScopeLocal {
		return false, nil
	}
	_, raw, exists, err := settings.LoadProjectRaw(ctx)
	if err != nil {
		return false, fmt.Errorf("load project settings before local default check: %w", err)
	}
	if !exists || raw == nil {
		return false, nil
	}
	return rawHasReviewDefaultOrProfiles(raw)
}

func rawHasReviewDefaultOrProfiles(raw map[string]json.RawMessage) (bool, error) {
	if strings.TrimSpace(decodeRawReviewDefault(raw)) != "" {
		return true, nil
	}
	profiles, err := decodeRawReviewProfiles(raw)
	if err != nil {
		return false, err
	}
	return len(profiles) > 0, nil
}

// loadReviewSettingsRaw reads the raw JSON object for the chosen settings file.
func loadReviewSettingsRaw(ctx context.Context, scope reviewSettingsScope) (string, map[string]json.RawMessage, error) {
	var (
28 unmodified lines

return profiles, nil
}

func decodeRawString(raw map[string]json.RawMessage, key string) string {
	if msg, ok := raw[key]; ok && len(msg) > 0 {
func decodeRawReviewDefault(raw map[string]json.RawMessage) string {
	if msg, ok := raw["review_default_profile"]; ok && len(msg) > 0 {
		var s string
		if err := json.Unmarshal(msg, &s); err == nil {
