Skip to content

feat(registry): scope cross_repo_search to a subset of the registry - #915

Closed
mohammad-rj wants to merge 1 commit into
tirth8205:stagingfrom
mohammad-rj:session/crg-repos-filter
Closed

mohammad-rj wants to merge 1 commit into
tirth8205:stagingfrom
mohammad-rj:session/crg-repos-filter

Conversation

@mohammad-rj

@mohammad-rj mohammad-rj commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor

Closes #914

Problem

cross_repo_search fans out over the whole registry with no way to narrow it. On a registry
of 10 repositories spanning four unrelated products, one query returned 23 results across all
10, with the two functions it was actually about ranked below hits from products that could
not answer it. The rank-interleave from #619 and _MAX_CROSS_REPO_RESULTS bound the size of
that response, not its relevance. #311 makes this the normal case rather than an edge case:
one IDE-fixed cwd over many sibling repositories, all registered.

Change

  • Optional repos on cross_repo_search_func and cross_repo_search_tool. Omitted or empty
    keeps current behaviour exactly.
  • Names match an entry's alias first, then its folder name; only those repositories are opened.
  • Selection preserves registry order, so the merge tie-breaker cannot be changed by reordering
    the argument.
  • Names matching no entry come back in unknown rather than dropped, and a selection matching
    nothing searches nothing rather than the full registry.

Same query scoped to one product's three repositories: 5 results across 3, both wanted
functions in the top three.

Tests

Nine new tests: four on the selection helper, four on the func end to end, one on the MCP tool
forwarding the argument. Full suite passes; ruff and mypy clean. Docs updated in README.md,
docs/COMMANDS.md and the tool docstrings.

@mohammad-rj
mohammad-rj force-pushed the session/crg-repos-filter branch from 536fd84 to 08c8c02 Compare August 24, 2026 02:53
@github-actions

Copy link
Copy Markdown

code-review-graph review

Overall risk: 0.40 (MEDIUM) — 16 changed function(s)/class(es), 29 affected flow(s), 2 test gap(s)

Risk-scored changes

Risk Level Symbol Location Tested
0.40 medium code_review_graph/main.py::cross_repo_search_tool code_review_graph/main.py:1035 yes
0.35 low code_review_graph/tools/registry_tools.py::_select_repos code_review_graph/tools/registry_tools.py:54 yes
0.30 low code_review_graph/tools/registry_tools.py::cross_repo_search_func code_review_graph/tools/registry_tools.py:79 yes
0.25 low tests/test_registry.py::TestCrossRepoSearch.test_cross_repo_search_tool_forwards_repo_selection tests/test_registry.py:434 (test)
0.20 low tests/test_registry.py::TestCrossRepoSearch._three_repo_registry tests/test_registry.py:303 yes
0.10 low tests/test_registry.py::TestSelectRepos tests/test_registry.py:449 no
0.10 low tests/test_registry.py::TestSelectRepos.test_select_repos_matches_alias_then_folder_name tests/test_registry.py:458 (test)
0.10 low tests/test_registry.py::TestSelectRepos.test_select_repos_keeps_registry_order tests/test_registry.py:464 (test)
0.10 low tests/test_registry.py::TestSelectRepos.test_select_repos_reports_unknown_names tests/test_registry.py:469 (test)
0.10 low tests/test_registry.py::TestSelectRepos.test_select_repos_deduplicates_alias_and_folder_of_one_repo tests/test_registry.py:475 (test)

Affected execution flows

  • query_graph_tool — criticality 0.36, 2 node(s) across 1 file(s)
  • get_surprising_connections_tool — criticality 0.36, 2 node(s) across 1 file(s)
  • detect_changes_tool — criticality 0.36, 2 node(s) across 1 file(s)
  • main — criticality 0.32, 3 node(s) across 1 file(s)
  • get_impact_radius_tool — criticality 0.24, 2 node(s) across 1 file(s)
  • ...and 24 more affected flow(s)

Test gaps

  • tests/test_registry.py::TestCrossRepoSearch (tests/test_registry.py:224)
  • tests/test_registry.py::TestSelectRepos (tests/test_registry.py:449)

Token savings: this graph-backed report used ~28,168 fewer tokens (~76%) than reading every changed file in full (estimated, chars/4 approximation).


Powered by code-review-graph — local-first analysis; no code leaves the CI runner.

@tirth8205

Copy link
Copy Markdown
Owner

Good feature and the right shape — a pure filter over the already-trusted registry, backward compatible to the byte (no argument, None and [] all return the identical unscoped response), and I could not get it to reach anything outside the registry: absolute paths, ../ and a path to a genuinely registered repo all land in unknown with repos_searched: [] and no sqlite file opened. _select_repos can only ever return a subset of list_repos(), which is the property that matters.

One thing blocks it, and it goes straight at what this project is for.

unknown and the summary are the only unbounded fields in the tool. results goes through _bounded(); unknown = [name for name in names if name not in by_name] echoes every unmatched name verbatim, and the summary interpolates the raw list a second time (f"No registered repository matches {repos}."). Measured with this repo's own counter against a six-repo registry:

unscoped baseline                    1,068 tokens   truncated=False
documented worst (limit=10**6)       1,068 tokens   truncated=False
repos=["z"*200]*500                 50,917 tokens   truncated=False   0 results
repos=["z"*500]*1000               251,792 tokens   truncated=False   0 results

BUDGETS["cross_repo_search_tool"] sets worst_max: 30_000 and ABSOLUTE_MAX_TOKENS = 50_000 is documented as the ceiling no tool may exceed with every knob maxed. The last row is 8× the tool's own budget and 5× the absolute backstop, returns nothing, and reports truncated: False — so the response actively claims nothing was cut. That also makes two sentences in docs/COMMANDS.md:319 false for this tool.

The budget test can't catch it, which is worth knowing: tests/conftest.py points CRG_HOME at an empty temp directory, so the tool short-circuits at if not all_repos: before any of the new code runs. Adding "repos": ["z"*200]*500 to that tool's worst kwargs still passes. Please _bounded() the unknown list with an unknown_total, put a count rather than {repos} in the summary, and register the repos knob against a fixture that has at least one repo registered.

Second, colliding names silently search a subset. by_name is one flat namespace mixing aliases and folder names, built with setdefault, and wanted is a set of indices — so a name resolves to exactly one entry and the earliest registry position wins:

  • /repos/android (alias droid) and /repos/nested/android (alias tv): repos=["android"] searches only droid, with unknown: [].
  • [{"path":"/a/web"}, {"path":"/b/frontend","alias":"web"}]: repos=["web"] selects /a/web — an explicit alias loses to another entry's incidental folder name.

That is the sibling-repo topology #914 and #311 use as the motivating example (~/work/acme/api and ~/work/beta/api), and it is the exact failure mode the unknown field exists to prevent: fewer repositories searched than asked for, with no signal. Map name → list of indices and select all matches, and resolve every alias before any folder name so an explicit alias always wins. An ambiguous field would be even better.

Smaller ones, worth folding into the same push:

  • A dropped name never reaches summary. repos=["billing","nope"] gives status: "ok" and Found 1 result(s) across 1 repo(s), with nope only in the JSON — while the all-unknown branch does put it in the summary. CLAUDE.md trains agents to read summary first, so the two branches should be consistent.
  • The echoed names skip _sanitize_name. A name containing NUL, BEL and newlines comes back intact in both summary and unknown, while every other name in the same response is sanitized. Low practical risk since it reflects the caller's own input, but its 256-character cap is also the natural cap for the first finding.
  • repos is exact-case and rejects paths, while registry.resolve_repo() and unregister() both take alias-or-path. The strictness is the safe direction — just say so in the MCP docstring and docs/COMMANDS.md.
  • Six other docs still say "across all registered repositories" (docs/USAGE.md:130, docs/FEATURES.md:54, and the four translated READMEs).

Gates are green — 2994 passed, ruff and mypy clean, 13/13 checks — so this is about bounds and resolution semantics, not correctness of the plumbing.

cross_repo_search always fanned out over the whole registry, and a registry
that spans unrelated products let every repository contribute its best local
rank to the top of the merged list. The existing bounds cap the size of that
response but not its relevance, so a query aimed at one product still paid for
all of them, and the caller had to read and discard the rest.

Adds an optional `repos` argument, on the func and on the MCP tool. Omitted or
empty keeps the current behaviour exactly, so no existing caller changes. Given
names, entries are matched by alias first and then by the folder name of their
path, and only those repositories are opened. Selection preserves registry
order, so the caller cannot change the rank-interleave tie-breaker by reordering
the argument, and the merge from tirth8205#619 is untouched.

Names matching no registry entry come back in `unknown` instead of being
dropped: a silently skipped repository is indistinguishable from one that had
no matches, which is the same reason the existing missing-db skip is hard to
diagnose. A selection matching nothing searches nothing rather than falling
back to the full registry.

Closes tirth8205#914

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mohammad-rj
mohammad-rj force-pushed the session/crg-repos-filter branch from 08c8c02 to 18e5461 Compare August 26, 2026 19:50
@mohammad-rj

Copy link
Copy Markdown
Contributor Author

All of it is addressed in the amended commit.

Bounds. unknown and ambiguous both go through _bounded() now (cap 20), with unknown_total / ambiguous_total and unknown_truncated / ambiguous_truncated, and every echoed name passes through _sanitize_name first, so each entry is capped at 256 characters like every other name in a response. Neither summary branch interpolates the caller's list any more; both carry counts instead. The last row of your table measures 1,087 tokens where it measured 251,792.

The budget test. The entry now carries needs_registry, which registers the fixture repo into the temporary CRG_HOME before the call, plus a FLOOD_NAMES knob resolving to one name that matches and 500 that do not. The tool no longer short-circuits on the empty registry, so the ceiling binds against exactly the field you found.

Resolution. _select_repos maps a name to a list of indices and selects every match; a name matching more than one entry is reported in ambiguous. Aliases are resolved across the whole registry before any folder name, and only an explicit alias enters the alias namespace, so an entry registered without one cannot outrank another entry's deliberate alias. Both of your examples are tests now: two sibling checkouts sharing api search both and report ambiguous: ["api"]; [{"path":"/a/web"}, {"path":"/b/frontend","alias":"web"}] with repos=["web"] selects /b/frontend.

Docs. The MCP docstring and docs/COMMANDS.md state that matching is exact, case-sensitive and rejects paths, and describe the new fields. docs/USAGE.md, docs/FEATURES.md and the four translated READMEs no longer say "across all registered repositories".

Verification: full suite 3,003 passed, coverage 85.06%, ruff and mypy clean.

@tirth8205

Copy link
Copy Markdown
Owner

Integrated on integration/token-efficiency-hardening as dae2711. Cross-repository search can now select registered aliases or folder names without unrelated repositories consuming the compact result budget. The selection retains alias precedence, registry order and ambiguous matches, with bounded diagnostics; the registry and token-budget regressions passed.

@tirth8205

Copy link
Copy Markdown
Owner

Merged into staging through the integration PR #988 (merge commit 3030ca8). Your change is carried by commit dae2711, which keeps you as the author (Mohammad Rahimi Jafari). It will be promoted staging → testing → main and released from there. Thank you for the contribution! Closing this PR since its content is now on staging.

@tirth8205 tirth8205 closed this Sep 15, 2026
Bakul2006 pushed a commit to Bakul2006/code-review-graph that referenced this pull request Sep 15, 2026
…o a subset of the registry

Port the reviewed token-efficiency implementation with targeted regression coverage.

Source-PR: tirth8205#915
Maintainer corrections and scope extraction applied where needed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: scope cross_repo_search to a subset of the registry

2 participants