# search: address PR review — repo scoping, HasFilters, JSON invariant

`7ef3a57`→[main](/content/gh/entireio/cli/commits/main/index.html)·

alishakawaguchi·1mo ago·3 files·+59 added/-7 removed

- toQuery: an explicit owner/name repo filter now scopes the search even when --all-repos is also set (the more specific filter wins) instead of being silently ignored.
- Config.HasFilters now counts AllRepos, so `entire search --all-repos --json` with an empty query produces JSON instead of opening the TUI.
- Result.UnmarshalJSON clears the typed payload pointers before decoding so a reused Result keeps the "exactly one non-nil" invariant.
- Correct the stale "fetch the maximum" comment (it fetches DefaultLimit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

## Sessions

0debb873cd3aView transcript

[?\
Redesign Search TUI Table with Master-Detail LayoutClaude Code·Opus 4.8[1m]·1 step](/content/gh/entireio/cli/session/a891fc9c-9a5b-4cbb-8341-7bb4230da520#timeline-0debb873cd3a/index.html)

## Changes

3

- cmd/entire/cli

- search

- Msearch.go+21/-4

- Msearch_test.go+35

- Msearch_cmd.go+3/-3

```
153 unmodified lines

154
155
156
157
158
159
160
161
162
219 unmodified lines

382
383
384
382
385
386
387
388
157 unmodified lines

546
547
548
546
549
550
551
552
553
554
555
556
557
558
559
560
548
561
562
563
564
550
565
566
567
568
569
570

153 unmodified lines

r.Type = raw.Type
	 r.Meta = raw.Meta
	 r.rawData = raw.Data
	 // Clear any previously-decoded payloads so a reused Result keeps the
	 // "exactly one typed pointer is non-nil" invariant.
	 r.Checkpoint, r.Commit, r.Session = nil, nil, nil

switch raw.Type {
	 case TypeCheckpoint:
219 unmodified lines

// HasFilters reports whether any filter fields are set on the config.
func (c Config) HasFilters() bool {
	 return c.Author != "" || c.Date != "" || c.Branch != "" || len(c.Repos) > 0
	 return c.Author != "" || c.Date != "" || c.Branch != "" || len(c.Repos) > 0 || c.AllRepos
}

// ParsedInput holds the parsed query and optional filters extracted from search input.
157 unmodified lines

return nil, err
	}
	 allRepos := cfg.AllRepos || (len(cfg.Repos) == 1 && cfg.Repos[0] == AllReposFilter)
	 if len(cfg.Repos) > 0 && !allRepos {
		 hasExplicitRepo := false
		 for _, repo := range cfg.Repos {
			 if repo != AllReposFilter {
				 hasExplicitRepo = true
				 break
			}
		}
		switch {
		case hasExplicitRepo:
			 // An explicit owner/name filter always scopes the search, even when
			 // --all-repos is also set (the more specific filter wins).
			 for _, repo := range cfg.Repos {
				 q.Add("repo", repo)
				 if repo != AllReposFilter {
					 q.Add("repo", repo)
				}
			}
		} else if !allRepos && cfg.Owner != "" && cfg.Repo != "" {
		case allRepos:
			 // No repo scoping — search every accessible repo.
		case cfg.Owner != "" && cfg.Repo != "":
			 q.Set("repo", cfg.Owner+"/"+cfg.Repo)
		}
		 // Don't set types — let the API return all types (checkpoints, commits, sessions, etc.)
```

Mcmd/entire/cli/search/search.go+21/-4

```
638 unmodified lines

639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
118 unmodified lines

795
796
797
798
799
800
801
802
803

638 unmodified lines

}
}

func TestSearch_ExplicitRepoWinsOverAllRepos(t *testing.T) {
	 t.Parallel()

var capturedReq *http.Request
	 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		 capturedReq = r
		 resp := Response{Results: []Result{}, Total: 0, Page: 1}
		 w.Header().Set("Content-Type", "application/json")
		 json.NewEncoder(w).Encode(resp) //nolint:errcheck // test helper response
	 }))
	 defer srv.Close()

// --all-repos alongside an explicit owner/name filter must scope to the
	 // explicit repo (the more specific filter wins), not search all repos.
	 _, err := Search(context.Background(), Config{
		 ServiceURL:  srv.URL,
		 GitHubToken:  "tok",
		 Owner:       "default-owner",
		 Repo:        "default-repo",
		 Query:       "q",
		 AllRepos:    true,
		 Repos:       []string{"owner/explicit"},
	 })
	 if err != nil {
		 t.Fatal(err)
	 }

if got := capturedReq.URL.Query()["repo"]; len(got) != 1 || got[0] != "owner/explicit" {
		 t.Errorf("repo params = %v, want [owner/explicit]", got)
	 }
}

func TestSearch_MultipleExplicitReposRejected(t *testing.T) {
	 t.Parallel()

118 unmodified lines

if !(Config{Repos: []string{"entirehq/entire.io"}}).HasFilters() {
		 t.Error("config with Repos should have filters")
	 }
	 if !(Config{AllRepos: true}).HasFilters() {
		 t.Error("config with AllRepos should have filters")
	 }
	 if !(Config{Author: "alice", Date: testDateWeek}).HasFilters() {
		 t.Error("config with both should have filters")
	 }
}
