Per-language fair-coverage numbers (symbol-bearing files with a resolved
cross-file dependent) on a real-world benchmark repo each, plus framework
routing coverage. Honest framing: residual = genuine static-analysis frontiers,
never gamed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A Delphi form unit (UFRMAbout.pas) owns its visual form definition
(UFRMAbout.dfm / .fmx) via the `{$R *.dfm}` directive, not a `uses` clause — so a
form file used only as a definition was reported as having no dependents. New
pascalFormEdges synthesizer pairs each .dfm/.fmx with its same-basename sibling
.pas unit (same directory), linking unit → form so editing a form surfaces its
code-behind. PascalCoin 73.0% -> 75.7%. Gated to .dfm/.fmx (Delphi-only); false
edges 0; regression test fails without the fix; suite green (1187).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shopify OS 2.0 templates are JSON (`templates/*.json`, plus section groups
`sections/*.json`) that reference sections by `"type"`, NOT by a `{% section %}`
Liquid tag — so a section used only from a JSON template looked unused (the JSON
wasn't even indexed). Index Shopify JSON templates/section-groups (path-detected,
incl. nested `templates/customers/*.json`) through the Liquid extractor and emit a
`references` edge from each `sections[].type` to `sections/<type>.liquid` (file
node only, no symbols, so the JSON never enters a symbol-bearing-file metric).
Dawn 39.1% -> 73.8% (sections now covered; residual = preset/theme-editor sections
with no static reference + dynamic `{% render %}`). false edges 0. Regression test
(top-level + nested JSON template) fails without the fix; suite green (1186).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A Lua `require("a.b.c")` dotted module path (→ a/b/c.lua or a/b/c/init.lua) and a
Roblox/Luau instance-path require (`require(script.Parent.Signal)`, where only the
leaf survives extraction) now resolve to the module file by path-suffix match —
the module root (lua/, src/, …) is project-specific, so among suffix matches the
one sharing the longest directory prefix with the requiring file wins. There was
no Lua/Luau module resolver, so requires resolved to nothing. Confidence 0.9 so
the deterministic path match wins over name-matching, which otherwise self-matches
the require to its own import node.
telescope.nvim 31.6% -> 84.2%, Fusion (Luau) 12.5% -> 92.2%. Gated to lua/luau.
Regression test (dotted Lua + instance-path Luau) fails without the fix; suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Measured an A/B of extending extractStaticMemberRef to TS/JS/Python: ZERO
coverage gain on both excalidraw (TS, 94.3%→94.3%) and requests (Python,
100%→100%), because in import-based languages a `Type.MEMBER` read requires
importing the type first, so the import edge already covers it — the static read
is pure duplication. Meanwhile it adds real graph noise (+1813 edges / +2448
`references` on excalidraw, the retrieval-perf benchmark, all pointing at
already-covered types). Reverted the extension; documented the finding in the
STATIC_MEMBER_LANGS comment so it isn't re-attempted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nuxt auto-imports a nested component by a DIRECTORY-PREFIXED name —
components/media/Card.vue is used in templates as <MediaCard/>, not <Card/> — but
the component node is named by basename (`Card`), so the usage never resolved and
the nested component looked unused. Two parts in vueTemplateEdges: (1) match
PascalCase component tags (`<MediaCard>`), not only kebab-case — the extractor's
template-tag references were the only path before, and they fail on the prefixed
name; (2) a `nuxtComponentName` map (`media/Card.vue` → `MediaCard`, incl. Nuxt's
dir-name de-dup) so the prefixed usage resolves to the basename node.
nuxt/movies 47.6% -> 93.5% (all nested carousel/media components now covered;
2 residual zeros are genuine frontiers — an unused carousel variant + an
unimported constants file). false edges 0, node count unchanged, suite 1183.
Regression test fails without the fix. Vue-only (the loop is gated to .vue files).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SvelteKit wires a +page.server.js / +page.js `load` (and form `actions`) to the
sibling +page.svelte's `data` BY FILE PATH — there is no static import — so
editing a loader showed no impact on the page it feeds and the page had no
server-side dependency. New svelteKitLoadEdges synthesizer links each page
component to the load/actions in its OWN route directory (same for +layout);
path-deterministic, so it never crosses routes (provenance: heuristic).
Direction page→load, so getImpactRadius(load) surfaces its page. Verified on
sveltejs/realworld: 19 sibling links, 0 cross-dir mislinks, false edges 0, node
count unchanged (edges only). Regression test (incl. a does-not-cross-routes
guard) fails without the synthesizer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A type referenced only through a property-wrapper ARGUMENT — `@Siblings(through:
AcronymCategoryPivot.self, …)`, the Fluent many-to-many pivot/join model — was
orphaned: the wrapper type (`Siblings`) and the property's declared type were
captured, but the attribute's argument expressions were never walked, so a model
reached solely through a relationship looked like nothing depended on it. Route
the property's attribute args through extractStaticMemberRef (which already
handles Swift `Type.self` navigation, self-filtering to capitalized receivers and
skipping the `\.$keypath` args).
vapor-til 94.7% -> 100% (the AcronymCategoryPivot model, referenced only via
@Siblings, now resolves). Additive A/B (same faircov): vapor-til +1, Alamofire
neutral (44/50); cross-family false edges 0. Regression test fails without the fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rocket mounts handlers via `routes![a::b::handler, …]` / `catchers![…]` macros.
Tree-sitter leaves the macro body as a raw token tree, so the handler paths were
invisible — each handler looked uncalled (Rocket mounts it at runtime, not from
in-repo code) and its file showed no dependents. Walk the token tree,
reconstruct each comma-separated path, and emit a `references` edge; the Rust
path resolver links it to the handler fn. The handler names are explicit in
source, so this is precise static extraction, not a heuristic — resolution still
validates each path, so no false edges.
Rocket realworld 68.8% -> 93.8% (all 4 route-handler modules now covered; only
the crate-root lib.rs remains, a see-through root). No regression on
axum/actix/ripgrep/tokio; cross-family false edges still 0; node count unchanged
(edges only). Regression test fails without the extractor.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reference-resolver pre-filter (hasAnyPossibleMatch) dropped any `a::b::c`
call whose leaf it never checked: it tested the first segment and the `b::c`
remainder, neither of which names a symbol, so a 3+-segment scoped call was
discarded before reaching the Rust path resolver. 2-segment calls survived
(their `b::c` remainder IS the leaf), which masked the gap. Added a last-`::`
leaf check, mirroring the existing dotted-name branch.
Rocket realworld 62.5% -> 68.8% (database/profiles.rs — reached only via
`database::profiles::find()` inside a `db.run(|c| ...)` closure — now resolves);
additive on axum/ripgrep/tokio (held, no regression); cross-family false edges
still 0. Pure resolution change, node counts unchanged. Regression test fails
without the fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A bare Rust path call `submodule::fn()` — the standard router-assembly /
handler-registration pattern (Axum's `users::router()`, etc.) — was resolved
crate-relative only, looking for `src/submodule.rs`. `mod users;` makes `users`
a child of the CURRENT module (2018 `self::`-relative), so a call to a sibling
submodule found nothing and the handler module looked dependent-less. The
resolver now tries self-module-relative FIRST, then falls back to crate-relative;
external-crate paths miss both and fall through to name-matching, unchanged.
realworld-axum-sqlx 72.7% -> 100% fair coverage. Validated no regression on
controls (ripgrep neutral 66/79; tokio +3 -> 260/321); cross-family false edges
still 0. Regression test fails without the fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Regenerate assets/waitlist.svg as "Join the waitlist" (no exclamation),
same cream/8px/padding styling.
- Add ?v=2 cache-buster to the README image URL so the new button shows
immediately instead of waiting on GitHub's image cache.
- CLAUDE.md house rule: version-tag every README image and bump ?v=N in
the same commit whenever the asset changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the plain oxblood-filled rectangle + system-font text SVG with a
polished button that matches the getcodegraph.com design language:
- Cream (#f7f6f2) rounded-rect background with a soft hairline border, 8px
radius, 52px height
- Logo mark (graph triangle, mirrors favicon.svg) in ink/oxblood at left
- Hairline divider separating mark from label
- "Join the waitlist!" label rendered as vector outlines (Archivo Bold 760,
17.5px) so the brand typeface renders correctly on GitHub, which blocks
@font-face in statically-served SVGs
- Oxblood arrow at right
Adds assets/generate-waitlist.py (requires fonttools + brotli) so the SVG
can be regenerated from the landing-page's Archivo variable font. Updates
the README img height from 44→52 to match the new geometry.
Adds three Claude session handoff documents capturing the cross-language impact coverage campaign, per-language coverage work, and issue triage session. Also removes references to deprecated `codegraph_context` and `codegraph_trace` tools from docs, replacing with `explore` and `node` trail terminology.
Advertise the upcoming hosted CodeGraph platform at the top of the README with a "Join the waitlist" CTA (early beta access) linking to getcodegraph.com, plus the button SVG asset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the markup-coverage chain. With C# namespaces (dc7d033) giving
same-named types distinct qns, a Razor/Blazor simple type ref now resolves
through the component's `@using` namespaces: for each `@using NS`, look up
`NS::Name` — if exactly one type matches, use it. The `@using` set is the file's
own directives PLUS every `_Imports.razor` from the file's folder up to the
project root (the Razor `_Imports` cascade), cached per file.
So a `@model` / `<MyComponent>` / `@code` ref to `CatalogBrand` resolves to the
`@using`'d DTO (`BlazorShared.Models::CatalogBrand`), not the same-named domain
entity. Validated: eShopOnWeb 81.9% → 83.9%; the DTOs are now covered
(`BlazorShared.Models::CatalogBrand` 0 → 8 dependents, CatalogItem → 3).
Razor-gated, so no effect on non-razor refs (cs-mediatr 94.6%, cs-polly 80.7%,
gin/Express unchanged). Full suite 1178; 1 regression test (fails without the fix).
ASP.NET arc this session: 59.3% → 83.9% (chained calls → entry exclusions →
markup parser → @code → C# namespaces → @using). Residual ~24 = reflection/proxy
(AutoMapper/Swagger/middleware/health) — a separate modeling feature.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
C# class qns were bare simple names, so same-named types in different namespaces
collapsed (a domain entity `ApplicationCore.Entities.CatalogBrand` and a DTO
`BlazorShared.Models.CatalogBrand` were indistinguishable). Added `namespace`
capture via the shared packageTypes mechanism (like Java/PHP): both block
`namespace Foo { … }` and file-scoped `namespace Foo;` forms, so types get
namespace-qualified ids (`Foo::Type`).
Safe — name-based resolution (C#'s default) is unaffected (the `name` is
unchanged; only `qualifiedName` gains the namespace). cs-mediatr 90.7% → 94.6%
(namespaces disambiguate collisions), cs-polly neutral (80.7%), ASP.NET
81.2% → 81.9%. 250 namespace nodes on eShopOnWeb. Full suite 1177; 1 test.
This is the prerequisite for Razor `@using` disambiguation (next): now that the
entity and DTO have distinct qns, a `.razor`'s `@using BlazorShared.Models` can
pick the DTO.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends the Razor/Blazor markup parser to analyze the C# inside `@code { }`,
`@functions { }`, and `@{ }` blocks — the Blazor analog of Svelte's <script>
delegation. Blocks are brace-matched (skipping strings/comments), wrapped in a
synthetic class so tree-sitter parses the component's fields/methods in a class
context, then their external references (service/DTO calls, `new X()`, type uses)
are attributed to the component. No per-member nodes are added — coverage only
needs the dependency edges to external types, so node count stays flat.
So a service/DTO used only in component logic (`@code { _svc.Load(); }`) is now
linked. Validated: eShopOnWeb 79.9% -> 81.2%; the test proves a `@code`
`new CatalogService()` covers CatalogService. Full suite 1176; 1 regression test;
no node explosion (entry-excluded count unchanged).
Residual ASP.NET gap is now the DTO name-collision (`CatalogBrand` exists as both
a domain entity and a DTO — the markup/@code ref resolves to the entity) — the
next increment is `@using` namespace disambiguation, tracked in the scope doc.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New standalone RazorExtractor (follows the svelte/vue/liquid pattern) links
markup-driven C# that previously had no static in-repo caller:
- `@model Foo` / `@inherits Bar<Foo>` → the view-model / base type (.cshtml + .razor)
- `@inject IService svc` → the injected service type
- `@typeof(X)` → the referenced type
- Blazor `<MyComponent/>` tags + generic `TItem="Foo"` args → the component class
Wiring: `.cshtml`/`.razor` → new `razor` language (types.ts, grammars.ts
EXTENSION_MAP + isLanguageSupported/isGrammarLoaded + GrammarLanguage exclude +
display name); dispatch in tree-sitter.ts `extractFromSource`.
Key risks (per docs/design/template-markup-parser.md) all handled:
1. FAMILY GATE — `razor`+`csharp` share a new `dotnet` family in name-matcher,
so `@model Foo`→`Foo.cs` isn't dropped by the cross-family gate (082353e).
2. PASCALCASE vs HTML — only `[A-Z]`-initial tags are components (HTML is
lowercase); known Blazor framework components are skipped.
3. NODE DISCIPLINE — exactly one `component` node per file; tags → `references`
edges, never nodes (no per-tag explosion).
4. INDEXING — `.cshtml`/`.razor` registered so they're indexed.
Validated: eShopOnWeb — 61 razor nodes, 97 resolved markup→code edges
(`App.razor`→MainLayout, `Create.razor`→CatalogBrand/ICatalogItemService,
ToastComponent covered). FAIR coverage 77.2% → 79.9% (.cshtml/.razor counted as
view entries in the metric). No regression (gin/requests/Express unchanged,
cs-mediatr/cs-polly fine). Full suite 1175; 1 regression test.
Modest gain on this app is honest: residual DTOs are a name-collision
(`CatalogBrand` exists as both entity and DTO — edge resolves to the entity),
and many DTOs/services are used inside Blazor `@code { }` blocks not yet
delegated to the C# extractor — both documented as next increments in the scope.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`import articlesController from './article.controller'` where the module does
`export default router` left the controller with no dependent: the renamed local
binds to the default export, so the binding name isn't found as a symbol and only
named imports linked. resolveModuleImportToFile now treats a default import like
a namespace import — the dependency is on the MODULE FILE regardless of the
default export's name. External packages don't resolve (no file), so
`import React from 'react'` still creates no edge.
FAIR coverage: Express realworld 70.4% -> 100% (all route controllers + the
aggregator now resolve; residual main.ts/seed.ts/jest.preset excluded as
entries/config per methodology). NestJS 93.8% -> 96.8%. No regression; FastAPI
98% unchanged. Full suite 1173; 1 regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
matchMethodCall's pattern only accepted a single-segment receiver
(`^(\w+)\.(\w+)$`), so a call through a property/member chain
(`builder.Services.AddCoreServices(...)`) matched nothing and never reached the
method-name fallback (Strategy 3). Broadened the receiver to allow dots so the
last segment is taken as the method name and resolved by name (with Strategy 3's
existing single-candidate / receiver-overlap guards). This is the common C#
extension-method shape — ASP.NET DI registration (`builder.Services.AddX()`),
Guard clauses (`Guard.Against.X()`), LINQ-style chains.
Note: C# constructor DI / interface->implementation dispatch ALREADY works
(`csharp` is in IFACE_OVERRIDE_LANGS; service/repo impls were already covered) —
the gap was these chained extension calls, not DI dispatch.
FAIR coverage: ASP.NET eShopOnWeb 62.3% -> 65.3% (DI-registration + Guard
extension classes now covered). No regression: cs-mediatr 90.7%, cs-polly 80.7%,
gin/requests/fastapi unchanged. Full suite 1172; 1 regression test.
ASP.NET's remaining ~70 zeros are convention/reflection ENTRIES (MVC controllers,
Razor Pages, API endpoint classes, Blazor .razor-driven code-behind, EF
IEntityTypeConfiguration registered by reflection) with no static in-repo caller —
a genuine static-analysis frontier; literal 95% needs framework-entry metric
exclusions or per-convention modeling (a separate decision).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`url(r'^api/', include('conduit.apps.articles.urls'))` emitted an `imports` ref
to the dotted module path, but resolveOne's pre-filter dropped it (no symbol /
import / framework claim named `conduit.apps.articles.urls`) before resolution
could map it to the file. The Django resolver now claims `*.urls` names so the
ref survives the pre-filter; resolvePythonAbsoluteModule then maps the dotted
module to its `urls.py`. So an app's routes module records a dependency on the
project urlconf that mounts it.
FAIR coverage: django-realworld 63.0% -> 74.1% (all three app urls.py now
resolve). Residual zeros are framework entries (root urlconf + AppConfig
__init__.py, loaded via settings strings) and dynamic frontiers (signals via
in-body `ready(): import myapp.signals`; DRF/string-config exception classes) —
documented, not in scope here. Full suite 1172; 1 regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A router/aggregator's `from app.api.routes import authentication` (FastAPI/Django
pattern) left the imported route module with no dependent. The bare leaf
(`authentication`) is ambiguous — three `authentication.py` may exist across
sibling packages — so it must be resolved through the import's SOURCE package.
- `findPythonModuleFile(mod)` (shared): maps a dotted module path to its file
(`a/b/c.py` or `a/b/c/__init__.py`, suffix-matched).
- `resolveModuleImportToFile`: for a Python absolute `from a.b import c` where
resolveImportPath (relative-only) fails, resolve `a.b.c` directly via
findPythonModuleFile — using the package source to pick the RIGHT submodule.
- `resolvePythonAbsoluteModule` (the `import a.b.c` path from 61a993a) is now
restricted to DOTTED refs. This corrects a greedy bare-leaf match it was doing:
it matched a bare `from x import name` leaf to ANY same-basename file, which
was both wrong (FastAPI picked dependencies/authentication.py over
routes/authentication.py) and inflated flask/Django coverage with spurious
same-named matches. Bare leaves now go through the source-aware path only.
FAIR coverage: FastAPI realworld 78.6% -> 98.0% (4 route modules now resolve to
the right files). flask & requests are 100% (honest — the 61a993a 92%/Django
70.4% were partly the spurious greedy match; corrected here, with package
entry/barrel `__init__.py`/`__main__.py`/`docs` excluded per methodology).
No regression; full suite 1171 passed; 1 regression test (disambiguates
same-named submodules across packages).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`import conduit.apps.signals` (a dotted absolute module import — common in
package setup and side-effect imports) created an import node but NO edge to the
module's file; only `from x import y` was linked. So a module imported by its
dotted path looked like nothing depended on it.
- Extraction (tree-sitter.ts): a Python `import_statement` now pushes an
`imports` ref per dotted module (mirroring Go), at module scope.
- Resolution (import-resolver.ts): `resolvePythonAbsoluteModule` maps `a.b.c`
to a file node ending in `a/b/c.py` or `a/b/c/__init__.py` (suffix-matched, so
a package rooted under src/ works). Stdlib/external modules don't resolve (no
`os.py` node), so `import os` creates no edge.
Validated (FAIR file-dependent coverage): flask 88.0% -> 92.0%, Django realworld
63.0% -> 70.4% (covered exceptions + app config); requests neutral; no regression.
1 regression test. Full suite 1170 passed.
NOTE (frontier, deferred): an `import a.b.c` INSIDE a function body (Django
`AppConfig.ready(): import myapp.signals`) is still not linked — Python in-body
import_statements aren't reached by import extraction (only top-level are),
though in-body calls are. Django `signals.py` files remain a residual zero.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two resolution fixes that lift cross-file coverage on multi-platform native
modules (React Native Windows/Apple/Android side-by-side):
1. SAME-DIR C/C++ INCLUDE. `#include "Foo.h"` resolved by basename with no
directory awareness, so when a module ships a same-named header per platform
(windows/code/RNCAsyncStorage.h vs apple/.../RNCAsyncStorage.h) the includer
landed on an arbitrary one — then the cross-family gate (082353e) nulled the
wrong-family match, leaving the real local header with 0 dependents. Now C's
quoted-include rule is honored: resolve relative to the including file's own
directory FIRST (resolveViaImport C/C++ branch), with a same-dir/proximity
preference also added to matchByFilePath's basename fallback (pickClosestFileNode).
2. KMP commonMain IMPORT. An `expect` decl and its `actual`s share one FQN
across source sets; resolveJvmImport took candidates[0], so a single platform
`actual` absorbed every common-side import and the `expect` looked unused.
Now the same-FQN candidate CLOSEST to the importer (by shared dir prefix,
`expect` as tiebreak) wins — a commonMain import resolves to the commonMain
expect (pickClosestJvmCandidate).
Both are the same "prefer the closest declaration on a name collision" principle
as the 082353e gate. FAIR file-dependent coverage (authored source, excluding
generated/build/config/test/barrel/entry per methodology):
async-storage 75.0% -> 97.4% (residual: 1 KMP expect-decl frontier)
rn-device-info 72.4% -> 95.2% (residual: 1 REACT_METHOD-macro extraction frontier)
No regression: okhttp 75.9%->76.4%, kotlinx.coroutines 89.7% (neutral), cross-family
false edges still 0 on all. Node count unaffected (resolution-only). 2 regression
tests added. Full suite 1169 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cross-language gate added in dbc4862 covered only `references` edges from
the import/name-match strategies. Two holes remained, both surfaced validating
react-native-async-storage (a TS `type TestRunner` colliding with a Kotlin
`class TestRunner`):
1. Framework strategy was ungated. The React PascalCase component resolver
name-matches `getNodesByName` with no language check (its COMPONENT_KINDS
includes `class`), so a TS ref to `TestRunner` resolved onto the Kotlin
class at confidence 0.8 — outranking the cross-language-penalized (0.5) TS
name-match. Same hole for Svelte/Vue.
2. `imports` edges were never gated (only `references`), so a TS `import React`
matched a Swift `React`, and a C++ `#include "X.h"` matched a same-named
ObjC header on another platform (basename collision).
Adds `crossesKnownFamily` (both sides in a KNOWN family — jvm/apple/web/c — and
different) and applies it:
- `gateFrameworkLanguage`: new gate on the framework strategy for
references/imports. Both-known so config↔code bridges (yaml/blade side is not
a known family) and `calls` bridges (different kind) are untouched.
- `gateLanguage` (strategies 2/3): extended to also gate `imports`. Both-known
so a `.vue`/`.svelte` file (own language tag) importing a `.ts` module is left
alone, while a C++→ObjC header basename collision is dropped.
- `applyLanguageGate` (name-match candidates): filters `imports` candidates too,
so a ref RE-POINTS to the same-family target instead of merely dropping.
Validated: async-storage cross-known-family references/imports → 0 (the 6 TS
refs + 5 imports re-point to the TS type); rn-device-info JS↔native `calls`
bridges intact (91 JS→Java, 37 JS→ObjC, full Java↔ObjC↔C++ pairing). Node count
unaffected (edge-resolution change only). Two regression tests added (both fail
without the fix). Full suite 1167 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Many React Native libraries (react-native-device-info and others) emit events
through a custom `sendEvent(reactContext, "eventName", body)` helper that
internally calls `…emit(eventName, …)` with the event name as a VARIABLE — so
RN_JVM_EMIT_RE (which matches `.emit("literal", …)`) saw only the variable and
produced no event edge. The literal event name lives in the wrapper CALL.
Add RN_NATIVE_SENDEVENT_RE — captures the first string literal inside a
`sendEvent(...)` call (multi-line tolerant; `[^;{}]*?` keeps it to one statement
so the wrapper DEFINITION, whose `(` is followed by `… ) {`, never matches).
Wired for Java/Kotlin and Swift. A native method firing `sendEvent(…, "X", …)`
now links to the JS `addListener('X', handler)`.
Measured on react-native-device-info: the Java-side battery/power/headphone
events now connect to their JS hooks (usePowerState, useBatteryLevel, …) —
previously only the ObjC `sendEventWithName:` side did. Full suite green.
(Expo's constant-based events — `sendEvent(batteryLevelDidChange, …)` where the
name is a `const` — still need constant resolution; tracked as a follow-up.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`RCT_EXPORT_METHOD(foo:(...))` — the most common way to declare an iOS native
module method — parses as a macro-expression (an ERROR node), NOT a
`method_definition`, so the ObjC extractor never made a node for it. The iOS half
of a native module was invisible: a JS call to it couldn't resolve, and the
cross-platform pairing had nothing to pair. Only regular `- (void)foo` ObjC
methods were nodes.
The React Native bridge resolver now implements the `extract()` framework hook
for `.m`/`.mm` files: it reuses the existing `RCT_EXPORT_MODULE` /
`RCT_EXPORT_METHOD` / `RCT_REMAP_METHOD` source parser to emit a method node per
exported method, named by its JS-visible name (the selector's first keyword, or
the explicit `RCT_REMAP_METHOD` JS name) so it lines up with the Android
`@ReactMethod` method and the JS callsite. Node id is prefixed `rn-export:`.
Measured on react-native-device-info: JS→ObjC `calls` went 7 → 37, and Java↔ObjC
cross-platform pairs 22 → 29 (ObjC↔C++ 8 → 24) — the iOS bridge surface is now
visible and connected. Full suite green; node count grows only by the newly-
visible iOS methods.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The classic React Native bridge links a JS callsite to a native module method,
but only ONE platform's impl — the other (the iOS method when JS resolved to
Android, or vice versa) looked like nothing called it and editing it showed no
blast radius. Same gap fixed for Expo, now for the classic bridge.
`rnCrossPlatformEdges`: a native method (java/kotlin/objc/cpp) that HAS a JS-side
`calls` edge is a confirmed bridge method; link it to the same-named native
method in another language (the other platform's impl), both directions. Names
are normalized to the first selector keyword (`getFreeDiskStorage:` →
`getFreeDiskStorage`) so an iOS selector lines up with the bare Android name.
RN runtime-infrastructure methods (addListener / getConstants / getName / … —
present on every module) are excluded so they don't cross-link unrelated modules
in a multi-module repo.
Measured on react-native-device-info: 152 cross-platform pairs (Java↔ObjC↔C++);
the classic JS→Java/ObjC bridge itself is unchanged. Heuristic edges (provenance
`heuristic`, `synthesizedBy:'rn-cross-platform'`). Full suite green; node count
stable. (Note: iOS methods written with the `RCT_EXPORT_METHOD` macro parse as a
macro, not a method node, so only regular ObjC method impls pair today — macro
extraction is a separate follow-up.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Validated the cross-language bridge graph on real RN/Expo repos (expo-battery,
react-native-device-info) and fixed three gaps:
- Generic-typed Expo Functions: Android Expo Modules write `AsyncFunction<Float>
("getBatteryLevelAsync")`, but the framework extractor's regex allowed no
generic type parameter between the keyword and `(`, so EVERY Android Expo
method was silently dropped — a JS call resolved only to the iOS Swift impl.
The regex now accepts an optional `<…>`.
- Cross-platform pairing: an Expo Module exposes the same JS method from BOTH a
Swift (iOS) and a Kotlin (Android) impl, but a JS callsite name-resolves to
only one. New `expoCrossPlatformEdges` links the two platform impls of the same
`<module>.<method>` (both directions), so a JS call reaching one reaches the
other and editing either surfaces the JS caller.
- Cross-language type-reference precision: a `Type.member` static read in native
code (`BatteryManager.EXTRA_LEVEL`, the Android system class) name-matched to a
coincidentally same-named TS class. `references` (type-usage) edges are now
gated to the same language family (JVM java/kotlin/scala, Apple swift/objc, web
ts/js, C c/cpp) at the name-match + import strategies — framework resolvers are
NOT gated, so deliberate config→code bridges (Drupal routing.yml→PHP, Spring
@Value→YAML) and JS↔native `calls` bridges are unaffected.
The classic RN NativeModules bridge (`@ReactMethod`/`RCT_EXPORT_METHOD`) already
worked and is unchanged (118 JS→Java + JS→ObjC `calls` on device-info). Full
suite green; node count stable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Objective-C was the worst-covered README language (~33-50%). Four gaps:
- Single-argument selectors: `[cache storeImage:key]` (the most common call
form) was named `storeImage` — without the colon — at the call site, so it
never matched its `storeImage:` method. A single keyword now gets its colon
when the message has an argument (distinguished from the unary `[c reset]`).
- Class-message receiver: `[SDImageCache sharedCache]` / `[[Foo alloc] init]`
resolved the method but never referenced the CLASS — whose @interface lives in
the header. Emit a `references` edge to a capitalized message receiver, so a
class used via class messages (alloc/init, singletons, factories) and its
header record a dependent.
- `#import "Foo.h"`: the import created a node but never resolved cross-file
(a bare `Foo.h` has no slash). matchByFilePath now resolves a bare filename
with a short extension to the header by basename — so every imported header is
covered.
- Class-method calls: `SDImageCache.storeImage:` didn't resolve because the
method-call regex rejected the trailing-colon selector; broadened to allow
colon keywords (a no-op for other languages).
Measured (fair cross-file dependent coverage): AFNetworking 50.0% → 90.0%;
SDWebImage (Core) 33.8% → 91.6%. Node count stable. Residual = public-API
category methods called only by app code (a frontier). Full suite green; the
shared matchByFilePath / method-regex changes are no-ops for other languages
(only bare-filename refs and colon-bearing selectors are newly handled).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The recurring residual across Dart, Java, C#, Swift, Kotlin, Scala, PHP: a type
used only through a static member or enum VALUE recorded no edge. The body walker
handled CALLS (`Type.method()`) and `new`, but reading an enum value
(`MediaKind.video`), a static field (`Colors.red`, `JsonScope.EMPTY_DOCUMENT`), or
a class constant (`Foo::BAR`) produced nothing — so an enum/constants type used
purely by value looked like nothing depended on it.
`extractStaticMemberRef` (run in the function-body walk) emits a `references` edge
to the capitalized receiver of a member-access VALUE read. Handles each
language's member-access node (`field_access` Java, `member_access_expression`
C#, `navigation_expression` Kotlin/Swift, `field_expression` Scala,
`class_constant_access_expression`/`scoped_property_access_expression` PHP,
`qualified_identifier` C++, and Dart's `identifier` + sibling `selector`).
Skipped when the access is a call's callee (already linked) and gated to
languages where types are Capitalized by convention — so a lowercase
`obj.field` / `pkg.func` never triggers. TS/JS/Python are deliberately excluded:
already high-coverage and they drive the retrieval-performance benchmark.
Measured: flutter/packages (Dart) 92.4% → 93.2% (the enum-value files); additive
across the other languages. Node count stable (edges only). Verified per-language
(Java/Kotlin/PHP/Swift/Dart/Scala emit the ref to the right type). Full suite
green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two Dart gaps, both gated to `language === 'dart'` in tree-sitter.ts:
- Mixins: `class C extends Base with M1, M2` packs the `with` mixins into a
`mixins` child of the `superclass` node. The generic inheritance path read
`superclass`'s first child as the base and dropped the mixins entirely — and
`class C with M` (no extends) even had its `mixins` node misread as the
superclass. Mixins are Dart's core composition mechanism (Flutter is built on
them). Emit `extends` for the base type and `implements` for each mixin.
- Method type references: Dart was in the type-annotation languages but produced
ZERO `references` edges, because a `method_signature` wraps the real
`function_signature` (where params + return live) and the return type is a bare
`type_identifier` child, not a `type` field — so the generic getChildByField
walk found nothing. Walk the inner signature; a class or enum used only as a
parameter/return type now records a dependency.
Measured (fair cross-file dependent coverage, symbol-bearing source files):
flutter/packages 88.8% → 92.4%, dio 86.4% → 87.9%. Node count stable (edges only).
Residual is genuine frontiers: see-through export barrels (`lib/<pkg>.dart`,
`types.dart`), platform-conditional files (`_io.dart`/`_web.dart`/`_stub.dart`),
and enum-value access (`MediaDeviceKind.videoInput` — the cross-language
static-member/value-read frontier, deferred). Full suite green; Dart-only change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The C++ qualified-name resolver BFS-searched the whole declarator for a
`qualified_identifier`, INCLUDING the parameter list and the trailing return
type. So a plain free function `std::string TableFileName(const std::string&
dbname)` was named `string` (from the `std::string` parameter), and an
`auto to_string(...) -> std::string` was named `string` (from the trailing
return) — calls never resolved, `codegraph_node` couldn't find them, and the
defining file looked like nothing depended on it. This is how most free
functions in a namespaced C++ library look, so it was a broad miss.
Skip `parameter_list` and `trailing_return_type` when locating the function's
own qualified name; a non-qualified name correctly falls back to the default
declarator-name extraction (`TableFileName`, not `string`).
Measured: leveldb 91.7% → 94.8% fair cross-file dependent coverage; fmt went from
32 functions mis-named after a bare type to 1; redis (C, unaffected — uses no
qualified-name resolver) held at 92.2%, its residual being genuine frontiers
(generated command tables, macro-reached code, function-pointer command dispatch,
example modules). Node count stable (functions were already nodes, just
mis-named). Full suite green; qualified methods (`Foo::bar`) unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ruby's other half of cross-file dependency (besides mixins) is `require` — but
require imports created an import node that never resolved to the required file,
so a file pulled in only by `require` (config-loaded components like Sidekiq's
BasicFetch/systemd, or any gem that doesn't rely on autoloading) recorded no
dependent. Mixins alone left Sidekiq at 76.8%.
Emit an `imports` ref for `require "lib/foo"` (load-path — suffix-matched against
the file path, the same mechanism template imports use) and `require_relative
"../foo"` (resolved against the requiring file's directory). Bare gem/stdlib
requires (`require "json"`, no slash) are skipped as external. The ref resolves
to the required FILE node, so the requiring file → required file dependency is
recorded even when the required file's class is reached only dynamically.
Measured (fair cross-file dependent coverage, symbol-bearing source files):
sidekiq 76.8% → 100.0%, rails/activerecord 93.0% → 96.8% (Rails autoloads but
still has explicit requires for sub-components). Node count stable (the import
nodes already existed; this only resolves them). Full suite green; Ruby-only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ruby's primary composition mechanism is the mixin (`include`/`extend`/`prepend`
a module), but these parse as a bare `call` to a method named `include` with the
module as a constant argument, so they created no edge — a concern module mixed
into a class recorded no dependent unless its methods happened to be called
in-repo. On a concern-heavy codebase (Rails) that was a large share of modules,
and ActiveRecord is built almost entirely on `include`d concerns.
Detect `include`/`extend`/`prepend` (bare, no receiver) in the Ruby extractor's
visitNode hook and emit an `implements` edge from the enclosing class/module to
each module argument (`constant` or `scope_resolution`), so editing a concern
surfaces every class that mixes it in. Skips `extend self` / dynamic args, and
`arr.include?(x)` (has a receiver) is unaffected.
Measured (fair cross-file dependent coverage, symbol-bearing source files):
rails/activerecord 84.8% → 93.0%, sidekiq 71.0% → 76.8%. Node count stable
(edges only). Residual is genuine frontiers: classes instantiated by class-string
`constantize` (associations, Arel nodes), generators, version files, and
config/`require`-loaded components. Full suite green; Ruby-only change.
(Follow-up: resolve `require`/`require_relative` to files — helps explicit-require
gems like sidekiq; Rails autoloads so it benefits less.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PHP ignored namespaces entirely: every class's qualified name was its bare simple
name, so a framework's many same-named classes (Laravel has 7+ `Factory`
interfaces, several `Dispatcher`s, across namespaces) collapsed into one arbitrary
match, and `use` imports created nodes that never resolved to a definition. An
interface imported and constructor-injected (the DI pattern) showed zero
dependents.
- Capture `namespace Foo\Bar;` as a file-level package (like a Java/Kotlin
package) so each class is scoped to `Foo\Bar::Class` — same-named classes are
now distinguishable.
- Resolve `use Foo\Bar\Baz;` (single and grouped) to the namespace-qualified
definition via an `imports` ref the resolver matches by exact qualified name —
so an imported-but-DI-injected contract records a precise cross-file dependency.
- Add `php` to the type-annotation languages with a PHP-aware walker: parameter /
property / return type-hints are `named_type`/`union_type`/… wrapping
`name`/`qualified_name` (never `type_identifier`), and `variable_name` ($x)
param names are never mis-emitted.
Measured (fair cross-file dependent coverage, symbol-bearing source files):
guzzle 95.2% → 100.0%, laravel 80.5% → 94.9%. Node count grows only by the new
namespace nodes (one per file, a real symbol — like JVM package nodes), not
duplication; edges added precisely. laravel residual is genuine frontiers
(service providers / controllers / middleware registered by class-string,
console view components dispatched by magic `__call`, mixin traits for app
consumption). Full suite green; non-PHP languages unaffected (changes gated to
`language === 'php'`).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Scala extraction produced symbol nodes but almost no edges for typeclass-style
code: every supertype/type/implicit relationship was dropped, so a trait used as
a type, extended with type parameters, or required as an implicit looked like
nothing depended on it — on cats/algebra that was most of the graph.
- extends/with: `extends A[X] with B with C` packs all supertypes into one
`extends_clause`; the generic path took only namedChild(0) and kept the full
text (`A[X]`) so parameterized supertypes (every typeclass) never matched.
Iterate every supertype and unwrap `generic_type` → base type name via a new
shared `scalaBaseTypeName` helper.
- type references: add `scala` to the type-annotation languages so method
parameter/return types link; walk EVERY curried parameter list (the trailing
`(implicit M: TC[A])` list is where instances are passed); walk
`type_parameters` for context bounds (`def f[A: Monoid]`); and emit refs for
`val`/`var` type annotations from the Scala extractor. Capitalized Scala
primitives are skipped to avoid resolution noise.
- instantiation: `new T[...] { ... }` (`instance_expression`) now emits an
`instantiates` edge to the base type.
Measured (fair cross-file dependent coverage, symbol-bearing source files):
cats 48.9% → 89.2% (scalafix tooling + JMH benches excluded; 82.1% raw),
gatling 76.3% → 91.2%. Node count stable (edges added, not nodes); residual is
genuine frontiers (cross-build scala-2.12/2.13+/3 variants, laws test-support,
wildcard-import barrels, infix-type evidence). Full suite green; changes gated
to Scala so no cross-language regression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A KMP `common` source set declares `expect fun foo()` / `expect class Bar`;
each platform source set (jvm, native, js, wasm) provides an `actual`
implementation with the identical fully-qualified name in a different file.
Callers in common code resolve to the `expect` declaration, so every platform
`actual` ended up with zero dependents — invisible to impact/`affected` and
showing an empty blast radius even though editing it can break every caller.
- Capture the `expect`/`actual` platform modifiers at extraction via a new
generic `extractModifiers` hook (Kotlin reads `modifiers > platform_modifier`),
persisted onto the node's decorators list.
- Synthesize a heuristic `calls` edge from the common declaration to each
platform `actual` (mirroring the interface-impl bridge: abstract → concrete),
matching by qualified name + the `actual` marker. The declaration side is the
same-FQN, non-`actual` node, which also gates out plain cross-file overloads.
- Widen kind matching so an `expect class` fulfilled by an `actual typealias`
(a core KMP idiom: CancellationException, ReentrantLock, …) links too.
Measured (fair cross-file dependent coverage, symbol-bearing source files):
kotlinx.coroutines 76.8% → 93.5%; OkHttp holds at 96.2% (barely uses KMP).
Node count stable (edges added, not nodes); residual is genuine frontiers
(expect-decl sides with no in-repo callers, ServiceLoader/agent SPI, test infra).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Swift is otherwise healthy (init calls -> instantiates, protocol conformances ->
implements). The systematic gap was property wrappers / attributes:
- A Swift `@X` parses as `property_declaration -> modifiers -> attribute ->
user_type`. `extractDecoratorsFor` didn't recognize the `attribute` node type
(only decorator/annotation/marker_annotation) nor extract the name from its
`user_type` child, so every Swift attribute usage was dropped.
- Swift stored properties inside a type aren't extracted as their own nodes, so
the decorator helper never ran on them. Added a dispatcher branch that runs
extractDecoratorsFor + type-annotation extraction on Swift property declarations
inside a type.
Now `@Argument`/`@Option`/`@Published`/`@State`/custom `@propertyWrapper` usages
(and `@objc`/`@MainActor` on types/methods) link to the wrapper/attribute type.
Alamofire 93%->95.3%, swift-argument-parser 84.6%->96.2%. Reuses the same
extractDecoratorsFor path as the Java annotation fix, unifying
annotation/attribute/property-wrapper handling across Java/C#/Kotlin/Swift. New
test + CHANGELOG; full suite green (1151).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Java was otherwise healthy (FQN imports, new/extends/implements all resolve); the
systematic gap was annotations, and both halves were missing:
- Annotation USAGES (`@Foo` on a class/method/field) live inside the
declaration's `modifiers` node, which `extractDecoratorsFor` never descended
into — so every annotation usage was dropped. Now it descends into `modifiers`
(also fixes C#/Kotlin annotations, which use the same shape).
- Annotation DEFINITIONS (`@interface Foo` = `annotation_type_declaration`)
weren't in Java's interfaceTypes, so they weren't nodes and the usages had
nothing to resolve to.
Now `@SerializedName` / `@GetMapping` / `@Entity` style annotations link to their
definition (a `decorates` edge), so the annotation file shows its real users and
the annotated class depends on it. gson 78%->85%, retrofit 80.5%. New test +
CHANGELOG; full suite green (1150).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve a Rust qualified path `A::B::C` by mapping the module prefix `A::B` to a
file (crate/self/super-anchored; each segment -> `<dir>/seg.rs` or
`<dir>/seg/mod.rs`) and finding the leaf `C` there, instead of name-matching the
leaf. `emitRustUseBindingRefs` now emits the full path so this can fire.
Fixes common-name re-export collisions: `pub use self::read::read` now links the
correct `fs/read.rs` rather than a stray same-named `read` (ripgrep 81%->87%).
Returns null for non-module prefixes (`Widget::new`, enum variants) so those fall
through to method/name resolution. New collision test; full suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Continues the cross-language blast-radius work for Rust (ripgrep 63%->81%,
tokio src 70%->83% file-dependent coverage).
- Struct literals: add `struct_expression` to the instantiation kinds so
`Widget { n: 1 }` / `m::Widget { .. }` record an `instantiates` edge.
- Trait dispatch (#584 for Rust): extract trait method declarations
(`function_signature_item`) as method nodes so a trait's method set is
first-class, and enable Rust in the interface-override bridge — a struct's
explicit `impl Trait for T` now links `Trait.method -> impl.method`, so a type
reached only through `&dyn Trait` gets dependents.
- `use` / `pub use` binding linking: emit the leaf binding of each use path so a
`pub use self::sub::Item` re-export hub depends on the module it re-exports
(the largest lever) and items imported but used in non-call positions link.
Known remaining Rust frontier: common-name re-export collisions
(`pub use self::read::read`) need Rust module-path resolution; plus macros and
external-trait-only impls. New tests + CHANGELOG.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The file-level dependency queries followed only `imports` edges, which are
same-file in this engine, so `codegraph affected` and getFileDependents returned
0 dependents for every file. Project the symbol-level edge graph instead, then
close the cross-file edge gaps that left real dependencies unlinked per language.
Queries:
- Add getDependentFilePaths / getDependencyFilePaths (one indexed JOIN over all
cross-file edge kinds except `contains`); getFileDependents/getFileDependencies
and circular-dependency detection now use them.
Extraction & resolution, by language:
- TypeScript/JS: extract in-body local type annotations; link every named,
default, re-export (`export {X} from`), and namespace import to its definition
(incl. tsconfig `@/` aliases), with a namespace-import file backstop.
- Python: link `from module import X` bindings (value imports, aliases, __init__
re-export barrels); resolve `from . import sub` + `sub.func()` module-member
access; fix Python dotted-relative import path resolution.
- Go: extract cross-package struct composite literals (`pkg.Type{...}`, incl.
package-level var registries) and `(*T)(x)` type conversions; implicit
interface satisfaction — extract interface methods, synthesize `implements`
edges via method-set matching, bridge interface dispatch to impls (#584).
- C#: index `record` / `record struct` declarations as types (#237).
Measured file-dependent coverage on real repos: this repo (TS) 62.5%->95.8%;
requests (Py) 54%->100%; gin (Go) 62.7%->96.6%; MediatR (C#) 81.5%->85.2%.
Node counts stay stable (edges added, not nodes, aside from real interface/record
symbols). Adds tests and CHANGELOG entries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vue's extractor parsed only the <script> block, so a component used solely
in another component's <template> (`<MyButton />`) produced no reference —
and thus showed a false 0 callers, even after the barrel-resolution fix in
PR #657. This is the Vue analogue of Svelte's extractTemplateComponents.
extractTemplateComponents() now scans the template (everything outside the
<script>/<style> blocks, which also handles nested <template> tags for
v-if/slots) for component tags:
- PascalCase tags (`<MyButton/>`) — captured as-is.
- kebab-case tags (`<my-button/>`) — converted to PascalCase so they match
the imported component's name. Safe: an unmatched name creates no edge
during resolution, so native custom elements just don't resolve.
- Native HTML elements (lowercase, no hyphen) and Vue built-ins
(Transition, KeepAlive, …) are skipped.
Adds no nodes — only `references` — so node counts stay stable. With this
plus #657, a Vue component re-exported through a barrel and used only in a
template now resolves end-to-end (callers/impact/callees).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Component barrels (`export { default as X } from './X.svelte'`) and
monorepo workspace imports (`@scope/ui/widgets`) left the consumer↔component
edge uncreated, so live components showed a false `0 callers` — the canonical
dead-code signal — risking deletion of live code.
The Svelte default-barrel case broke at FOUR layers, each of which alone left
it unresolved:
- findExportedSymbol matched only function/class for a default export, never
`component` (Svelte/Vue SFCs are kind 'component').
- extractImportMappings had no svelte/vue branch, so SFC consumers produced
zero import mappings and resolveViaImport never ran.
- EXTENSION_RESOLUTION had no svelte/vue entry, so relative imports from an
SFC (`./lib` -> `/index.ts`) resolved to nothing.
- getReExports parsed the barrel in the CONSUMER's threaded language, so a
.svelte consumer made extractReExports bail on a .ts index barrel.
Workspace package-subpath barrels get a new workspace-packages module
(mirrors go-module/path-aliases): reads package.json `workspaces`
(npm/yarn/bun) + pnpm-workspace.yaml, maps member name->dir, resolves
`@scope/ui/widgets` -> `packages/ui/widgets`. Gated behind the workspaces
field so single-package repos are unaffected.
Bare `./`/`.` directory imports already resolved; covered with a regression
test. Verified both directions (callers/impact AND callees) for Svelte; Vue
script-level imports also resolve. 4 new tests; full suite green (1126).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>