Skip to content

fix(dataflow): barrel-only tie-break misclassifies hybrid files, drops their call edges - #2488

Merged
carlos-alm merged 1 commit into
mainfrom
fix/issue-2339-barrel-only-tiebreak
Aug 14, 2026
Merged

fix(dataflow): barrel-only tie-break misclassifies hybrid files, drops their call edges#2488
carlos-alm merged 1 commit into
mainfrom
fix/issue-2339-barrel-only-tiebreak

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Problem

is_barrel_file/isBarrelFile classified a file as barrel-only using reexports >= ownDefs. A file with exactly one reexport and exactly one own definition hit that >= and got misclassified as barrel-only, even though it's a genuine hybrid (real logic plus a reexport), not a pure barrel. Once misclassified, the call-edge builder skips ALL of that file's own outgoing call/receiver edges, so on any incremental build where the file gets pulled into the barrel-candidate reparse (because something imports from it), its own call edges are silently dropped and never re-emitted.

Fix

Changed the tie-break to strict > in both engines. This is orthogonal to #1848's fix, which scopes which files are even eligible for this check (only transient barrel-candidate reparses, never a file genuinely part of the current build's changed set) — not the comparison itself. Verified no existing test exercises the exact reexports==ownDefs tie, so this couldn't have silently broken an already-covered case (e.g. issue-1174-chained-barrel-incremental.test.ts's hybrid file has 1 reexport vs. 4 defs, never at the tie — still passes).

A second bug found and fixed along the way

While writing this issue's regression test, discovered the JS/WASM engine's reparseBarrelFiles deletes a barrel candidate's outgoing edges without first clearing dataflow rows that reference them via call_edge_id — so the delete throws a FOREIGN KEY constraint error whenever the candidate's own call edges are also tracked by interprocedural dataflow. This was silently swallowed by the surrounding try/catch's debug() call (a no-op unless verbose), silently aborting the entire barrel reparse for that file. The Rust engine already carries this exact fix (#979); the JS engine never got the mirror.

Without this fix, the new regression test couldn't even reach the >=/> code path on the WASM engine — barrel reparsing was silently failing before ever classifying the file. Confirmed both fixes are necessary by temporarily reverting each independently and watching the test fail for the expected reason, then confirming it passes with both applied.

Test plan

  • New tests/integration/issue-2339-barrel-tiebreak-incremental.test.ts, dual-engine (mirrors issue-1174-chained-barrel-incremental.test.ts's structure): a hybrid file at the exact reexports==ownDefs==1 tie, whose own call edge must survive an incremental rebuild that pulls it into the barrel-candidate reparse
  • Confirmed the test fails (edges genuinely missing) with either fix reverted, and passes with both applied
  • cargo test --workspace → 1011 passed
  • cargo fmt --check / cargo clippy --workspace --all-targets -- -D warnings → clean
  • Full JS/TS suite (native addon rebuilt fresh) → 5241/5241 passed, including the pre-existing issue-1174-chained-barrel-incremental.test.ts (no regression)
  • npm run lint clean
  • node dist/cli.js diff-impact origin/main → contained to the 2 functions actually touched (reparseBarrelFiles, isBarrelFile)

Closes #2339

…s their call edges

Issue #2339: `is_barrel_file`/`isBarrelFile` classified a file as barrel-only
using `reexports >= ownDefs`. A file with exactly one reexport and exactly
one own definition hit that `>=` and got misclassified as barrel-only, even
though it's a genuine hybrid (real logic plus a reexport), not a pure
barrel. Once misclassified, the call-edge builder skips ALL of that file's
own outgoing call/receiver edges, so on any incremental build where the
file gets pulled into the barrel-candidate reparse (because something
imports from it), its own call edges are silently dropped and never
re-emitted.

Changed the tie-break to strict `>` in both engines. This is orthogonal to
#1848's fix, which scopes *which files* are even eligible for this check
(only transient barrel-candidate reparses, never a file genuinely part of
the current build's changed set) — not the comparison itself; verified no
existing test exercises the exact reexports==ownDefs tie, so this couldn't
have silently broken an already-covered case.

Also fixes a second, previously-hidden bug discovered while writing this
issue's regression test: the JS/WASM engine's `reparseBarrelFiles` deleted
a barrel candidate's outgoing edges without first clearing `dataflow` rows
that reference them via `call_edge_id`, so the delete threw a FOREIGN KEY
constraint error whenever the candidate's own call edges were also tracked
by interprocedural dataflow — silently swallowed by the surrounding
try/catch's `debug()` call (a no-op unless verbose), aborting the entire
barrel reparse for that file. The Rust engine already carries this exact
fix (#979); the JS engine never got the mirror. Without it, the new
regression test couldn't even reach the `>=`/`>` code path on the WASM
engine, since barrel reparsing silently failed before ever classifying the
file. Confirmed the fix is necessary by verifying the test fails without
it (edges genuinely go missing) and passes with it.

New tests/integration/issue-2339-barrel-tiebreak-incremental.test.ts,
dual-engine (mirrors issue-1174-chained-barrel-incremental.test.ts's
structure): a hybrid.js at the exact reexports==ownDefs==1 tie, whose own
call edge to helper.js must survive an incremental rebuild that pulls it
into the barrel-candidate reparse. Confirmed this fails without either fix
and passes with both. tests/integration/issue-1174-chained-barrel-
incremental.test.ts (parser.js has 1 reexport vs. 4 defs, never at the
tie) is unaffected — still passes.

Closes #2339

docs check acknowledged: internal engine bug fix + regression test, no
new user-facing behavior or CLI surface change.

Impact: 6 functions changed, 0 affected
@github-actions

Copy link
Copy Markdown
Contributor

Heads up: this PR references #979 without a closing keyword (Closes #N / Fixes #N). If this PR fully resolves #979, update the description so the issue auto-closes on merge — otherwise disregard this comment.

@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR corrects barrel-only classification for hybrid files at the reexport/definition tie and mirrors Rust’s foreign-key-safe edge cleanup in the JS/WASM pipeline.

  • Changes both engines’ barrel heuristic from reexports >= ownDefs to reexports > ownDefs.
  • Deletes call-edge-linked dataflow rows before replacing outgoing edges.
  • Adds a dual-engine incremental regression test covering the exact tie.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/domain/graph/builder/stages/resolve-imports.ts Mirrors foreign-key-safe cleanup and changes the JS/WASM barrel classification tie-break to preserve hybrid-file call edges.
crates/codegraph-core/src/domain/graph/builder/stages/import_edges.rs Changes the Rust barrel-only heuristic to strict reexport predominance, maintaining engine parity.
tests/integration/issue-2339-barrel-tiebreak-incremental.test.ts Verifies on both engines that an exact-tie hybrid retains its call edge after an incremental rebuild.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["Incremental build changes app.js"] --> B["Side-load and reparse hybrid.js"]
  B --> C["Delete dataflow rows referencing old outgoing edges"]
  C --> D["Delete old outgoing edges"]
  D --> E{"reexports > own definitions?"}
  E -->|No: 1 equals 1| F["Treat as hybrid"]
  E -->|Yes| G["Treat as barrel-only"]
  F --> H["Re-emit hybrid.js call edges"]
Loading

Reviews (2): Last reviewed commit: "fix(dataflow): barrel-only tie-break mis..." | Re-trigger Greptile

Comment thread src/domain/graph/builder/stages/resolve-imports.ts
@github-actions

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

2 functions changed23 callers affected across 3 files

  • reparseBarrelFiles in src/domain/graph/builder/stages/resolve-imports.ts:129 (3 transitive callers)
  • isBarrelFile in src/domain/graph/builder/stages/resolve-imports.ts:361 (22 transitive callers)

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review — replied to the dataflow-regeneration finding above with an investigation showing it's a pre-existing, dual-engine-consistent characteristic (not a regression from this PR), and filed #2489 to track it separately.

@carlos-alm
carlos-alm merged commit beb2fa2 into main Aug 14, 2026
46 checks passed
@carlos-alm
carlos-alm deleted the fix/issue-2339-barrel-only-tiebreak branch August 14, 2026 01:21
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 14, 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.

follow-up: barrel-only classification misclassifies a hybrid file at exact reexports==ownDefs parity

1 participant