Commit Graph

454 Commits

Author SHA1 Message Date
Colby McHenry d4b5427499 docs: add measured cross-file coverage table to README; CHANGELOG headline
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>
2026-06-06 11:00:58 -04:00
Colby McHenry ae391f093a Merge remote-tracking branch 'origin/main' into feat/cross-language-impact-coverage 2026-06-06 10:58:33 -04:00
Colby McHenry 8530cc2558 chore: consolidate session handoffs (cross-language impact coverage) 2026-06-06 10:58:33 -04:00
Colby McHenry 2f30a3b16a feat(impact): pair Delphi forms with their .pas code-behind (.dfm/.fmx ↔ .pas)
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>
2026-06-06 10:45:18 -04:00
Colby McHenry 2f57119022 feat(impact): resolve Shopify OS 2.0 JSON template → section references
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>
2026-06-06 10:39:58 -04:00
Colby McHenry 415560986f feat(impact): resolve Lua/Luau require() to module files
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>
2026-06-06 10:30:38 -04:00
Colby McHenry e7b86dfcc6 docs(impact): record why the static-member pass stays off for TS/JS/Python
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>
2026-06-06 09:37:36 -04:00
Colby McHenry 1dec7654f0 feat(impact): Nuxt nested auto-imported component resolution (<MediaCard> → media/Card.vue)
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>
2026-06-06 09:02:34 -04:00
Colby McHenry 3ea03e55ba feat(impact): SvelteKit load→page synthesizer (+page.server.js → +page.svelte)
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>
2026-06-06 00:27:10 -04:00
Colby McHenry bb7659e2f7 feat(impact): Swift property-wrapper attribute arg type refs (Fluent @Siblings)
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>
2026-06-06 00:01:06 -04:00
Colby McHenry 6d214cd32c feat(impact): extract Rocket route-registration macros (routes![]/catchers![])
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>
2026-06-05 23:48:29 -04:00
Colby McHenry 7bb958b215 feat(impact): resolve multi-segment Rust module-path calls (a::b::c())
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>
2026-06-05 16:39:23 -04:00
Colby McHenry a3f59fbd08 feat(impact): Rust self-relative submodule call resolution (X::router())
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>
2026-06-05 15:31:09 -04:00
Colby McHenry bfa84d32b8 docs(readme): drop "!" from waitlist button, version-tag README images
- 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>
2026-06-05 00:54:10 -04:00
Colby McHenry 2a7b34d5a3 docs(assets): redesign waitlist SVG button with outlined Archivo typeface and brand palette
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.
2026-06-05 00:38:24 -04:00
Colby McHenry b653688f1b chore: add session handoff notes and update docs to reflect explore/node tools
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.
2026-06-05 00:17:45 -04:00
Colby McHenry c95ead5c4e docs(readme): add getcodegraph.com waitlist banner
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>
2026-06-04 19:19:27 -04:00
Colby McHenry 9e5a9511b9 feat(impact): Razor @using namespace disambiguation (incl. _Imports.razor)
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>
2026-06-04 19:17:06 -04:00
Colby McHenry dc7d033106 feat(impact): C# namespace extraction (qualify type names)
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>
2026-06-04 19:13:31 -04:00
Colby McHenry 4589cf97eb docs: mark template-markup-parser P1/P2/@code as implemented
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:04:49 -04:00
Colby McHenry 90c5f39568 feat(impact): delegate Blazor @code / Razor @{} blocks to the C# extractor
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>
2026-06-04 19:03:56 -04:00
Colby McHenry 59b8de21fd feat(impact): Razor/Blazor (.cshtml/.razor) markup parser
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>
2026-06-04 18:51:57 -04:00
Colby McHenry 2a0b6e001d fix(impact): link renamed default imports to the module file (Express controllers)
`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>
2026-06-04 18:06:07 -04:00
Colby McHenry 4c144134b9 fix(impact): resolve chained method calls (a.b.Method()) — C# extension methods
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>
2026-06-04 18:00:35 -04:00
Colby McHenry 58dc463330 fix(impact): Django include('app.urls') links project urlconf → app urls
`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>
2026-06-04 17:34:20 -04:00
Colby McHenry 283562305f fix(impact): source-aware Python from pkg import submodule (FastAPI aggregator)
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>
2026-06-04 17:25:11 -04:00
Colby McHenry 61a993a0ce fix(impact): link Python absolute whole-module imports (import a.b.c)
`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>
2026-06-04 17:08:57 -04:00
Colby McHenry 529d822879 fix(impact): same-dir C/C++ includes + KMP commonMain imports (multi-platform coverage)
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>
2026-06-04 16:28:54 -04:00
Colby McHenry 082353eb7f fix(impact): gate cross-known-family references/imports (TS↔native name collisions)
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>
2026-06-04 15:48:26 -04:00
Colby McHenry 74b599c86b feat(impact): RN native→JS events via the sendEvent(ctx, "X", body) wrapper
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>
2026-06-04 15:13:58 -04:00
Colby McHenry d06a5ec890 feat(impact): extract RCT_EXPORT_METHOD / RCT_REMAP_METHOD as ObjC method nodes
`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>
2026-06-04 15:06:33 -04:00
Colby McHenry 4a64ca56cd feat(impact): classic RN NativeModules cross-platform pairing (Android @ReactMethod ↔ iOS impl)
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>
2026-06-04 14:55:46 -04:00
Colby McHenry dbc4862940 feat(impact): React Native / Expo cross-language bridges — generic Expo Functions, cross-platform pairing, type-ref precision
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>
2026-06-04 14:18:11 -04:00
Colby McHenry 33ce43194c feat(impact): Objective-C — single-arg selectors, class-message receivers, #import, class-method resolution
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>
2026-06-04 13:36:23 -04:00
Colby McHenry 857baf76b8 feat(impact): cross-language static-member / value-read pass — link Enum.value, Type.CONST, Foo::BAR
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>
2026-06-04 12:29:38 -04:00
Colby McHenry 94879546bd feat(impact): Dart mixins (with) + method type references
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>
2026-06-04 11:53:58 -04:00
Colby McHenry ec8fe3f1f4 feat(impact): C++ free-function name extraction — stop naming functions after a param/return type
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>
2026-06-04 11:06:05 -04:00
Colby McHenry 5bccab6afe feat(impact): Ruby require/require_relative resolution — link to the required file
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>
2026-06-04 02:15:13 -04:00
Colby McHenry 44fb978597 feat(impact): Ruby mixin coverage — link include/extend/prepend to the module
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>
2026-06-04 02:07:00 -04:00
Colby McHenry acfb4445a1 feat(impact): PHP namespace capture + use-import resolution + type-hint refs
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>
2026-06-04 01:55:46 -04:00
Colby McHenry b5489d916c feat(impact): Scala type-graph coverage — parameterized extends, type refs, implicit params
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>
2026-06-04 01:32:28 -04:00
Colby McHenry d8a2e91b2f feat(impact): Kotlin Multiplatform expect/actual linking — bridge common decls to platform impls
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>
2026-06-04 01:09:38 -04:00
Colby McHenry d111f26573 feat(impact): Swift property wrappers & attributes — link @Argument/@Published/etc.
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>
2026-06-04 00:18:17 -04:00
Colby McHenry badb12481f feat(impact): Java annotation coverage — index @interface defs, link @Foo usages
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>
2026-06-03 23:59:12 -04:00
Colby McHenry 2ac7df58f7 feat(impact): Rust module-path resolution for qualified use/path references
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>
2026-06-03 23:49:34 -04:00
Colby McHenry b538aee853 feat(impact): Rust cross-module coverage — struct literals, trait dispatch, use-binding linking
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>
2026-06-03 23:37:41 -04:00
Colby McHenry 16b5633749 feat(impact): fix affected/blast-radius and expand cross-file dependency coverage
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>
2026-06-03 23:17:43 -04:00
Colby Mchenry 629d8472b1 fix(extraction): index Vue <template> component usages (#629 follow-up) (#659)
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>
2026-06-02 21:44:27 -05:00
Colby Mchenry bdfd55e69c fix(resolution): resolve Svelte/Vue component barrels & workspace imports (#629) (#657)
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>
2026-06-02 21:37:56 -05:00
Colby Mchenry 7b62356f53 feat(cli): add version, indexPath, lastIndexed to status --json (#329)
Adds `version`, `indexPath`, and an ISO `lastIndexed` to `codegraph status --json`, plus a `CodeGraph.getLastIndexedAt()` library method. `agentCount` dropped (no clear consumer). Reworked from contributor PRs #333 and #480.

Co-Authored-By: Javier Gómez <199902626+12122J@users.noreply.github.com>
Co-Authored-By: Ran <8403607+eddieran@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 17:50:33 -05:00