fix(find): exclude file/package Symbol nodes; stop rendering path-FQN as 'java'#411
Merged
Merged
Conversation
… as 'java'
`jrag find --fqn-contains <X>` surfaced structural file/package Symbol nodes
(kind='file'/'package') whose fqn is a filesystem path, so a substring like
'Assign' matched the filename (.../DevAssignmentController.java) and leaked a
file row. The row then rendered as just `java` (the file extension), because
find_v2's SELECT omits s.name and display_name fell through to
simple_name(fqn) = fqn.rsplit('.', 1)[-1].
Two fixes:
- find_v2 (mcp_v2.py): exclude kind in ('file','package') from the symbol
branch, mirroring search_lexical.py. Safe unconditionally: DeclarationSymbolKind
excludes those values, so no filter ever requests them.
- simple_name (jrag_envelope.py): a path-shaped fqn (separator present, no space
— routes carry a space) returns the basename instead of the extension.
Defense-in-depth: layer 1 stops the noise at the query; layer 2 ensures any
file/package node reaching the renderer (query mode, traversal) shows its
filename, not 'java'.
Co-Authored-By: Claude <noreply@anthropic.com>
Address code-review feedback on PR #411: - simple_name: drop the `"\\" in fqn` disjunct. The indexer POSIX-normalizes file fqns via .as_posix() (build_ast_graph.py), so backslashes never occur — and on a POSIX host Path.name wouldn't split on `\` anyway. The branch was inert complexity that implied Windows support that doesn't work. Docstring now states the .as_posix() guarantee as the reason '/' is the only check. - test_jrag_render: add test_render_listing_file_node_shows_basename_not_extension to observe the user-visible symptom (file node rendering as 'java') through the render path end-to-end, not just simple_name in isolation. - test_jrag_render: use a literal for the route-fqn assertion (was computed via the same .rsplit the impl uses) and add a package-fqn boundary case. - test_mcp_v2: comment that the package guard in the Assign test is vacuous (CONTAINS is case-sensitive; package fqns are lowercase) and is proven instead by the empty-filter test. Co-Authored-By: Claude <noreply@anthropic.com>
The macos-15-intel (graph-only) CI leg intermittently failed 11 tests across find/describe/neighbors/resolve with `'<' not supported between instances of 'int' and 'MagicMock'`. Flaky: passed on one commit, failed on the next, and failed on master too — my review-fix commit reshuffled xdist --dist loadfile worker assignment and exposed it. Root cause (two layers): 1. The leak — tests/package/test_java_codebase_rag_cli.py drives server.main() with resolve_operator_config mocked to a MagicMock cfg. server.main() calls mcp_v2.set_absence_config(cfg) + resolve_service.set_absence_config(cfg), caching the MagicMock in the module-global _absence_config singleton. monkeypatch does NOT revert it (set via a function call, not an attribute patch), so it leaks across tests in the same worker. On graph-only the @needs_vectors search tests skip, shifting file->worker assignment so the polluter and the absence/resolve victims collide. 2. The crash — get_vocabulary_index (absence_vocab.py) passed cfg.absence_ngram_q UNCOERCED into VocabularyIndex.build -> _qgrams -> `len(text) < q`. With a MagicMock cfg that's `int < MagicMock`, and it fires BEFORE diagnose()'s try/except, so it escapes to the tool's outer handler and surfaces as success=False (hiding the real traceback). Fix (defense-in-depth): - tests/conftest.py: autouse fixture resetting mcp_v2._absence_config and resolve_service._absence_config to None around every test (stops the leak). - absence_vocab.py: coerce q to int (int(getattr(cfg, "absence_ngram_q", 3) or 3)), mirroring the existing int(getattr(cfg, "absence_candidate_count", 5)) pattern in absence_diagnosis.py — stops the crash even if a leak recurs or a YAML string slips through. - Regression: a two-part test pinning the cross-test isolation contract (fails if the autouse fixture is removed). Pre-existing, unrelated to this PR's find_v2/simple_name changes — same flaky graph-only area as b7d9b14. Verified locally: with the fixture the polluter-> victim sequence passes; without it the regression guard fails (sees the leaked MagicMock). Co-Authored-By: Claude <noreply@anthropic.com>
HumanBean17
added a commit
that referenced
this pull request
Jul 10, 2026
- CLI: distinguish "fuzzy matched but a post-filter removed all hits" from "genuinely no match" in the empty-result message (previously blamed the query, misdirecting agents). Track identifier_matched pre-post-filter. - CLI: map internal 'contains' mode to user-facing 'substring' in the warning; note case-sensitivity in the --fuzzy help. - backend: guard empty needle in fuzzy modes (STARTS WITH '' / CONTAINS '' match every string) so the method is safe-by-construction. - tests: add post-filter-empty + scope-forwarding CLI tests; add a vacuous-pass guard on the prefix #411-exclusion test; tighten assertions (status ok, agent_next_actions, all three mode words); drop a redundant import. Co-Authored-By: Claude <noreply@anthropic.com>
HumanBean17
added a commit
that referenced
this pull request
Jul 10, 2026
…413) * feat(find): implement --fuzzy exact->prefix->contains fallback (#375) find_by_name_or_fqn gains a keyword-only `mode` param (exact|prefix|contains, default exact -> back-compat). `jrag find --fuzzy` runs exact -> prefix (STARTS WITH) -> contains (CONTAINS) on name/FQN when the exact match is empty. Fuzzy modes exclude file/package Symbol nodes to avoid filename leaks (mirrors #411). Co-Authored-By: Claude <noreply@anthropic.com> * find(fuzzy): address review — post-filter hint, scope test, hardening - CLI: distinguish "fuzzy matched but a post-filter removed all hits" from "genuinely no match" in the empty-result message (previously blamed the query, misdirecting agents). Track identifier_matched pre-post-filter. - CLI: map internal 'contains' mode to user-facing 'substring' in the warning; note case-sensitivity in the --fuzzy help. - backend: guard empty needle in fuzzy modes (STARTS WITH '' / CONTAINS '' match every string) so the method is safe-by-construction. - tests: add post-filter-empty + scope-forwarding CLI tests; add a vacuous-pass guard on the prefix #411-exclusion test; tighten assertions (status ok, agent_next_actions, all three mode words); drop a redundant import. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
While testing #405 (absence diagnosis) on
tests/bank-chat-system/,jrag find --fqn-contains 'Assign'returned 18 garbage lines:(The plain
jrag find 'Assign'→0 symbol+next: jrag find --fqn-contains Assignhint is #405 working as designed — exact match by design atladybug_queries.py:1001; the hint is the new absence nudge.)Root cause (pre-existing, unrelated to #405)
Two defects combine in the
--fqn-containspath:find_v2leaks file/package nodes._symbol_where_from_filterhas no structural-kind guard, so:Symbolnodes withkind='file'match. A file node'sfqnis the filesystem path (chat-assign/.../DevAssignmentController.java), ands.fqn CONTAINS 'Assign'matches the PascalCase filename.search_lexical.py:209-210excludes these on purpose —find_v2was the gap.find_v2's SELECT omitss.name, soNodeRef.nameisNone;display_namefalls through tosimple_name(fqn)=fqn.rsplit('.', 1)[-1]=java(the extension).Verified against the live index — file nodes carry a good
namebutsimple_name(path-FQN)returns the extension:Fix (two layers)
mcp_v2.pyfind_v2): excludekind in ('file','package')from the symbol branch, mirroringsearch_lexical.py. Safe unconditionally —DeclarationSymbolKind(= class/interface/enum/record/annotation/method/constructor) excludes those values, so no filter can ever request them.jrag_envelope.pysimple_name): a path-shaped fqn (separator present, no space — route fqns carry a space) returns the basename instead of the extension.Layer 1 stops the noise at the query; layer 2 is defense-in-depth so any file/package node reaching the renderer (query mode, traversal) shows its filename, not
java.Verification
Reproduced before / after on
tests/bank-chat-system/:java role=OTHER symbol_kind=filelines polluting the top.DevAssignmentController,AssignConfiguration,AssignProperties, …).Tests
test_find_symbol_excludes_file_and_package_nodes(reproduces the chat-assign case via thebank-chat-systemfixture) + strengthened the existing empty-filter test to assert file/package exclusion.test_simple_name_file_path_fqn_returns_basename_not_extension(incl. route-fqn non-regression).Subset run (mcp + package + analysis + absence + graph + index): all green except 5 pre-existing/environmental failures unrelated to this change:
test_installer.py::TestPR4IndexProgress— fail onmastertoo (FileExistsError, test-isolation issue with leftover index dirs).test_search_typo_has_absence_diagnosis— order-dependent in the big suite (passes in isolation and on master-in-file); usessearch_v2+ vocab index, which this change does not touch.🤖 Generated with Claude Code