review: preserve default profile on configure edits · Entire
review: preserve default profile on configure edits
e0d56ac→main· 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 {