Skip to content

fix(explore): keep test edges out of the Relationships section - #1891

Open
MohammadBnei wants to merge 2 commits into
colbymchenry:mainfrom
MohammadBnei:explore-relationship-test-filter
Open

MohammadBnei wants to merge 2 commits into
colbymchenry:mainfrom
MohammadBnei:explore-relationship-test-filter

Conversation

@MohammadBnei

@MohammadBnei MohammadBnei commented Sep 16, 2026 •

Copy link
Copy Markdown

The problem

On a well-covered symbol, the Relationships section of codegraph_explore is mostly the symbol's own test suite. Asking a real question about a Go store method with a dozen callers:

codegraph explore "What calls sessions.ReserveSlot and where is the live-pod cap maxLive enforced?"
**calls:**
- TestSaveAgentSessionId_StoresTheAgentIdNotTheSessionId → ReserveSlot
- TestReserveSlot_ConcurrentCallersCannotExceedTheCap → ReserveSlot
- killPod → ReserveSlot
- TestDismissStanding_LetsAnExplicitRunGoThrough → ReserveSlot
- TestReserveSlot_ResetsPerPodStateForTheNewPod → ReserveSlot
- TestReserveSlot_ResolvesDecisionsTheDeadPodLeftBehind → ReserveSlot
- TestReserveSlot_CountsAgainstTheCapImmediately → ReserveSlot
- TestSaveAgentSessionID_StaleLeaseCannotClobberTheNewPod → ReserveSlot
- TestArchive_ReleasesTheSlotAndBlocksFurtherReservation → ReserveSlot
- TestSweepQueries_SelectOnlyTheSessionsTheyDescribe → ReserveSlot
- ... and 100 more

Nine of the ten lines are the suite. maxEdgesPerRelationshipKind is small by design, so the production callers — the answer to the question — sit inside "... and 100 more". The section named the test suite instead of the call graph.

The change

Test files are already hard-excluded from the source section for exactly this reason (budget crowding). This applies the same rule one section later, to the edge list.

  • Omitted → follows the query, matching what the source-file filter already does, so "which tests cover X" still answers with tests.
  • includeTests: true (MCP) / --tests (CLI) → forces them in.
  • includeTests: false / --no-tests → forces them out, waiver included.

The waiver regex moved into query-utils as queryIsAboutTests and is now shared with the source-file filter, so the two cannot drift apart on what a test-y query is.

Same query after:

**calls:**
- PromptSession → liveStateOf
- WaitForSessionState → liveStateOf
- DeriveLiveState → IsPodPhaseLive
- DeriveLiveState → humanOriginatedEntry
- CountByRepoLiveState → DeriveLiveState
- enforceStartupStall → ListStartupStalledIDs
...

Blast radius is deliberately untouched. Its tests: line names the covering files rather than flooding a cap — that is the half of this information that was already working, and it stays the way to answer "what covers this?".

Notes for review

  • The new test fixture writes 520 filler files because Relationships is gated off below 500 indexed files. Runs in ~5s.
  • I did not touch src/mcp/server-instructions.ts. The house rule says to edit it when tool behavior changes, but the new parameter's own schema description is agent-facing and the instructions never mention Relationships or test filtering — happy to add a line there if you'd rather have it.
  • CHANGELOG entry added under [Unreleased] → ### Fixes → #### MCP / indexing.

Testing

  • __tests__/explore-relationship-test-filter.test.ts — 5 cases: default drops the suite and keeps the production caller, includeTests: true restores it, a test-y query restores it unasked, includeTests: false overrides that waiver, blast radius still names the covering file.
  • npx tsc --noEmit clean.
  • Full suite on this branch vs. main on the same machine: identical set of 12 failing files (watcher / sync / git-index / worktree — timing-sensitive, unrelated to this change), 39 vs 41 failed tests across two runs, i.e. flake. Everything under explore-*, cli-*, is-test-file, deprioritize-config and context-ranking passes on this branch.

MohammadBnei and others added 2 commits September 16, 2026 21:22
On a well-covered symbol the relationship list was almost entirely its own
suite. A Go store method with a dozen callers rendered as ten
`TestReserveSlot_* -> ReserveSlot` lines plus "... and 100 more", so the one
production caller the agent asked about never made the per-kind cap — the
section named the test suite instead of the call graph.

Test files are already hard-excluded from the source section for the same
reason. Apply the rule one section later, with the same waiver: a query that
is itself about tests still gets them. `includeTests` (MCP) and
`--tests` / `--no-tests` (CLI) override in both directions.

The waiver regex moves to query-utils as `queryIsAboutTests` so the source
filter and the edge filter cannot drift apart on what a test-y query is.

Blast radius is deliberately untouched: its `tests:` line names the covering
files rather than flooding a cap, which is the half of this that worked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review of the previous commit turned up three ways the filter overreached,
each reproduced as an output diff against main:

- It used `isTestFile`, the WIDE predicate — examples, samples, benchmarks
  and fixtures went with the test suites. Those still render in the Source
  Code section, so the response contradicted itself, and no phrasing of the
  query could win them back (`queryIsAboutTests` doesn't match "which
  examples call X"). Now `isTestPath`, which is the "literally a test suite"
  reading the docstring points at for exactly this case.
- A test file NAMED BY PATH was pinned into the source section and erased
  from Relationships. `extractQueryPaths` strips the path span out of
  `matchQuery` before the waiver runs, so the "test" inside
  `src/store.test.ts` was invisible to it. Pinned files are now exempt, as
  they already are in the source-file filter.
- With no non-test callers the section vanished outright. The source filter
  stands down in that case ("tests are the only signal for this area") and
  the blast radius does not cover for it — that section is roots-only and
  capped. This one now stands down too, unless `includeTests: false` asked
  for the empty section explicitly.

Each has a test that fails on the previous commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MohammadBnei

Copy link
Copy Markdown
Author

Follow-up commit — a review pass found three ways the first cut overreached. All three are reproduced output diffs against main, and each has a test that fails on the previous commit.

  1. Wrong predicate. It used isTestFile, which is the wide reading — examples, samples, benchmarks and fixtures went with the test suites. Those still render in the Source Code section, so the response contradicted itself, and no phrasing of the query could win them back (queryIsAboutTests doesn't match "which examples call X"). Now isTestPath, which is the "literally a test suite" reading the docstring points at for exactly this case.

  2. A pinned test file was erased. extractQueryPaths strips the path span out of matchQuery before the waiver runs, so the "test" inside src/store.test.ts was invisible to it: the file was pinned into the source section and dropped from Relationships in the same response. Pinned files are now exempt, matching the source-file filter's own exemption.

  3. The section could vanish outright. With no non-test callers there was nothing left to render. The source filter stands down in that case ("tests are the only signal for this area") and the blast radius does not cover for it — that section is roots-only, capped at 5, and MEANINGFUL-kinds-only, so a non-root symbol whose callers are all tests lost the information entirely. It now stands down too. An explicit includeTests: false still means false: that caller asked for the empty section.

Test file is up to 9 cases; the three new ones fail on the previous commit and pass on this one. tsc --noEmit clean, sibling explore suites + is-test-file + deprioritize-config all pass (65 tests).

This branch has not been deployed

No deployments
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.

1 participant