Skip to content

fix(cli): distinguish zero exports from an unbuilt graph in exports <file> - #2582

Merged
carlos-alm merged 6 commits into
mainfrom
fix/issue-2530
Aug 19, 2026
Merged

fix(cli): distinguish zero exports from an unbuilt graph in exports <file>#2582
carlos-alm merged 6 commits into
mainfrom
fix/issue-2530

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

Closes #2530

Test plan

  • tests/integration/exports.test.ts: added a entry.js fixture file (a file-kind node with a non-exported function, simulating an entry script) and a new test asserting fileFound: true with zero exports; updated the existing "unknown file" test to assert fileFound: false.
  • tests/presentation/queries-cli.test.ts: split the old single "no exports found" test into two — one confirming the rebuild suggestion still appears when fileFound: false, one confirming it's absent when fileFound: true.
  • Revert-verify: temporarily disabled the data.fileFound branch and confirmed the new "does not recommend a rebuild..." test fails with the exact pre-fix message; restored the fix and it passes again.
  • codegraph diff-impact --staged -T: 3 functions changed, 2 transitive callers affected — contained blast radius, no unexpected consumers.
  • npm run lint clean.
  • Full npx vitest run: 5520/5520 tests pass (344 test files, +2 new).

…file>

exportsData conflated "no file-kind node matched at all" (unbuilt/not-found)
with "the file is in the graph but legitimately has zero exports" (entry
script, side-effect-only module) -- both produced results.length === 0, so
fileExports told the user to rebuild even on a correctly built graph.
exportsData now also returns fileFound, true whenever a file-kind node
matched regardless of its export count. The CLI uses it to print "No
exported symbols found for X." without the rebuild suggestion whenever the
file was actually found, mirroring the fix applied to roles --role in #2390.

docs check acknowledged

Closes #2530

Impact: 3 functions changed, 2 affected
@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR now distinguishes an indexed file with zero exports from a file absent from the graph, while tightening fuzzy file selection to avoid unrelated substring collisions.

  • Adds exact-case and slash-bounded candidate selection for export queries.
  • Returns an explicit fileFound signal and uses it to select accurate empty-result CLI messaging.
  • Adds integration and presentation coverage for zero-export, collision, case-sensitive, and missing-file scenarios.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/domain/analysis/exports.ts Selects a plausible requested file deterministically, rejects unrelated fuzzy collisions, and returns consistent presence metadata.
src/presentation/queries-cli/exports.ts Uses file presence to distinguish legitimate zero-export results from missing or unbuilt graph results.
tests/integration/exports.test.ts Covers zero-export files, fuzzy collisions, candidate ordering, and case-sensitive file selection.
tests/presentation/queries-cli.test.ts Verifies rebuild guidance appears only when the requested file is absent.

Reviews (7): Last reviewed commit: "fix: exports treats no-plausible-match a..." | Re-trigger Greptile

Comment thread src/presentation/queries-cli/exports.ts
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

4 functions changed3 callers affected across 3 files

  • isExactFileMatch in src/domain/analysis/exports.ts:38 (3 transitive callers)
  • isPlausibleFileMatch in src/domain/analysis/exports.ts:58 (3 transitive callers)
  • exportsData in src/domain/analysis/exports.ts:64 (2 transitive callers)
  • fileExports in src/presentation/queries-cli/exports.ts:151 (1 transitive callers)

…ring hit

findFileNodes uses LIKE '%target%', so a nonexistent target could
mid-string-collide with an unrelated file's path (e.g. "add.js" matching
"badd.jsx", or "utils.js" matching "my-utils.js") and set fileFound: true
for a file the caller never actually asked about, suppressing the rebuild
suggestion when it was still warranted. fileFound now requires an exact
match or a "/"-bounded path suffix via isPlausibleFileMatch.

docs check acknowledged

Impact: 2 functions changed, 3 affected
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

Comment thread src/domain/analysis/exports.ts Outdated
…esult

findFileNodes has no ORDER BY, so when a target ambiguously LIKE-matches
multiple files, fileResults[0] was whichever row SQLite's unordered scan
happened to visit first. If an unrelated mid-string collision was inserted
before the genuine exact/suffix match, it would win, showing the wrong
file's (possibly empty) data and reporting fileFound: false even though a
real match existed elsewhere in the result set. exportsData now prefers the
first fileResults entry that isPlausibleFileMatch accepts, falling back to
the first result only when no candidate is plausible.

docs check acknowledged

Impact: 1 functions changed, 2 affected
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

Comment thread src/domain/analysis/exports.ts Outdated
… default

SQLite's LIKE is case-insensitive for ASCII by default, so a case-mismatched
target (e.g. "Entry.js" against a stored "entry.js") already passes the
LIKE '%target%' lookup used throughout this module. isPlausibleFileMatch's
own exact/suffix comparison was case-sensitive, making it stricter than the
lookup that decided the match was a hit in the first place -- reintroducing
a false "file not found" for a spelling variant SQLite itself already
accepted. Both sides are now lowercased before comparing.

docs check acknowledged

Impact: 1 functions changed, 3 affected
Comment thread src/domain/analysis/exports.ts
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

Comment thread src/domain/analysis/exports.ts Outdated
Comment thread src/domain/analysis/exports.ts
…Found

Two related gaps in the fuzzy file-match selection:

- fileFound could be false while first.results was non-empty (a real
  collision file with real exports, but no plausible candidate at all) --
  a self-contradictory signal for structured/JSON consumers even though the
  CLI's own text messaging never reads fileFound once results are non-empty.
  fileFound is now also true whenever first has real results or reexports.

- isPlausibleFileMatch's case-insensitivity (needed to match SQLite LIKE's
  own default behavior) meant two distinct real files differing only by
  case could both look plausible for the same target, and the unordered
  LIKE scan could return the wrong one. exportsData now prefers an exact
  (case-sensitive) match via isExactFileMatch before falling back to the
  case-insensitive check.

docs check acknowledged

Impact: 2 functions changed, 3 affected
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

Comment thread src/domain/analysis/exports.ts Outdated
… fallback

The previous fix (hasContent) resolved the fileFound/non-empty-results
contradiction by making fileFound true whenever an unordered fallback still
had real content -- but that fallback could be an entirely unrelated
substring collision, so a missing target could still surface a different
file's real exports under fileFound: true. Requiring a plausible candidate
(isExactFileMatch or isPlausibleFileMatch) to exist at all, and treating its
absence exactly like fileResults.length === 0, removes the arbitrary
fallback entirely: nothing plausible now means nothing is returned, full
stop, matching a genuinely missing file's behavior. fileFound is then
trivially always true once `first` exists, since it can only exist when one
of those checks already accepted it.

docs check acknowledged

Impact: 1 functions changed, 2 affected
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm
carlos-alm merged commit a45a0ba into main Aug 19, 2026
23 checks passed
@carlos-alm
carlos-alm deleted the fix/issue-2530 branch August 19, 2026 04:05
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 19, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(cli): codegraph exports <file> says "Run codegraph build first" for a file that legitimately has zero exports

1 participant