Skip to content

fix(sync): a name imported through a barrel links when an edit gives the barrel its re-export - #2744

Merged
colbymchenry merged 10 commits into
mainfrom
claude/infallible-mayer-f99e69
Oct 21, 2026
Merged

colbymchenry merged 10 commits into
mainfrom
claude/infallible-mayer-f99e69

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Summary

Re-verified on current main first

The shapes originally reported (late-file hold scenario) now converge on main without this change:

  • a barrel added after its importers (milo-ui useInvocation);
  • a file behind a barrel added later (vben ThemeToggle, passed on as default as ThemeToggle).

#2512's importer re-open and #2560 / #2665's barrel walks cover them. A probe on ada1445 passed both add cases and failed the edit case with 2 of 8 links. This PR covers the edit.

Why the rule is narrow

Three versions were measured on the same 18 repos. All three fix the same barrel-edit cases; they differ only in how many references they park, which sync then retries whenever a file with that key changes.

parks every plain named import + module file must carry the key + module must lack the name (this PR)
refs re-parked by a fresh index, 18 repos 11,061 7,484 2,727 (10 repos byte-identical)
vscode — 39,082 5,074
retried per changed file vs main (mean), folo / vben / milo-ui +15% / +19% / +17% +5% / +3% / +17% +3% / +1% / +3%

What the narrower rules leave out can't be satisfied through the key:

  • a workspace package's own name (@vben/icons, @follow/constants) resolves to the package folder, and its entry file (src/index.ts) never has the folder's key;
  • an import that resolves to nothing;
  • a reference whose module already gives the name but which fails anyway, such as a constant or class passed where a function is expected. That covers 34,015 of vscode's 39,082 and almost all of ghostfolio's (Angular DI tokens).

Tests

__tests__/sync-import-binding-retry.test.ts:

  • New describe, "sync links a plain named import through a barrel": the barrel added, a file behind it added, and an edit that gives the barrel the names it forwards. Each is compared edge for edge with a fresh index of the final tree in a second folder.
  • New tail assertions:
    • forwarded from a barrel yet to pass it on → module:lib;
    • a missing module → name tail;
    • @main, an alias of a file with another key → name tail;
    • LIMIT, a constant its module exports, passed as a function → name tail.

Red on main ae0fc63: the barrel-edit case (2 of 8 links) and the tail test fail. The two add cases pass on main through #2512 / #2665 and stay as regression coverage.

Mutations:

  • neutralizing the key check fails the @main assertion (module:@main);
  • neutralizing the "module gives the name" check fails the LIMIT assertion (module:consts).

Full suite on this branch, with main 51be28d merged, the kernel built and CODEGRAPH_KERNEL_EXPECT=1: 689 files, 8,185 passed, 0 failed, 82 skipped. The related sync suites (12 files) passed as well.

A/B (main ae0fc63 vs this branch, 18 repos, each arm's sync vs its own fresh index)

Fresh index: nodes, edges and files are identical in all 18 repos. 10 repos are byte-identical (controls include gin, django-realworld and vue-realworld). The rest differ only in the tail of refs that fail on both arms.

Late sync, every 7th source file:

scenario main this PR
gut (emptied, then restored by an edit): vben 2,337 lines 194
gut: vue-element-admin 18 6
gut: milo-ui 1,078 1,050
gut: folo 134 126
hold (held out, then added): vue-element-admin 8 0
hold: vben 1,702 2 *
hold: infrahub 0 300 **
route (route targets held out), all 9 repos 0 0
  • In every changed gut scenario, all failed-reference residue relinks: vben's 807 $t calls and its layout components, vue-element-admin's TagsView, Sidebar and Settings, milo-ui's chart utils, folo's NoopChildren. What remains on both arms is same-edge metadata and retargets, which predate this change.
  • Every residue bucket went down (by 1,938 in total) except the one marked **.
  • * vben hold: this is a side effect, not part of the design. Its 844 failing $t refs end up split between their name tail (462) and module:locales (382), so both by-name lookups fall under the 500-name ceiling. On main all 844 sit under one name, over it.
  • ** infrahub hold: ref rows only, with identical edges. Refs into generated/types.ts (2.1 MB, indexed with no nodes) failed while the file was held out, kept their name tail, and nothing re-resolves them after it returns. A fresh index parks them under module:types. They fail on both arms; only the bookkeeping differs.

Cost

  • Retry volume per changed file (mean, vs main): +1% to +3% on folo, vben, ghostfolio, infrahub, milo-ui and bulletproof; +13% on takenote.
  • Hot-file syncs (median of 3, the files whose keys hold the most parked refs): the retry phase grows by +3 to +117 ms. The largest is folo constants/ui.ts, 89 → 206 ms. Whole syncs stay within noise. These were timed with the "module file must carry the key" version, which parks a superset of this PR's refs.
  • vscode:
    • nodes, edges, files and refs identical apart from tails;
    • module-parked failed refs 10,541 → 15,753;
    • the retry lookup for hot files (uri.ts, lifecycle.ts) is unchanged;
    • full index: main 194.0, 194.7 and 193.1 s; this branch 183.9 and 183.1 s, both run uncontended. A third run of this branch overlapped another session's kubernetes index and took 232 s, so it is left out.

The A/B ran on ae0fc63; this branch was then synced to 51be28d (#2743, extraction), which touches none of its files. The validation kit lives in D:\cgwork\ubarrel and is deleted after the merge.

🤖 Generated with Claude Code

@colbymchenry
colbymchenry merged commit e248270 into main Oct 21, 2026
@colbymchenry
colbymchenry deleted the claude/infallible-mayer-f99e69 branch October 21, 2026 12:42
timothybrush pushed a commit to timothybrush/codegraph that referenced this pull request Oct 21, 2026
…olves another way (colbymchenry#2752)

A named import that a name match linked to a declaration elsewhere while
its module did not export the name kept that link after an edit gave the
module the name. A barrel's `export { EmptyState } from './EmptyState'`
declares nothing, so no definition delta, failed-ref retry or barrel walk
saw it (the "retargets" PR colbymchenry#2744 left). A name match into the module
itself, a class exported only as its default and imported by name, kept
its link after the default was dropped, where a fresh index finds nothing.

reopenNameMatchedImports (module-importers.ts): for each module the sync
modified, its importers' binding edges that resolved some other way than
their import (getImportBindingsResolvedElsewhere, an indexed range per
importer), whose import statement names this module, are resolved again
against the graph as it is (ReferenceResolver.resolveNow, writing
nothing). When the answer differs, the importer's edges through the name
and into the members of what the name match took go back for the sweep.

Regression test sync-name-matched-imports.test.ts: 4 cases comparing the
synced graph with a fresh index of the final tree. Fresh indexes are
byte-identical on 8 repos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
danusha2345 pushed a commit to danusha2345/codegraph that referenced this pull request Oct 21, 2026
…mchenry#2759; EXTRACTION_VERSION 52 for Go `error` embedding)

Co-Authored-By: Claude Fable 5.1 <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