Skip to content

fix(find): exclude file/package Symbol nodes; stop rendering path-FQN as 'java'#411

Merged
HumanBean17 merged 3 commits into
masterfrom
fix/find-exclude-file-package-nodes
Jul 10, 2026
Merged

fix(find): exclude file/package Symbol nodes; stop rendering path-FQN as 'java'#411
HumanBean17 merged 3 commits into
masterfrom
fix/find-exclude-file-package-nodes

Conversation

@HumanBean17

Copy link
Copy Markdown
Owner

Problem

While testing #405 (absence diagnosis) on tests/bank-chat-system/, jrag find --fqn-contains 'Assign' returned 18 garbage lines:

java  role=OTHER  symbol_kind=file
java  role=OTHER  symbol_kind=file
... (x18)
DevAssignmentController  @chat-core  module=chat-app  role=CONTROLLER  symbol_kind=class

(The plain jrag find 'Assign'0 symbol + next: jrag find --fqn-contains Assign hint is #405 working as designed — exact match by design at ladybug_queries.py:1001; the hint is the new absence nudge.)

Root cause (pre-existing, unrelated to #405)

Two defects combine in the --fqn-contains path:

  1. find_v2 leaks file/package nodes. _symbol_where_from_filter has no structural-kind guard, so :Symbol nodes with kind='file' match. A file node's fqn is the filesystem path (chat-assign/.../DevAssignmentController.java), and s.fqn CONTAINS 'Assign' matches the PascalCase filename. search_lexical.py:209-210 excludes these on purpose — find_v2 was the gap.
  2. The renderer prints the file extension. find_v2's SELECT omits s.name, so NodeRef.name is None; display_name falls through to simple_name(fqn) = fqn.rsplit('.', 1)[-1] = java (the extension).

Verified against the live index — file nodes carry a good name but simple_name(path-FQN) returns the extension:

name='ChatAssignApplication.java' | simple_name(fqn)='java' | kind=file | role=OTHER | module=''

Fix (two layers)

  • Layer 1 (mcp_v2.py find_v2): exclude kind in ('file','package') from the symbol branch, mirroring search_lexical.py. Safe unconditionally — DeclarationSymbolKind (= class/interface/enum/record/annotation/method/constructor) excludes those values, so no filter can ever request them.
  • Layer 2 (jrag_envelope.py simple_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/:

  • Before: 18 java role=OTHER symbol_kind=file lines polluting the top.
  • After: clean declaration results only (DevAssignmentController, AssignConfiguration, AssignProperties, …).
DevAssignmentController  @chat-core  module=chat-app  role=CONTROLLER  symbol_kind=class
DevAssignmentController#simulateAssign  ...  symbol_kind=method
AssignConfiguration  @chat-assign  ...  role=CONFIG  symbol_kind=class
...

Tests

  • New test_find_symbol_excludes_file_and_package_nodes (reproduces the chat-assign case via the bank-chat-system fixture) + strengthened the existing empty-filter test to assert file/package exclusion.
  • New 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 on master too (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); uses search_v2 + vocab index, which this change does not touch.

🤖 Generated with Claude Code

HumanBean17 and others added 3 commits July 10, 2026 13:40
… 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 HumanBean17 merged commit 9e6cf2d into master Jul 10, 2026
4 checks passed
@HumanBean17 HumanBean17 deleted the fix/find-exclude-file-package-nodes branch July 10, 2026 14:40
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>
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