Skip to content

fix(sync): an import moves to an added file that outranks the one it found - #2529

Merged
colbymchenry merged 1 commit into
mainfrom
claude/sync-outranked-imports
Oct 12, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/sync-outranked-imports

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Problem

An import written without an extension that resolved to one file kept that edge, and what it binds, when a file the resolver finds first was added beside it. A fresh index of the same tree links the added file.

In vbenjs/vue-vben-admin, packages/@core/ui-kit/shadcn-ui/src/components/button/ holds both button.ts and button.vue. With button.ts held out, check-button-group.vue's import … from './button' resolves to button.vue. Once button.ts is back, the resolver finds it first: .ts comes before .vue in EXTENSION_RESOLUTION.vue. But sync only retries imports that failed, and this one had resolved.

Script imports have had this gap since #2263 (a module path resolves to its file). #2528 made it show for components: these were the only residue lines that PR added in its A/B (4 lines in vben's every-7th restore and added-late scenarios).

Fix

src/resolution/module-importers.ts, on the add side, given ReferenceResolver.resolveModuleFile (from #2528):

  • The module edges. For each added file, find the module edges into the files an import of the same name finds instead (QueryBuilder.getModuleImportsNamed): dir/name.* under another extension, or the folder's own entry dir/name/index.* or dir/name/__init__.py. Those whose specifier now resolves to the added file go back to the sweep.
  • Their importers join the added module's importers with that statement, so the names it binds are re-resolved too. A name only the outranked file exports is then found by name, as in a fresh index. A name the added file also declares was already re-opened by CG-33's rebind, and a binding that failed by fix(sync): a reference through an import binding links its module when the module appears later #2450's module keys.

Removal needed nothing new: the edge into a deleted file is resurrected and re-resolved as before.

No schema change and no extraction change. Fresh indexes are untouched.

Validation

Tests. __tests__/sync-module-importers.test.ts has a new case, "file outranking the one an import resolved to":

  • button.vue exports sizeOf and onlyVue; button.ts exports a default, buttonClass and sizeOf.
  • group.vue imports all of them through './button', and panel.ts imports the default.
  • It runs deleted, deleted and restored, and added late, each compared with a fresh index of the final tree in a second folder.

Results:

  • On main, 2 of the 3 fail (restored, added late).
  • Without re-opening the module edge: 2 fail. Without the importer's bindings: 2 fail, through onlyVue.
  • It passes with the kernel on and off.
  • Full suite on Windows: 6,945 passed, 82 skipped.

Fresh-index A/B: all 9 repos byte-identical (a sync-only change).

Sync A/B. Each arm's synced graph is compared with its own fresh index. Barrels are every 2nd index.*; the rest are every 7th source file, deleted, restored and added late.

  • vben every 7th restored: 24 → 20.
  • vben every 7th added late: 24 → 20.
  • Those are exactly the 4 lines above: check-button-group.vue and icon-button.vue now link button.ts, as the fresh index does.
  • Every other scenario is unchanged, in excalidraw, shadcn-svelte, starlight, vue3-element-admin and svelte-realworld.
  • No scenario gains a residue line.
  • Controls (requests in Python, gin in Go, serilog in C#; every 7th deleted, restored and added late): synced graphs byte-identical to main's in all 9 runs.

Cost. One indexed range query per added file. Sync times are within run-to-run noise in every scenario, for example vben's every-7th added late at 6.46 s on main and 6.48 s with this PR.

Overlap

🤖 Generated with Claude Code

…found

An import written without an extension that resolved to one file, `./button`
to `button.vue` while `button.ts` was missing, kept that edge and what it
binds when `button.ts` was added beside it, though the resolver now finds
`button.ts` first: sync only retried imports that had failed. A fresh index
links the added file.

When a sync adds a file, the module edges into the files an import of the
same name finds instead (`dir/name.*` under another extension, the folder's
own `dir/name/index.*` or `dir/name/__init__.py`) whose specifier now
resolves to the added file go back to the sweep, and their importers join
the added module's importers with that statement, so the names it binds are
re-resolved too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@colbymchenry
colbymchenry merged commit 80e4b45 into main Oct 12, 2026
@colbymchenry
colbymchenry deleted the claude/sync-outranked-imports branch October 12, 2026 20:05
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