Skip to content

fix(explore): names the query spells that the index never saw are said, not glossed over - #2789

Merged
colbymchenry merged 1 commit into
mainfrom
claude/issue-2462-build-skip-explore
Oct 22, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/issue-2462-build-skip-explore

Conversation

@colbymchenry

@colbymchenry colbymchenry commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Summary

#2462 has two halves. This PR fixes the second and records where the first stands.

1. The default build skip. On main a root build/ folder is still skipped by default. #2681, #2685 and #2687 re-opened only build folders below src/, for hand-written TS/JS-family, Go, C# and Rust files. The documented opt-in works: !/build/ in the project's .gitignore indexes it, and codegraph sync picks the opt-in up. I verified that on main and on v1.6.2, with the negation in .gitignore and in .git/info/exclude. I could not reproduce the report's "Already up to date". The reporter's repo has since committed exactly that opt-in, with a comment naming CodeGraph. Widening the default to a root build/ is a separate decision about the skip list: a root build/ is many toolchains' output folder, e.g. a CRA production bundle. This PR leaves the skip list alone.

2. Explore's silence on a miss. When a query names symbols the index has never seen, the answer now says so first, success-shaped, and points at what decides it, never at Read:

**Exploration: ReleaseNotesBuilder Program Main ChangelogParser PersistentData Entry AddFeatures**

Found 4 symbols across 2 files.

> **Not in the index:** `ReleaseNotesBuilder`, `ChangelogParser`, `PersistentData`, `AddFeatures`. No indexed symbol, file, signature or comment uses these names, so the code below matches the query's other words, not them. If they are defined in this project, the files defining them are not indexed: `codegraph status` shows what is. If they live in a folder skipped by default, like `build/` or `dist/`, the user can include that folder by adding a re-include such as `!/build/` to `.gitignore`.

That is newrelic/newrelic-dotnet-agent at 418a0f2f, the reporter's repo, with its committed !/build/ removed and the index synced. On main, the same query returns src/Agent/NewRelic/Profiler/VersionFileGenerator/Program.cs's Main and the C++ Sicily/Parser.cpp, with nothing to say four of the names matched nothing. That is the report, line for line.

Root cause

Explore answers whatever the query's words match. A name with no match simply contributes nothing, and the answer is built from the remaining words. The #2026 miss diagnostics ran only when nothing at all matched.

Fix

  • Which tokens are candidates. In explore's named-symbol seeding, a token is a candidate if it is written like a code name and resolves to no node at all, not even through the camel-infix field-name fallback. Code-name shapes are a camelCase hump, snake_case, or a qualified a.b / a::b.
  • Which candidates get named. getUnseenNames (new, in queries.ts) keeps the names the index has never seen: no node called that in any case, and no name, qualified name (a file's path), signature or docstring word that starts with it. It's bounded like getExploreMissDiagnostics: two index probes per name, at most 16 names, never a source scan.
  • Qualified names. A qualified name is judged by its member. Express's app.handle = function handle(…) is indexed as handle, so a receiver the index doesn't record never makes a name missing. This was a real false positive on express's prose probe until fixed.
  • Single words are never named. Program, Main, How and Parser can be English, and a question's ordinary words must never be reported as missing code.
  • Placement. The note sits under the summary line, ahead of the source. Re-including a folder skipped by default is worded as the user's choice, since indexing scope is the user's call (AGENTS.md). The total-miss answer ends with the same note, worded without "the code below".
  • Shared tokenizer. The seeding's query tokenizer moved into exploreQueryTokens, unchanged, so both paths read a query the same way.

Validation

Tests. New __tests__/explore-unindexed-names.test.ts, 8 tests; 6 fail on main. It uses the report's layout (a root build/ with the tool projects, an unrelated Program.cs, a C++ Parser) and covers:

  • the note's content and placement, success-shaped, with no Read wording;
  • single words and names the index has are never named, and a prose question gets no note;
  • a name seen only in a docstring or a folder name counts as seen;
  • qualified names are judged by their member;
  • the total-miss answer;
  • the note goes away once !/build/ is added and the project synced.

All 38 explore suites pass, plus the tool-allowlist and dynamic-boundary suites (466 tests).

Ordinary queries. 996 on express, flask, excalidraw, django, airflow and go-control-plane: the 50 longest functions by name, their files by path, caller/callee pairs, call chains, random names and flow questions. All are byte-identical to main.

Miss probes. 120 queries, 20 per repo: real pair queries with an invented code-style name appended, invented names alone, and prose that mentions one.

  • Main never notes anything.
  • The fix names every invented name in all 120, and names no real name in any.
  • On a full response the note costs about 5 source lines (623 of 44,213 across the 120); in 28 of them nothing else changes.

Full suite on bb72ca6 (rebased onto 0953084 after main moved), with the native kernel built and CODEGRAPH_KERNEL_EXPECT=1: 716 files (710 passed, 6 skipped), 8,572 tests: 8,490 passed, 0 failed, 82 skipped. The pushed commit differs from bb72ca6 only in the note's wording, so including a skipped folder reads as the user's choice, and the test lines that pin it (git diff --stat bb72ca63 HEAD: 2 files, 7 insertions, 5 deletions). The new test file, the 38 explore suites and the tool-allowlist and dynamic-boundary suites were re-run on it: 466 passed.

Not in this PR

  • The root build/ default (part 1 above), left for a separate decision.
  • The report's third suggestion, sync missing a newly un-ignored path, did not reproduce on main or on v1.6.2 for this layout.
  • No agent A/B: the change is deterministic, and the probes measure exactly what the agent receives.

Fixes #2462

🤖 Generated with Claude Code

@colbymchenry
colbymchenry force-pushed the claude/issue-2462-build-skip-explore branch from 4ddd41b to bb72ca6 Compare October 22, 2026 07:27
…d, not glossed over

Root cause: explore answers whatever the query's words match. When a name
the query spelled is not in the index at all, the answer is built from the
other words and says nothing about the missing ones. In the report, the
tool projects under a .NET repo's root build/ folder are skipped by
default. Asking for ReleaseNotesBuilder, ChangelogParser, PersistentData
and AddFeatures returned an unrelated Program.Main and a C++ Parser, and
the agent read the wrong code. The miss diagnostics (issue #2026) ran only
when nothing at all matched.

Fix:
- Query tokens written like code names (a camelCase hump, snake_case, or a
  qualified name) that resolve to no node at all are checked with a new
  bounded probe, getUnseenNames. It reports names the index has never seen:
  no node called that in any case, and no name, qualified name or path,
  signature or docstring word starting with it.
- A qualified name is judged by its member. Express's app.handle is indexed
  as handle, so the receiver alone never makes a name missing.
- Names that pass are listed in a note right under the summary line,
  success-shaped. The note points to codegraph status and never to Read.
  It names the .gitignore re-include for a default-skipped folder like
  build/ as the user's choice, since indexing scope is the user's call.
- A single word (Program, Main, How) is never named: it may be English.
- The total-miss answer gains the same note.
- The seeding's query tokenizer moved into one shared function, unchanged.

The skip itself is unchanged here. On main a root build/ folder is still
skipped by default: PRs 2681 and 2687 re-opened build folders below src/
only. The documented opt-in, !/build/ in .gitignore, indexes it, and sync
picks the opt-in up, verified on main and on 1.6.2.

Validation:
- New explore-unindexed-names.test.ts, 8 tests: 6 fail on main.
- All 38 explore suites pass, plus the tool-allowlist and dynamic-boundary
  suites (466 tests).
- The reporter's repo, newrelic/newrelic-dotnet-agent 418a0f2f, now commits
  the !/build/ workaround. With that line removed and the index synced,
  main answers the reporter's query with VersionFileGenerator/Program.cs
  Main and Sicily/Parser.cpp, saying nothing. The fix adds the note naming
  exactly the four missing names.
- Ordinary queries: 996 on express, flask, excalidraw, django, airflow and
  go-control-plane, all byte-identical to main.
- Miss probes: 120 queries mixing real names with invented ones, or only
  invented ones. Main never notes anything. The fix names every invented
  name in all 120 and no real name. The note costs about 5 source lines
  on a full response.

Fixes #2462

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Default "build" skip hides tracked first-party source, and explore returns unrelated matches instead of saying so

1 participant