Skip to content

cli: add code search with multi-region fan-out (ENT-993)#1616

Open
evisdren wants to merge 4 commits into
mainfrom
evis/ent-993-code-search-client
Open

cli: add code search with multi-region fan-out (ENT-993)#1616
evisdren wants to merge 4 commits into
mainfrom
evis/ent-993-code-search-client

Conversation

@evisdren

@evisdren evisdren commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds cmd/entire/cli/codesearch/ — client package for peregrine's /search/api/search endpoint
  • Wires --code and --case-sensitive flags into entire search, gated behind ENTIRE_CODE_SEARCH=1
  • Multi-region fan-out matching the BFF pattern: list repos from entire-core → group by cell/jurisdiction → parallel search with per-cell timeouts → merge results
  • Single-cell path when --repo filter is specified (routes to repo's owning cell)
  • One bad cell never sinks the search — errors are logged and skipped

How it works

Scenario Auth path Behavior
--code query (no filter) ListRepos → group by cell → NewEntireAPICellClient per jurisdiction Fan-out across all cells
--code --repo owner/name query NewAuthenticatedEntireAPICellClient Single cell (repo's owner)
--code repo:owner/name query Same (inline filter parsed) Single cell
--code --all-repos query Fan-out (same as no filter) Fan-out across all cells

Files

  • cmd/entire/cli/codesearch/codesearch.go — client types and Search() function
  • cmd/entire/cli/codesearch/codesearch_test.go — client tests (success, API error, non-JSON error, response too large)
  • cmd/entire/cli/search_cmd.go — command wiring, fan-out orchestration, output formatters
  • cmd/entire/cli/search_cmd_test.go — command tests (gate, flags, output, groupReposByCell, mergeSearchResults)

Test plan

  • go test ./cmd/entire/cli/codesearch/... passes
  • go test ./cmd/entire/cli/ -run "TestSearchCmd|TestWriteCodeSearch|TestCodeSearch|TestGroupRepos|TestMergeSearch" passes
  • mise run lint clean
  • CI passes

🤖 Generated with Claude Code

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.

Pull request overview

Adds a new cmd/entire/cli/codesearch package to provide a small, typed Go client for peregrine’s POST /search/api/search endpoint using the existing authenticated api.Client, including response-size capping and basic HTTP error parsing.

Changes:

  • Introduces codesearch.Search() plus request/response types for peregrine code search.
  • Implements an 8 MiB response read cap with explicit overflow detection.
  • Adds unit tests covering success, API error responses, non-JSON error bodies, and oversized responses.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
cmd/entire/cli/codesearch/codesearch.go New code search client wrapper around api.Client.Post, including response cap and error parsing.
cmd/entire/cli/codesearch/codesearch_test.go Unit tests for the new client (success + error/edge cases).

Comment on lines +84 to +86
if apiErr.Message == "" && len(body) > 0 {
apiErr.Message = string(body)
}
Comment thread cmd/entire/cli/codesearch/codesearch_test.go
@evisdren evisdren changed the title cli: add codesearch client package for peregrine (ENT-993) cli: add code search with multi-region fan-out (ENT-993) Jul 2, 2026
@evisdren evisdren force-pushed the evis/ent-993-code-search-client branch 7 times, most recently from 1e7a2b3 to 0ef9bc1 Compare July 3, 2026 05:17
evisdren and others added 2 commits July 3, 2026 17:43
Add `entire search --code <query>` to search code content across all
jurisdictions via the peregrine search service, gated behind
ENTIRE_CODE_SEARCH=1.

Architecture:
- codesearch package: thin client for peregrine's search endpoint
  through the cell's entire-api gateway (GET /api/v1/search/api/search)
- Multi-region fan-out: list repos from core → group by jurisdiction →
  search each cell in parallel with per-cell timeouts → merge results
  by score, deduplicate, cap to --limit
- Per-cell repo ID scoping: each cell only receives ULIDs belonging to
  its jurisdiction, avoiding cross-jurisdiction leakage
- Partial failure resilience: a down region warns but returns results
  from healthy cells; only all-cells-fail is an error

Repo filter formats:
- gh/owner/repo, et/proj/repo, bare owner/repo, or raw ULID
- Defaults to current repo (derived from git origin), matching
  checkpoint-search behavior; use --all-repos or repo:* for all repos
- Multiple filters supported via --repo flag or inline repo: syntax

Output:
- Text: grep-style (repo:path:line: context), truncated to 200 chars
- JSON: full response with stats, repo_stats, and results (--json)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Group fan-out by cell (not jurisdiction), matching BFF pattern
- Resolve cell apiUrl from cluster catalog for direct routing
- Align resolveRepoFilters with BFF: only strip gh/, try unstripped
  filter as full_name fallback, match id on raw filter
- Use sort.SliceStable with tiebreaker for deterministic output
- Route single-cell path through mergeSearchResults for uniform
  sort/dedup/truncate
- TrimSpace error body fallback in codesearch client
- Strengthen TestSearch_NonJSONError assertions
- Add searchAllCells test seams and 5 integration tests covering
  cell grouping, cluster URL resolution, partial failure, filter
  resolution, and single-cell merge
- Document case_sensitive as optimistic (peregrine proto lacks it)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@evisdren evisdren force-pushed the evis/ent-993-code-search-client branch from 1fdef33 to 8ec2ed5 Compare July 4, 2026 00:59
evisdren and others added 2 commits July 3, 2026 20:35
The cluster catalog is keyed by Cluster.Slug, which corresponds to
RepoIndexEntry.ClusterSlug — not RepoIndexEntry.Cell. Using Cell for
the lookup would silently miss baseURL resolution when Cell differs
from ClusterSlug, falling back to the home-cell URL.

- Add clusterSlug field to cellGroup, populated from r.ClusterSlug
- Use cells[i].clusterSlug for slugToCluster lookup
- Test ResolvesBaseURLFromClusters now uses distinct Cell/ClusterSlug
  values to prove the lookup path
- All test RepoIndexEntry literals updated with ClusterSlug field

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The BFF uses r.cell with a URL template to resolve cell URLs. No other
CLI code path does a Cluster.Slug lookup — the experts resolver matches
by ClusterHost instead. Revert to using cell for the cluster catalog
lookup to mirror the BFF pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Soph added a commit that referenced this pull request Jul 5, 2026
resolveExpertsCellTarget was experts-named but nothing in it is
experts-specific: it maps any repo (ULID or owner/repo) to the entire-api
cell hosting it, exactly what every future repo-scoped or fan-out command
needs (code search is next, PR #1616). Rename the file and identifiers —
resolveRepoCellTarget, cellCoreClient, newCellCoreClient,
cellResolveTimeout — and keep experts as the documented example consumer.
Mechanical rename; no behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 89b4d13bd2b0
Soph added a commit that referenced this pull request Jul 5, 2026
The data plane has no server-side cross-cell aggregator: a query over all
of the caller's repos must be fanned out to each cell hosting them and
merged client-side (the entire.io BFF's code-search pattern). PR #1616
inlines that orchestration into search_cmd.go; this extracts the generic
layer so code search — and any later repo-set command — shares one
implementation:

- groupReposByCell: repo index → per-cell groups, deterministic order.
- resolveCellBaseURLs: cluster-catalog join on ClusterSlug↔Cluster.Slug.
  The catalog exposes no cell field, so joining the cell name against
  Slug (as #1616 currently does) only works when the two coincide.
- fanOutCells: parallel per-cell calls under a per-cell timeout, one
  shared auth.CellClientFactory (subject resolved once, one identity
  token per jurisdiction), partial failures isolated per result slot.
  Merge semantics stay with the command.

Also documents the three cell-routing shapes in CLAUDE.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 842e421e74a8
Soph added a commit that referenced this pull request Jul 5, 2026
resolveExpertsCellTarget was experts-named but nothing in it is
experts-specific: it maps any repo (ULID or owner/repo) to the entire-api
cell hosting it, exactly what every future repo-scoped or fan-out command
needs (code search is next, PR #1616). Rename the file and identifiers —
resolveRepoCellTarget, cellCoreClient, newCellCoreClient,
cellResolveTimeout — and keep experts as the documented example consumer.
Mechanical rename; no behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 89b4d13bd2b0
Soph added a commit that referenced this pull request Jul 5, 2026
The data plane has no server-side cross-cell aggregator: a query over all
of the caller's repos must be fanned out to each cell hosting them and
merged client-side (the entire.io BFF's code-search pattern). PR #1616
inlines that orchestration into search_cmd.go; this extracts the generic
layer so code search — and any later repo-set command — shares one
implementation:

- groupReposByCell: repo index → per-cell groups, deterministic order.
- resolveCellBaseURLs: cluster-catalog join on ClusterSlug↔Cluster.Slug.
  The catalog exposes no cell field, so joining the cell name against
  Slug (as #1616 currently does) only works when the two coincide.
- fanOutCells: parallel per-cell calls under a per-cell timeout, one
  shared auth.CellClientFactory (subject resolved once, one identity
  token per jurisdiction), partial failures isolated per result slot.
  Merge semantics stay with the command.

Also documents the three cell-routing shapes in CLAUDE.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 842e421e74a8
Soph added a commit that referenced this pull request Jul 6, 2026
resolveExpertsCellTarget was experts-named but nothing in it is
experts-specific: it maps any repo (ULID or owner/repo) to the entire-api
cell hosting it, exactly what every future repo-scoped or fan-out command
needs (code search is next, PR #1616). Rename the file and identifiers —
resolveRepoCellTarget, cellCoreClient, newCellCoreClient,
cellResolveTimeout — and keep experts as the documented example consumer.
Mechanical rename; no behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 89b4d13bd2b0
Soph added a commit that referenced this pull request Jul 6, 2026
The data plane has no server-side cross-cell aggregator: a query over all
of the caller's repos must be fanned out to each cell hosting them and
merged client-side (the entire.io BFF's code-search pattern). PR #1616
inlines that orchestration into search_cmd.go; this extracts the generic
layer so code search — and any later repo-set command — shares one
implementation:

- groupReposByCell: repo index → per-cell groups, deterministic order.
- resolveCellBaseURLs: cluster-catalog join on ClusterSlug↔Cluster.Slug.
  The catalog exposes no cell field, so joining the cell name against
  Slug (as #1616 currently does) only works when the two coincide.
- fanOutCells: parallel per-cell calls under a per-cell timeout, one
  shared auth.CellClientFactory (subject resolved once, one identity
  token per jurisdiction), partial failures isolated per result slot.
  Merge semantics stay with the command.

Also documents the three cell-routing shapes in CLAUDE.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 842e421e74a8
@Soph

Soph commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Heads-up: the generic multi-cell plumbing this PR inlines in search_cmd.go now exists as a shared layer in #1641 (green, awaiting review), extracted so code search and future repo-set commands share one implementation:

  • groupReposByCell / resolveCellBaseURLs (cell_fanout.go) — repo index → per-cell groups, then a cluster-catalog join keyed on ClusterSlugCluster.Slug. On the join-key back-and-forth in this PR's history: the BFF's r.cell usage works because the BFF templates the URL from the cell name (https://<cell>.api…); this PR instead looks up the catalog, and the catalog exposes no cell field — its key is Slug. So slugToCluster[cells[i].cell] only matches while slug and cell name coincide, and silently degrades to jurisdiction routing (plus a hand-rolled catalog re-fetch inside auth per cell) when they don't. The earlier fix: use ClusterSlug commit had it right; cli: cell-routing foundation — client factory, generic repo→cell resolver, multi-cell fan-out #1641 pins that with a slug≠cell test.
  • fanOutCells — parallel per-cell dispatch, per-cell timeout, partial failures isolated per result slot. Builds every client from one auth.CellClientFactory, which resolves the login subject once and mints one identity token per jurisdiction (tokens are per-jurisdiction, not per-cell). Calling auth.NewEntireAPICellClient inside the per-cell loop re-runs discovery + login refresh + RFC 8693 exchange once per cell.
  • resolveRepoCellTarget (cell_target.go, generalized from the experts resolver) — the single-cell path for the --repo case.

Suggested shape for a rebase onto #1641: the codesearch client package, merge/sort/dedup semantics, and text/JSON rendering carry over as-is; the inline searchAllCells/searchCell/groupReposByCell orchestration collapses onto the shared helpers. The three routing shapes are documented in CLAUDE.md under "Entire-API Cell Routing". Happy to help with the rebase once #1641 lands.

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.

4 participants