Skip to content

[pull] main from colbymchenry:main - #61

Open
pull[bot] wants to merge 822 commits into
codingwatching:mainfrom
colbymchenry:main
Open

pull[bot] wants to merge 822 commits into
codingwatching:mainfrom
colbymchenry:main

Conversation

@pull

@pull pull Bot commented Jun 25, 2026 •

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

@pull pull Bot locked and limited conversation to collaborators Jun 25, 2026
@pull pull Bot added ⤵️ pull merge-conflict Resolve conflicts manually labels Jun 25, 2026
github-actions Bot and others added 27 commits September 29, 2026 05:06
[skip ci] Auto-generated by Release workflow.
The 1.6.1 bump changed package.json only, as AGENTS.md said was enough, so
ui-package.test.ts ("is versioned with the engine") has failed on main since.
Bump ui/package.json to match, and say in AGENTS.md that a version bump
touches both files.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…, prefix groups, the /api mount (#2099)

Routes were named by the bare literal in the routes file, so BookStack's
api.php showed `GET pages` for `/api/pages` and koel's `Route::prefix('api')`
group showed `GET ping` for `/api/ping`. Beyond the label, a front-end
`fetch('/api/pages')` could never pair with a route recorded as `pages`.

- extract(): `Route::prefix()` chains and `Route::group(['prefix' => …])`
  scope their routes, nested ones in order; a computed prefix is kept as a
  `{prefix}` segment. Paths are joined the way Laravel joins them (leading
  slash, no doubled or trailing slashes). qualifiedName now holds that
  in-file path.
- postExtract(): finds where each routes file is mounted — a group whose
  argument is the file, a require / include / loadRoutesFrom inside a
  group, Laravel 11's `withRouting(api:, web:, apiPrefix:)` — composes the
  prefixes down the include tree, and names every Laravel route from its
  qualifiedName plus its file's prefix (idempotent; a removed mount is
  undone). routes/api.php defaults to `/api` unless something mounts it or
  a `withRouting(using:)` loader might. A file mounted at two prefixes stays
  at its in-file path. Only files in routes/ or naming `routes/` /
  `withRouting(` are read.

On BookStack all 80 api.php routes become `/api/…` (from its
RouteServiceProvider group); on koel all 118 become `/api/…`; no route is
left without a leading slash.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…it reaches (#2100)

Tests reach a controller by URL, never by a call — MockMvc's
`perform(post("/owners/new"))`, Laravel's `$this->postJson('api/me')` — so
every controller those suites exercise read as untested: the viewer's
"No test reaches this" and explore's `tests:` line both walk callers.
petclinic's processCreationForm, hit three times by OwnerControllerTests,
said no test reached it.

A new synthesis pass (tier-synthesizer section 4, registered after the
cross-tier pass, gated on java / kotlin / php) reads test files only:
- Spring: MockMvc builders (with MockMvcRequestBuilders imported),
  `request(HttpMethod.X, …)`, the Kotlin `mockMvc.get(…)` DSL,
  WebTestClient `.get().uri(…)`, TestRestTemplate, RestAssured.
- Laravel: `$this->get/post/…Json(…)`, `->json('POST', …)`, `->call(…)`
  on a chain rooted at `$this` that only calls methods (so
  `$this->app->get('config')` is not a request); Pest's helpers where the
  file imports Pest\Laravel; and a project's own verb-named helpers
  (koel's getAs → jsonAs → `$this->json($method, $uri)`), recognised by
  their body, never by name alone.
Paths are read as built — Spring `{var}` templates, PHP / Kotlin
interpolation and concatenation become holes — and matched with the
cross-tier pass's own route matcher (a hole fills a parameter, never a
literal). One `calls` edge per (test, route), `synthesizedBy:
'test-request'`. These edges are excluded from countIncomingEdges, so a
well-tested endpoint never becomes a hub the Steps walk refuses to enter.

Routes reached by a test: petclinic 16/17, realworld 18/19, BookStack
194/340, koel 105/134 (from 0 each). A random sample of edges checked
against the test source was all correct. The pass costs 86 ms on
BookStack and 68 ms on koel.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…ns on its packages (#2102)

A Maven / Gradle project keeps every line of Java under
src/main/java/org/<company>/<app>/, and those folders hold nothing but the
next one. The Map cut one folder per level, so petclinic drew the whole
program as one `src/main/java/org` box, and the deepest grouping option
(4) stopped at `src/main/java/org/springframework` — no setting reached the
packages seven levels down.

A folder with exactly one subfolder and no file of its own (read from
every indexed file, tests included, so ids don't move when tests are
toggled) now joins the level below it. moduleIdFor takes the set as an
optional fourth argument — without it nothing changes — and
pickDefaultDepth counts the same levels. Collapsing never splits a group
at the same depth (a pass-through folder has one child), so repos without
such chains keep their grouping; a lone chain like koel's
`app/Console` → `app/Console/Commands` is renamed to the folder that holds
the files.

The box label (the wire `label`, which nothing read) now elides a chain of
three or more folders — `src/main/java/…/petclinic/owner` — and ModuleNode
and the width calculation use it; the tooltip and side panel keep the full
path. Everywhere else the label equals the id, so other maps look the same.

petclinic's default map: 8 boxes (owner, vet, model, system, the package's
root files, resources) instead of one; realworld opens on io/spring's api,
application, core, graphql, infrastructure.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…ls are functions (#2103)

Found testing `codegraph ui` on Spring and Laravel: a server-rendered
endpoint's Steps picture never said how it answers. petclinic's POST
/owners/new showed its database write and no reply; BookStack's book page
showed a 404 and not the page it renders.

Spring MVC answers by what a handler RETURNS, which is no call:
- A new tree-sitter reader, returnsInTree / returnsForFile (graph/
  branch-guards.ts, exposed as SiteReader.returns), lists the returns a
  definition makes itself — never one in a lambda, a local function or a
  nested / anonymous class — and the string a same-class constant holds.
- steps.ts: for a mapped method of a class that renders views (not
  @RestController, not @responsebody), a returned view name / constant is a
  200 render, `"redirect:…"` a 302, `"forward:…"` a forward, each under the
  condition it is returned in (effectLink takes a preset effect for a site
  that is no call). Non-literal returns are not guessed at.
- effects.ts: `new ModelAndView(…)` (200, or 302 for "redirect:…") and
  `new RedirectView(…)` (302) are replies.

Laravel's `view(…)` and `redirect(…)` were already reply rules, but never
reached them:
- name-matcher.ts: a PHP call written without a receiver is a function
  call, so only a `function` is a candidate — BookStack's every
  `return redirect(…)` bound to ApiDocsController::redirect, every
  `view(…)` to a `$view` field (and, through the fuzzy fallback, to the
  class `View`). PHP refs record the call expression's column, so the call
  text at that column decides. koel alone loses 60 such wrong edges
  (`basename()`, `auth()`, `value()`, …).
- effects.ts: the PHP reply rule never matched a chain — rules see the text
  with argument lists stripped (`response->json`), the rule wanted `()->` —
  so `response()->json(…)`, `redirect()->route(…)`, `redirect()->back()->
  withErrors(…)` were never replies; `to_route` added; redirect chains are
  302s. PHP's SPL programming-error exceptions (InvalidArgumentException,
  LogicException, …) are no longer replies, and a namespaced exception is
  read by its class name.

petclinic POST /owners/new: 200 `view owners/createOrUpdateOwnerForm` WHEN
result.hasErrors(), 302 `redirect:/owners/${…}` WHEN !result.hasErrors().
BookStack GET /books/{slug}: 404, 200 `view`, 302 `redirect` (the
InvalidArgumentException from a helper is gone). realworld's REST login is
unchanged.

Not in scope: PHP has no site reader (no branch-guard rules), so Laravel
replies carry no WHEN and no arguments — `abort(Response::HTTP_…)` and
`response()->json()` show without a status.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…t edit (#2104)

The symlink retarget/restart test flaked inside the full suite with
"waitFor timed out". A traced failing run showed the edit made right after
unwatch()+watch() was never reported: fs.watch() returns before macOS
registers the stream (libuv serves a process's watches from one FSEvents
stream, rebuilt on its own thread after each watch/close), and an edit made
in that gap is dropped, not delayed — so no timeout would have helped.

Poke a probe file under the link until the watcher reports it, then make the
edit under test once. Applied after the restart (the observed failure) and
after the retarget (the same race with a 100-300ms gap). Under a simulated
300ms registration delay the old test fails 3/3 and the new one passes 3/3;
a dead post-restart stream still fails it.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…ding (#2105)

`MCP staleness banner > lists pending files under "Pending sync"` flaked in
the full suite (2 of 4 runs): status showed "0 modified" and no Pending sync
section. The tests used `debounceMs: 4000` to keep an edit pending while they
queried, but since #1397 a lone edit syncs after a 300ms quiet window whatever
the configured debounce. codegraph_status awaits a freshly spawned worker to
count changes before reading the pending set, and under load that outlasted
the window, so the sync had already cleared the entry.

A 500ms pause before the query fails the status, banner and #1968 path tests
deterministically. Hold the watcher's sync open with a never-settling spy in
those four setups instead; no assertion changes, and the test that checks the
banner drops after a sync still runs a real one.

Co-authored-by: Claude Opus 5.5 <[email protected]>
`use: SearchController.show` kept only `show`, and name matching bound
every controller's `show` route to whichever `show` came first — on
SwiftPackageIndex-Server 7 of 13 routes pointed at another endpoint's
code (Search, HealthCheck, ValidateSPIManifest and ReadyForSwift6's
`show` all at AuthorController.show).

The route's handler ref now keeps the type: `Type@method` (dotted for
nested types, `API.PackageController@get`), `@method` for `self.x` and a
bare `x`, and a `let todos = TodoController()` receiver's type. The Vapor
resolver claims these and resolves on the owner's qualified name:
nested types, handlers declared in `extension Type` in another file, and
`extension API.PackageController` (whose node carries only the last
segment, so its declaration line decides). `self.x` / bare `x` take the
method of the type the route is written in. Of one type's overloads, the
one declared to take a `Request` wins. Two types, or none, leave the
ref unresolved rather than guess.

Co-authored-by: Claude Opus 5.5 <[email protected]>
`extension View { … }` parses as a class_declaration, so the index held a
`class View` node for every file extending SwiftUI's View, Text, Color,
String, URL. With no declaration of the type in the project, every
`struct X: View`, `Text("…")` and `Color.red` bound to whichever
extension came first: on IceCubesApp 6 of the top 12 "most depended on"
symbols were extension files (View 632 edges, Text 402, Label 209). And
a bare `@State` bound to a view model's nested `enum State` (253 edges),
`@Test` to a test's nested struct (821 on SwiftPackageIndex), `: Error`
to a nested `IOError.Error` (233 on swift-nio).

A gate at resolveOne's seam (every strategy, framework patterns and
name matching alike) checks a Swift reference's type target against what
the site wrote:
- an extension moves to the declaration it extends, or the ref stays
  unresolved when the SDK declares the type;
- a bare name of a nested type needs the site inside its parent, the type
  itself, or a subtype that declares inheriting from the parent;
- a qualified name must fit its path. The index keeps `Build.Id` as a ref
  named `Id`, so the qualifier is read back from the source line
  (generic arguments stripped: `EventLoopFuture<T>.Isolated`); a member
  indexed top-level because an `#if` ended its type's body early still
  fits a parent declared in its file.
Redirects pick the unique declaration in scope, then the same file, then
the nearest directory; a tie is left unresolved.

Two things rode on the wrong edges and are kept:
- `getSupertypes` named `View` for `struct X: View` only because that
  edge resolved to an extension. Swift conformances to a type the
  project only extends are now read from the declaration head (once per
  node, memoized; a per-node unresolved_refs query was +75% index time on
  swift-nio), so `X().withEnvironments()` still resolves to the
  `extension View` method.
- interface-impl dispatch read a protocol's methods off the node the
  conformance landed on. Swift protocol requirements are not extracted as
  methods; the default implementations live on `extension P` nodes, which
  are now included — every extension, not one arbitrary one (+359
  dispatch edges on swift-nio, +44 on Alamofire, none lost).

Measured edge diffs vs main, every removal classified:
IceCubesApp 2473 removed (172 redirects to the declaration, 1989
SDK-extension edges, 280 wrong bare nested-type bindings, ~30 from the
class the typealias parse error swallows); SwiftPackageIndex 3810 (1461
redirects); swift-nio 10991 (5085 redirects); Alamofire 1368 (198). Index
time at parity (swift-nio 4.1s vs 4.0s, Alamofire 1.33 vs 1.37,
IceCubes 1.31 vs 1.31).

Co-authored-by: Claude Opus 5.5 <[email protected]>
…ts type (#2108)

typealias EditorClient = AutocompleteService.Client
      & MediaUploadService.Client

is valid Swift, but in a type's body tree-sitter-swift ends the member at
the newline and the `&` line is an ERROR that swallows the enclosing
type. IceCubesApp's 980-line `EditorStore` came out as loose variables
and functions — no class, no init, no methods — so a call in its init
counted as file-level code ("a file that runs something"), and calls
into its methods from the views landed on same-named methods of other
services.

A Swift preParse moves a line-leading `&` onto the line before, for an
indented `typealias`/`let`/`var` statement's continuation lines only.
Same length and line count; the continuation line's tokens sit one
column left (the Swift type gate reads the site's qualifier at that one
column over). Measured shapes: the failure is type bodies only — a
top-level declaration, a function body and a parenthesized type parse as
written, and are left alone; `&&`, `&+` and inout `&x` are never
touched. The preParse is hoisted before the kernel call like the C/C#
ones, so both arms parse the same bytes, and the file no longer defers
to wasm.

Across IceCubesApp, SwiftPackageIndex-Server, swift-nio, Alamofire, TCA
and mastodon-ios (3,000 Swift files) it rewrites exactly the one failing
file, which now parses clean; none newly erroring. EditorStore is a class
again with 38 methods; views now reference it and call its
insertStatusText / runAssistant (62 edges into it), and
`urlSession.upload` no longer lands on MediaUploadService.upload.

Co-authored-by: Claude Opus 5.5 <[email protected]>
Three things IceCubesApp showed in `codegraph ui`:

Steps labelled calls that leave the index by other stacks' names:
`rawValue.data(using: .utf8)` network (25 boxes), Foundation's `Timer`
telemetry, `Calendar` and a SiriKit `intent` device, the app's own
`Notifications` endpoint enum device (expo-notifications' row), and
`viewModel.votes.firstIndex(of:)` a database read (the `\w*Model` row).
Now: a Swift `.data/.upload/.download/.bytes` is network only on a
`*session` receiver; Timer/Calendar/intent are no effect in Swift; a
`viewModel` is not a table (any language); a keychain receiver is
storage ahead of Swift's generic `x.delete` database row. A call whose
receiver is a type the Swift project declares — not an `extension X {}`,
which declares nothing (read through the site reader) — no longer
matches the library-name rows; the model and reply rows, written for
project types, still do.

"Files that run something" listed every view with a `#Preview { … }`:
the macro sits at a file's top level, so its constructions were
module-level edges. Entry points now subtract calls inside a Swift
file's top-level previews (trailing closures included — WidgetKit's
`} timeline: {`), drop a file left with none, and re-rank.

Every `struct X: View` had a one-line `component` twin, and `@main`
apps, view controllers and UIView subclasses a `class` twin, from the
SwiftUI/UIKit resolvers' regex extract(): 188 nodes on IceCubesApp, 184
on mastodon-ios, zero edges into any of them — dead ends in search and
the symbol view. The two extract()s are removed.

IceCubesApp's effect sweep is now URLSession-only network, no
telemetry, no database, keychain storage; its entry files drop from
eleven preview files to its one real script. SwiftPackageIndex's Fluent
database and reply steps are unchanged.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…apps (#2113)

An Angular app had no route nodes, no navigates edges and no link from a
template to what it renders — the viewer's Steps tab said "No screens or
endpoints" and there was no Screens tab.

Routes (frameworks/angular-router.ts): every `Routes` array — typed
`Routes` / `Route[]`, `RouterModule.forRoot/forChild`, `provideRouter`, a
routes file's `export default [...]` / `as|satisfies Routes` — walked as
objects. `component` binds by `references` (route-roots takes a class as
a named handler); a lazy `loadComponent` binds to the file's `@Component`
class. `children` join their parent's path; a lazily loaded file's routes
are put under the path that loads them in postExtract (following an
NgModule `loadChildren` to the routing module it imports), idempotently
from the in-file path kept in the qualified name. A route with children
is a layout: a screen only where no child claims its address, and linked
from each screen inside it (`references`, `layout: true`). Paths written
as route constants (`internalRoutes.account.path`, through destructured
locals and `const x + '/:id'`) and `$localize` strings are read from the
constant object. `redirectTo` aliases an address to the screen it sends
the user to; a root `**` redirect answers `/` only. A `matcher` route and
a `**` catch-all are not screens.

Navigation: `router.navigate([...])`, `navigateByUrl`, a guard's
`createUrlTree` / `parseUrl` — a command array (non-literal elements are
holes), a static string, a route constant, a component property holding
one, a function constant called with arguments, `.concat(id)`. A
`relativeTo` navigation, a query-only `navigate([])` and a destination
made only of holes (it would match any route of its length) are left
unresolved.

Templates (angular-template-synthesizer.ts): a component's `templateUrl`
file or inline `template:` is read at synthesis time. `<app-foo>` by
element selector (same app first; two candidates = none) is a `calls`
edge from parent to child (`angular-template`), the edge a navigation in
a child component rides to its screen. `routerLink` / `[routerLink]` and
a `routerLink:` field written in the class (a tab bar's config) are
navigates edges. Gated on an `@angular/core` dependency.

Measured:
- angular-realworld: 10 routes all bound; 11/11 navigation sites
  accounted for (10 resolved, 1 query-only); 31/31 routerLink sites
  resolved; 18 renders.
- Ghostfolio: 84 routes all bound, 0 unresolved constant paths; 27
  navigate resolved, 23 correctly not (8 relative, 9 query-only, 2
  computed, 4 aimed at a matcher route); 162 routerLink edges, every one
  checked against its site; 170 renders.
- ngx-admin (NgModule): 55 routes, 47 bound (8 are @nebular/auth's); no
  navigation calls in the source.
- Controls byte-identical vs main (nodes and edges incl. metadata):
  koel, IceCubesApp, spring-petclinic, proshop. Ghostfolio index time at
  parity.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…ds (#2114)

An Angular handler's only caller is its template: `(click)="toggleFavorite()"`,
`(ngSubmit)="submitForm()"`. With no edge, the method showed no callers,
impact stopped at it, and the Steps picture of a screen reached its
handlers only through class containment, with nothing to say what fires
them.

The template pass now reads each `(event)="…"` binding — not
`[(ngModel)]`'s two-way half — and links the component to every method of
its own the statement calls (`save()`, `this.toggle(x)`; not
`closed.emit()` or `form.reset()`), as a `calls` edge
(`synthesizedBy: 'angular-event'`) carrying the binding as
`metadata.trigger`: `{ kind: 'prop', name: '(click)', of: 'button' }`.
The binding is in the template, so it cannot be read back from the source
at the edge's line; Steps now takes a trigger an edge carries before
reading one at the site.

angular-realworld: 21 binding sites → 17 edges (3 repeats of a method and
event, 1 `delete.emit(true)`); Steps for /article/:slug draws
deleteArticle on (click) · <button> → DELETE and back to /, addComment on
(ngSubmit) · <form>, the favorite button's toggle → POST/DELETE or
/register. Ghostfolio 225 edges of 302 sites, ngx-admin 75 of 100 — the
rest are `$event.stopPropagation()`, output emits, assignments and
repeats. Controls (koel, proshop) byte-identical.

Co-authored-by: Claude Opus 5.5 <[email protected]>
)

`app.setGlobalPrefix('api')` and `app.enableVersioning({ type:
VersioningType.URI, defaultVersion: '1' })` put every controller under
`/api/v1`, but routes were named by the controller path alone (`GET /user`).
A front end's `this.http.get('/api/v1/user')` then linked to nothing:
Ghostfolio's Angular client had no cross-tier edge into its own NestJS API.

postExtract now reads each `NestFactory.create` bootstrap and renames the
routes of controllers in that app, in NestJS's order:
`/{prefix unless excluded}/{v}{version}/{RouterModule prefix}/{path}`.

- `exclude` paths (plain, `{ path, method }`, `{/*wildcard}`, `(.*)`, `*`)
  skip the prefix and keep their version, as NestJS does.
- A route's version: its own `@Version` (found past decorators with object
  arguments), else the class's `@Version` or `@Controller({ version })`,
  else `defaultVersion`. `VERSION_NEUTRAL` has none; header/media-type
  versioning adds no segment.
- A computed prefix (`config.get(...)`) is not guessed.
- Each app uses its own bootstrap (the longest root above it). A seed script
  that creates the same app without serving it doesn't displace the one that
  configures it, and an app with no config doesn't borrow another app's.
- Only routes whose decorator sits in a `@Controller` are touched, so an
  Express route in the same repo keeps its name.

Ghostfolio: 0 → 103 cross-tier edges (82 of 85 client call sites), no
mismatches. koel, proshop, IceCubes and nest-samples are byte-identical to main.

Co-authored-by: Claude Opus 5.5 <[email protected]>
)

`API.PackageController.GetRoute.query(on:)` kept only `query` — the
receiver was a navigation_expression, not an identifier — and the bare
name exact-matched whichever type's `query` came first. On
SwiftPackageIndex-Server every route type has one, so health checks and
package-controller tests "called" the dependency controller (or a test's
own `query`).

Extraction (TS and the kernel's swift walker, byte-parity): a receiver that
is a path of capitalized type names, two segments or more, not led by
`Self.`, is kept: `API.PackageController.GetRoute.query`. Instance chains
(`self.store.load()`) and mixed paths (`URLSession.shared.data`) are
unchanged.

Resolution (swift-type-visibility.ts, called decisively from
matchReferenceInner so nothing falls through to name guessing): the member
on the type the path names — a method, an enum case with associated values,
or a nested type's initializer. A member's owner path is its qualified name
with the outermost extension's written path read back from its line
(`extension API.PackageController { enum ShowRoute … }` →
`API::PackageController::ShowRoute`), so twin `Feature::State`s declared in
`extension BasicsView.Feature` and `extension ObservableBasicsView.Feature`
are told apart. A path shortened inside its namespace or behind a module
qualifier fits; two owners, or none, leave the call unresolved. TCA's
`@Reducer enum Path { case detail }` generates `Path.State`/`Path.Action`,
so `Path.State.detail(…)` lands on the written case.

A/B vs main (edge diffs, every change classified): SPI 76 retargeted, all to
the type the line names, 7 dropped (Fluent `App.Version.query`, `Plot.Node.*`
— SDK members main pinned on project methods); TCA 61 retargeted, 1 dropped
(`State.StateReducer.scope` → an unrelated `Store::scope`); mastodon-ios 177
retargeted (`Mastodon.API.V2.Instance.instance`, `L10n.Plural.Count.vote`),
11 SDK drops; swift-nio 6 calls into a deprecated alias became instantiates
of `ChannelOptions.Types.*`; IceCubes 1/1, Alamofire 2 SDK drops.
Kernel/wasm parity 0 diffs over 2,752 files on six repos.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…lled (#2119)

`codegraph ui` re-execs itself with `--liftoff-only`; the shim the user
started blocks in spawnSync and cannot forward a signal. Killing it by pid —
a process manager, an IDE task, `kill` — left the re-exec'd server serving
its port forever (found during a sweep: servers from hours earlier were still
answering on ports 4801–4808 against deleted indexes, holding their stdout
pipes so the harness that started them never exited). Ctrl+C was unaffected
(the signal reaches the whole process group).

`index`/`init` already guard this with the #277 PPID watchdog. Factor it out
of installCommandSupervision as `watchParent(onLost)` and use it in `ui` with
the same shutdown as SIGINT/SIGTERM (close the index, then the socket). The
liveness watchdog is deliberately not installed for `ui`: a long Steps
computation can legitimately hold the event loop.

Test: POSIX end-to-end through the real relaunch (CODEGRAPH_WASM_RELAUNCHED
unset, 200ms poll) — SIGKILL the shim, the port stops answering; confirmed
failing without the fix.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…when the code rules it out (#2120)

Found by the README-wide sweep: the "most depended on" lists of popular
repos were topped by members that nothing actually calls.

- A member call on a NAMESPACE import (`import * as z from "zod/v4"`) calls
  one of the module's exports, never some class's method; a receiver
  imported from a package outside the repository names nothing in it.
  matchMethodCall's name/word-overlap strategies no longer run for either.
  zod: `z.string()` had bound 2,640 calls to a test helper's `string`
  getter (Mocker::string topped the hub list with 3,209), `z.toJSONSchema`
  371 to ZodType::toJSONSchema; trpc `z.record` to a generated SDK class.
- A receiver-less JS/TS or Go call cannot reach a PROPERTY, FIELD or enum
  case either (it already excluded methods, #1714/#1857): typeorm's mocha
  `it(…)` bound 2,879 calls to an interface's `it` property and `describe(…)`
  1,368 to a command class's `describe` string field.
- A bare call to a name the file imports from outside the repository
  (`import { useQuery } from '@tanstack/react-query'`, CommonJS
  `require('supertest')`, vitest's `test`) gets no name-matched candidate.
- The React resolver leaves an imported component/hook/context to import
  resolution (framework resolution runs first and picked any same-named
  project hook — trpc tests' `useQuery` landed on a hook nested inside one of
  trpc's factories), and never targets a nested hook; same-file hook first.
- "Outside the repository" is the coordinator's `isOutOfRepoImport`:
  isExternalImport (aliases, workspace members) AND not the repo's own package
  name AND unresolvable by resolveImportPath — so axios's own tests'
  `import { getAdapter } from 'axios'` now resolve to lib/adapters.js instead
  of the .d.ts interface member.

A/B vs main (edge diffs, every change classified): zod −5,228, typeorm
−4,318, express −700 (calls onto the `var request = require('supertest')`
binding), trpc −350/+17, excalidraw −233/+3 (props callbacks onto Props
interface members), hono −150, axios −52/+23 (the self-import upgrade),
bulletproof-react: 156 sites that pointed at ANOTHER app's component/hook in
the three-app monorepo now point at the importing app's own, 16 wrong
cross-app links dropped, 8 imports the React heuristic had guessed right lost
(per-app tsconfig paths don't resolve — separate follow-up); gin/cobra
byte-identical, react-redux-realworld byte-identical.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…-named member (#2121)

Found by the README-wide sweep: AutoMapper's most-depended-on list held a
test class's `List` property (519) and an attribute's `Type` property (259);
eShop's held MSTest's `TestContext` and MAUI's `Easing` as properties.

A `references`/`type_of`/`returns` ref whose SITE is a type position — read
from the source at its column: `Type name` (a declaration names its type
first), `List<`, `Type?` (not `?.`), `Type[]`, a generic argument, `new X`,
VB's `As` / `New` / `Of` — and every `instantiates` ref (its column is the
`new`) now has only type candidates in exact-name and fuzzy matching: a
property, method (a constructor is one), field or enum case sharing the name
is not one. With the member out of the way the real type wins: `TypeMap
typeMap` now links the `TypeMap` class instead of a `TypeMap` property beside
it. Anything else — `Builder.Services`, `MapGet("/x", GetItems)`, a route's
reference to its action — keeps every candidate.

A/B vs main (edge diffs, every change classified): AutoMapper −1,871/+777,
Newtonsoft.Json −3,612/+599, serilog −447/+187, eShop −78/+15,
CleanArchitecture −9. Losses are BCL/framework types that had been pinned on
project members (List, Type, DateTime, CancellationToken, Exception,
TimeSpan, Easing, TestContext…); gains are the project's own types
(TypeMap, MemberMap, LogEvent, LogEventProperty, TypeNameHandling,
JTokenReader, OrderStatus). An earlier cut that dropped every member
candidate also dropped method groups, `Builder.Services` reads and ASP.NET
route→action references — the position reading keeps all of those.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…re not hubs (#2122)

Found by the README-wide sweep: vendored bundles topped "most depended on"
across many repos — delphimvcframework's bundled `Buffer` (6,162) and four
`n`s (2,386 each), retrofit's docs `main.js` `t`/`e`/`i`, TestBox's
`__webpack_require__` — because only `*.min.js` was recognized as generated.

- detectGeneratedFile (index time, persisted as files.generated) now also
  flags a .js/.mjs/.cjs file whose text is mostly lines of 1,000+ chars AND
  whose long lines are code (≥3% `;{}(),`) — not an ordinary file carrying
  one long base64 string — and any file that defines webpack's
  `__webpack_require__` loader, however readable its lines. The path rule
  also matches `-min.js` (underscore-min.js).
- Entry points' hubs skip nodes in generated files, as they skip tests.

Scanned every .js file of express, axios, lodash, excalidraw, typeorm, zod,
trpc, hono, bulletproof-react: zero new flags on hand-written code; newly
caught: lodash vendor/underscore/underscore-min.js, retrofit website
main.js (754 KB) + prism.js, TestBox syntaxhighlighter.js (webpack).

Co-authored-by: Claude Opus 5.5 <[email protected]>
…2123)

Found by the README-wide sweep: cats' most-depended-on symbols were an
`implicit def A` (4,916) and a `case class B()` declared inside a test
method (3,573) — every type parameter `A`/`B`/`F` in the library bound to
them; getx's `T` to a constant `T`; serde's `E`/`T`/`D`/`S`/`F` to test
structs.

New seam gate (type-parameters.ts, applied in resolveOne so every strategy
obeys it):
- A resolved TYPE reference whose name an enclosing declaration declares as
  a type parameter is that parameter: read from the heads (the index records
  none) — after the declared name (`class Foo<T>`, `fn f<T>`, `func F[T any]`,
  `def f[F[_]: Monad, A]`), before it detached from a type (`<T> T max(`,
  `fun <T> f`), a C++ `template <…>` above; annotations (`@sp(Int) A`),
  variance and keywords skipped; a `[…]` list counts only [] and () so
  Scala's `B >: A` bound does not close it; lists up to 40 lines.
- Scala: a bare name a `def` takes as a value parameter (`(f: A => B)`,
  `(implicit G: …)`) is that parameter — cats bound 846 `f(a)` calls to a
  case class's `f` field. Class parameters are fields and are left alone.
- Scala: a BARE (in the source) type reference reaches a member of another
  type only from inside it, a subtype, or a file importing its members;
  otherwise the in-scope type of that name is taken when one fits (cats'
  `trait FlatMap`, not `Eval`'s nested `FlatMap`; sttp's `trait Backend`).
Name matcher:
- A type (class/struct/enum/interface/trait/alias) declared inside a
  function is only reachable from inside it, like nested functions (#1230).
- A capitalized Scala type position never names a method.

A/B vs main (edge diffs, every change classified; each arm's init exit code
checked — an earlier cut crashed resolution on a Scala method named `*`,
now pinned by the test): cats −25,281/+179 (the gains retargets to the
in-scope FlatMap/State; the real types Shown and IsEq kept 5/5 and
315/315), sttp −1,237/+23, getx −430, serde −446/+9, typeorm −632/+152
(`EntityTarget<Entity>`'s type parameter; codemod fixtures' function-local
classes), gson −9; okio/fmt ±2, Newtonsoft.Json and gin byte-identical.

Co-authored-by: Claude Opus 5.5 <[email protected]>
Found by the README-wide sweep: ripgrep bound 469 `Some(x)` calls to its
own `EncodingMode::Some` variant and 536 `Ok(x)` to `ParseResult::Ok`;
serde bound its `Ok(…)`/`Result<…>` to the `struct Ok` / `struct Result`
its macro-hygiene test declares (845 on one); tokio 924 `Ok` onto
`TransitionToIdle::Ok`, 290 `drop(x)` onto a `drop` method.

For a name that is bare IN THE SOURCE (a `::` path is kept by its last
segment and is not a prelude lookup — checked at the reference's column):
- an enum variant is in scope only through a `use` of it (and its enum) or
  of its enum's `*`, and never names a type;
- a prelude name (Ok/Err/Some/None/Result/Option/Box/Vec/String/drop/…)
  reaches a project item in another file only through a project `use` of
  that name or a glob of that item's own module (`use crate::glob::*`,
  `use super::*` for the parent) — `use std::…` trees don't count, and the
  `use` scan runs on comment-stripped text (a doc comment's "…use the
  Option…" once read as an import);
- fuzzy matching is case-exact for Rust (`Bytes` ≠ method `bytes`,
  `Ok` ≠ fn `ok`).
Applied as a candidate filter (so the in-scope item can win) and at
resolveOne's seam — a Rust framework resolver's `Ok(x)` → `struct Ok`
construction bypassed name matching.

A/B vs main (edge diffs, every change classified): ripgrep −1,462/+0,
serde −2,072/+40, tokio −1,820/+2, clap −752/+7, axum −397/+4, bat −45/+0 —
losses are Ok/Some/Err/Result/Option/Vec/drop and same-named variants
pinned on project items; gains are pre-existing same-name ambiguity
(serde's two `Content` enums).

Co-authored-by: Claude Opus 5.5 <[email protected]>
… never by name alone (#2125)

Found by the README-wide sweep — Django/Flask/FastAPI apps' most-depended-on
lists were test and serializer methods: netbox `UserConfig.all` (3,610 from
`X.objects.all()`), `PortSerializer.create` (2,093 from `.objects.create`),
a test-local `FakeQuerySet.count` (1,910); healthchecks `AuthTestCase.get`
(880 from `Channel.objects.get()`), `ProfileAdmin.login` (702 from
`self.client.login()`); mealie `_FakeHTTPResponse.json`.

Python keeps a call through a receiver it cannot name (`User.objects.get`,
`self.client.login`, `request.POST.get`) as the bare `get` / `login`, and
exact-name matching then took any class's method by proximity. The call's
shape is now read at its column (the call's start):
- `bare` (`get(1)`): not a method — Python has no implicit self — and not a
  project symbol when the file takes that name `from` a module the project
  doesn't contain (`from django.shortcuts import render`);
- `chained`: only a member of what the chain names last — a method of a
  class of that name (case/underscores ignored: `self.store` → `Store`,
  `self.user_service` → `UserService`) or a function/class in a module of
  that name (`app.helpers.slugify()` → helpers.py);
- `self.x()` / `cls.x()`, and chains split across lines: unchanged.
Fuzzy matching is case-exact for Python (`dir(…)` ≠ class `Dir`).
Lexical reachability now walks every enclosing scope, so a method of a
class declared inside a function is only reachable in there (memoized per
candidate).

A/B vs main (edge diffs, every change classified): netbox −18,091/+73
(indexes 8s → 5s), healthchecks −3,354/+361, pytest −2,950/+105,
mealie −1,802/+6, allauth −1,086/+57, DRF −943/+50, flask −167/+17,
httpx −147/+24. Gains are precise member links (`self.channel.notify` →
Channel.notify, `item.stash.get` → Stash.get, `config.cache.get` →
Cache.get, `self.provider.verify_token`); the one deliberate loss class is
calls through an instance whose type needs inference (`flask.g.setdefault`
in flask's own repo). excalidraw, typeorm, gson, okio, AutoMapper, gin and
hono byte-identical.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…atic imports (#2126)

Found by the README-wide sweep: halo's most-depended-on symbols were an
`EmailVerificationService.verify` (1,038 — Mockito's `verify(mock)`) and a
builder's `eq` (845 — ArgumentMatchers `eq`); retrofit's a test helper's
`assertThat` (1,357 — Truth); mall's a DTO's `hashCode` (Object's).

A bare Java call (`verify(x)`, `helper()`, `this.x()`, `super.x()`) now has
only method candidates declared on a class around it, on one of that class's
supertypes (read transitively from the declarations' `extends` /
`implements` — the resolved edges don't exist yet on the first pass), or
imported statically (`import static a.B.m;` / `a.B.*`). Memoized per type
and per file.

A/B vs main (edge diffs, every change classified): halo −2,103/+145,
retrofit −1,380/+18, commons-lang −202/+87, jsoup −92/+45, mall −76,
gson −7/+4. Gains are the right target where one exists: halo's `and` /
`equal` / `isNull` through `import static …Queries.*`, jsoup's `attr(…)`
through `LeafNode`, commons-lang's `addExact` on its own class instead of a
nested MathBridge, retrofit's `getRawType` through `CallAdapter.Factory`.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…2128)

Calls in `.vue`, `.svelte` and `.astro` files carry their own language, so
the JS/TS rules from #2120 never applied to them: halo's `t('…')` from
`useI18n()` went to an interface property `t` (562 callers) and `ref(…)`
from `vue` to a local `ref` (523).

"Outside the repository" is now a package the importing file's package.json
chain (or a Deno import map) declares, a Node built-in, `@std/…`, or a
framework's virtual module. An alias the resolver can't follow — SvelteKit's
`$lib/…`, a nested Nuxt app's `~/…`, a nested app's own `@/…` — stays the
project's, and a `workspace:`/`file:`/`link:` dependency is in the repo.

SFC files also get the JS built-ins (`fetch`, `Error`, `Map`), and a file's
own import of a built-in's name (`import Map from './Map.svelte'`) is the
import. Untyped three-segment chains (`api.groupReports.getAll()`) keep
resolving in SFCs, where they reach their API client class.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…repo (#2129)

create-t3-turbo, tamagui's starter and react-native-reusables keep an Expo
app beside a Next.js app. Expo Router read the Next app's `app/` folder as
its own, so `layout.tsx` became a `/layout` screen, `page.tsx` a `/page`
one, `_components/posts.tsx` and `api/auth/[...all]/route.ts` screens too,
and a shared `packages/app/` library's files became screens as well.

A resolver can now name `appDependencies`; its extractor runs on a file
only when that file's package.json or an enclosing one declares one of
them. When no manifest in the project declares any, detection found the
framework by other evidence and it runs everywhere, as before. Expo Router
names `expo-router`, Next.js `next`.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…uxt routes only in Nuxt (#2130)

vue-element-admin, vue-admin-template and vben had no routes: their tables
are named arrays (`export const constantRoutes = [...]`, `const routes:
RouteRecordRaw[] = [...]`) or per-module objects (`const tableRouter = {…}`)
handed to `new Router(...)`, and most of their screens are `children`.

The Vue Router reader now reads those tables in any file that builds a
router, imports vue-router or lives in a router/ directory; joins children
onto their parent's path (a parent is a screen only when no child claims
its address and it doesn't redirect); binds a lazy view by the FILE it
imports (all of vue-element-admin's are `…/index.vue`), through an alias the
resolver can't follow by the one file in the app with that path; links each
screen to its parents' components as layouts; and takes `this.$router.push`.

Nuxt's file routes move into their own `nuxt` resolver, detected only in a
Nuxt app: halo's plain-Vue console got 30 made-up screens from its `pages/`
folders. A Nuxt app at the repository root now gets its routes, and a
top-level `pages/index.vue` is `/`, not `/index`.

Co-authored-by: Claude Opus 5.5 <[email protected]>
colbymchenry and others added 30 commits October 6, 2026 23:18
… a Shopify theme (#2393)

isShopifyLiquidJson matched every templates/**/*.json and sections/**/*.json
by path alone, so a .NET project template's template.json (jasontaylordev/
CleanArchitecture), schematics and CMS content were indexed as empty Liquid
files, and JSON listing `sections` could link to an unrelated Liquid file.

The rule now needs the directory holding templates/ or sections/ to also
hold layout/theme.liquid or config/settings_schema.json, looked up under the
project root. isSourceFile, detectLanguage and preloadLanguagesForFiles take
an optional rootDir, and every caller that decides what to index or how to
parse passes it: the scans, git change detection, scoped sync, the watcher,
indexAll, indexFileWithContent and CodeGraph.extractFromSource. Without a
root there is no theme to find. A sync removes JSON an older index stored
this way.

Shopify/dawn, expressjs/express and gofynd/Astra index byte-identically;
CleanArchitecture loses its 2 bogus Liquid files.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…2394)

extractInheritance carried Go's struct-embedding check (a field_declaration
with no direct field_identifier names an embedded type) into every grammar
with a field_declaration_list. tree-sitter-c/cpp, and Objective-C's C
structs, nest a member's field_identifier inside its pointer/array/
function/reference declarator, so `SharedState* shared;`, `jv elements[];`
and `virtual Status Put(...) = 0;` all became `extends` edges to the
member's type: false supertypes in the type hierarchy, blast radius and
codegraph_explore, and false cpp-override call edges built on them
(google/leveldb: 246 of 302 extends edges).

Gate the branch to Go in the TS extractor, drop the mirrored arm from the
C/C++ kernel port (and the "kept for parity" quirk note), and drop the
inert copy from the Rust-language port. C++ base_class_clause, Objective-C
superclasses and Go embedding are unchanged.

Validated on leveldb, jq, llm.c and redis: every removed extends edge is a
member site, no base-clause edge is lost; gin, SDWebImage and ripgrep
dumps are byte-identical; kernel/wasm parity sweeps show 0 diffs.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…levels down (#2395)

declaredDependencies read the root manifest and the ones one or two
directories down. An app whose package.json sits deeper was never read:
an ASP.NET solution's src/Web/ClientApp/, or prometheus's
web/ui/mantine-ui/ under a workspace root that declares only tooling.
Every resolver gated on dependsOn() then stayed off for it: Angular Router
and the Angular template pass, React Router's navigation, Vue Router,
SvelteKit and TanStack Router.

The existing pass is unchanged. When it leaves slots open, a second pass
probes the directories three or more levels down that hold indexed JS/TS
code, shallowest first and sorted within a level, at most 192 of them,
never inside node_modules.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…ide that theme (#2396)

A `{% render %}`, `{% section %}` or JSON template section `type` written in a
file inside a Shopify theme now resolves to that theme's own `sections/` or
`snippets/` file, or to nothing. It no longer reaches another theme's copy by
path tail (panoply/syncify: using-tailwind's templates linked straps/dusk's
sections), nor the root theme's copy over a folder theme's own.

The theme detector is shared with isShopifyLiquidJson (#2393):
shopifyThemeRoot in grammars.ts.

Co-authored-by: Claude Opus 5.5 <[email protected]>
extractInheritance's Go interface-embedding branch matched a
`constraint_elem`, a node type neither the vendored tree-sitter-go wasm
nor the kernel's tree-sitter-go 0.23 crate has: both parse an embedded
interface as a `type_elem` holding one type. So no Go interface ever
extended anything, in either engine; gin's IRouter never reached IRoutes.

Read an embedded type the same way in an interface and in a struct:
`T`, `*T`, `pkg.T` and `T[X]` all embed `T`, recorded at the name so
resolution reads the package back from the source (#2322). A type_elem
holding a union, a `~T` term or a lone predeclared type is a type-set
constraint and records nothing, and predeclared names (`error`, `any`)
are skipped in structs too. Qualified and generic struct embeddings,
skipped until now, link the same way.

Resolution keeps an embedded type in its package. The Go framework
resolver's naming heuristics no longer take extends/implements refs: its
PascalCase-model pattern saw structs only, so an embedded interface went
to another package's same-named struct (promql/parser's Node, etcd's
Lease), as #1827 did for Spring. And a Go supertype written through a
package that is none of the file's imports as indexed (`yaml` under an
unaliased go.yaml.in/yaml/v3, registered as `v3`) stays unresolved
instead of binding by its bare name.

Validated on gin, prometheus and etcd (before/after edge diffs, every
added supertype edge checked against its package; kernel/wasm parity
sweeps unchanged).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…e member it calls (#2398)

The Angular template pass read own-member calls only in event bindings, so
a member a template calls from `[name]="icon()"`, `{{ label() }}`,
`*ngIf="isOpen()"` or `@if (loading())` had no incoming edge. With signals
that is the usual way a template reads state, and the dead-code derivation
listed such members as unused (CleanArchitecture's ThemeToggleComponent
icon and label).

Every render-time expression is now read: property bindings,
interpolations, structural-directive microsyntax, control-flow blocks and
@let. A call of one of the component's own members becomes an
`angular-binding` edge with the binding as `via` and no trigger. It runs at
render, and a trigger drew computed signals as user actions in Steps. A
read without a call counts only for a method-kind member: a getter is
called, a function handed to a child is a fnRef reference, and a field a
call filled is a plain reference. Property reads link nothing, as in
TypeScript. Pipes, object and microsyntax keys, members of other objects,
template locals, strings and comments link nothing either.

Validated on CleanArchitecture, angular-realworld, ngx-admin and Ghostfolio:
272 edges added, none removed, and every registeredAt line checked. The
proshop_mern and next-saas-starter dumps are byte-identical.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…the type it names (#2399)

google/leveldb's MemTable::Add calls `table_.Insert(buf)` on `Table table_;`,
declared next to `typedef SkipList<const char*, KeyComparator> Table;` in the
class. Receiver inference returned `Table`, no class `Table` has an `Insert`
(the one class `Table` is the unrelated leveldb::Table, and C++ type_alias
nodes record no aliased type), so the call fell through to the receiver-word
guess HandleTable::Insert. It now reaches SkipList::Insert.

cpp-type-aliases.ts reads the alias from source and looks it up the way C++
does: an alias declared earlier in the calling function, the caller's class
and its bases (read from the class's own base clause), then the namespaces
around it. A qualified name resolves a segment at a time (Table::Iterator is
SkipList::Iterator). Aliases of a template parameter, a dependent typename or
a decltype name no particular class. The constructor path uses the same
lookup, so `Table::Iterator iter(&table_);` constructs SkipList::Iterator and
an alias hides an outer class of its name.

When the aliased type lacks the method, a `.` call or a call through a
pointer on a receiver the caller itself declares gets no guessed edge. A `->`
through an iterator or smart pointer, a project class template (whose
specializations can add members) and declarations read from outside the
caller keep the previous behavior.

Co-authored-by: Claude Opus 5.5 <[email protected]>
… not dead (#2405)

The viewer's Dead code screen listed two kinds of framework-called methods.

A method whose decorator resolves outside the project (`@HostListener`,
`@Cron`, `@OnEvent`, `@Process`, `@BeforeInsert`, `@InitBinder`) has no
`decorates` edge and no `node.decorators`, only the resolver's unresolved
`decorates` row. That row now counts as a decorator, the same evidence the
opaque-ancestor rule reads for an `extends`/`implements` row, under the
existing `decorated` count.

A lifecycle hook on a class that skips the `implements` clause (`ngOnInit`
on a `@Component` without `implements OnInit`) had no evidence at all. A new
`hooks` rule drops a TypeScript method when its class carries a decorator and
an interface outside the index carries the name. What the interface carries is
read off the classes that write it out: every one has the method, declared or
inherited, at least two and more than half declare it, and more than half of
them carry a decorator. TypeScript only, because `implements` is erased there
and a framework can only be finding the hook by name.

The ancestor walk moves out of overrideCandidates into ancestorsOf, shared by
both rules. Declarations are read from the candidate list rather than searched
by name: a hook is never called from inside the index, and a name some
declaration of which is called is the ambiguous-name rule's anyway.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…struct elsewhere (#2408)

* fix(go): a type written bare links its own package's, not a namesake struct elsewhere

The Go framework resolver's name heuristics (Patterns 1-4) picked a
same-named declaration by folder convention and fell back to the first
candidate anywhere, outranking name matching on a tie. Go reads a bare
name only from the reference's own package, so every cross-package pick
was wrong: promql/parser's `Node` parameters went to discovery/kubernetes's
struct, `apiv1.Node` to the same file's struct, and method calls through
an expression (`err[i].Error()`, a chained `.String()`) became
instantiations of structs named like the method.

- The patterns guess only for a name written bare, and only in its own
  package; Pattern 4 takes any type there, not only structs.
- isGoBareName is the one check for "Go reads this name from its own
  package"; a Go call through an expression never lands on a type.
- Go's if/switch/for/range join the bare-call keywords, so `if T(x)`
  conversions stay bare.
- goRefQualifier reads a variadic `...pkg.T` as written through `pkg`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs(changelog): the Go type fix covers exported types; name the before-state precisely

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
…2400)

A route table kept in a file of its own produced no routes: the ASP.NET
Core React template's AppRoutes array, which App.js maps into
<Route {...rest}>, a routes array imported into useRoutes(routes) or
createBrowserRouter(routes), and codedthemes' one-object-per-file
[MainRoutes, LoginRoutes]. Route objects were read only from a file that
itself names a data router, so these apps had no routes and their
navigate()/<Link to> navigation reached nothing.

The table's own file cannot tell it from a menu's { path, element } list,
so the evidence is the file that hands it over: a useRoutes /
create*Router argument, or a .map whose callback renders a <Route> from
the item's own fields ({...route}, {...rest}, path={route.path}). A new
FrameworkResolver.crossFileNodes hook keeps those routes, reconciled in
runPostExtract on every index and sync (removal-only syncs included):
new ones are inserted with pending references, ones no longer wanted are
removed with their incoming resolution edges resurrected, unchanged ones
keep their edges. The React pass starts from the files that import React
Router, follows the import to the table and the tables it names
(...ApiAuthorizationRoutes, children: x), each under its mount, and owns
its routes by id (route:<file>:<line>:table:<path>).

Route objects (tables and data routers alike): an index: true route is
the page at its parent's address; a route object around others is their
layout (a references edge marked layout: true, as Angular's), and is not
a page of its own where a child claims its address. A route renders past
a *Wrapper or *Route guard, and through a same-file
lazy(() => import(...)) to the module it loads, never a same-named
component in another app. useRoutes joins the file-level gate.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…in (#2402)

* fix(go): satisfying an interface counts the methods embedding brings in

goImplementsEdges (#584) built each interface's method set and each struct's
from their own `contains` edges only. Embedded interfaces are `extends` edges
since #2397, and embedded structs `extends` (or `implements` for an embedded
interface), so both sides now follow those declared edges, recursively and
around cycles: an interface asks for the methods of the interfaces it embeds,
and a struct has the methods promoted from the structs, interfaces and
defined types it embeds. Synthesized edges are never followed.

A struct that embeds the interface keeps its declared edge rather than gaining
a synthesized duplicate, and under the per-interface cap, structs that declare
every method themselves are linked before those that need promoted ones, so
the cap keeps the implementations interface-impl dispatch reaches. The
empty-interface skip and the cap are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test(go): the embedding test's supertype list reads declared edges only

Store in that fixture now satisfies other.Closer through the Close that
*storage.Base promotes, a go-implements edge synthesized from method names.
The assertion is about how declared embeddings resolve, as the rest of the
test already treats synthesized edges.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
…a namesake (#2407)

A C++ base class (`class DynamicMessage final : public Message`) was
resolved by its name alone, so a base named like a class of another
namespace, or of another language, bound to whichever namesake ranked
first. In protocolbuffers/protobuf, `DynamicMessage`, `MapEntry`,
`ZeroFieldsBase` and two arena_unittest classes derived from
`json_internal::ResolverPool::Message`; before #2397 they derived from the
PHP extension's C struct `Message`. The Go framework resolver claimed
those: protobuf ships Go examples, so the resolver is detected, its
`resolve()` runs for every language's refs (`languages` only gates
extraction), its PascalCase-struct rule found the C struct, and the
framework language gate lets C++ reach C. google/leveldb's and rocksdb's
iterators derived from an unrelated nested `Iterator` (`SkipList::Iterator`,
`WriteThread::WriteGroup::Iterator`), and leveldb's test comparators from a
`Comparator` struct that skiplist_test.cc declares for itself.

C and C++ inheritance refs now resolve in their own matcher
(cpp-supertypes.ts), ahead of every other strategy and with no
fallthrough, so no framework resolver, import strategy or name matcher sees
them, and only C and C++ declarations are candidates. The base is looked
up the way C++ looks it up:
- from the scope the deriving class is declared in, outwards, using the
  lookup cpp-type-aliases.ts (#2399) already has: a class scope with its
  bases, a qualified name a segment at a time, an alias followed to the
  class it names; `::Base` from the global scope only;
- then through what the file writes before the class: namespace aliases
  (`namespace _pbi = ::google::protobuf::internal;`), using-declarations
  and using-directives, and fmt's macro-opened namespaces;
- a class a .cc/.c file defines belongs to that translation unit;
- a template parameter, or a member of one, names no class: no edge.
When none of that finds the class, the only visible C/C++ class with that
qualified tail is taken, since the index loses some classes' namespaces
(`class PROTOBUF_EXPORT FieldDescriptor`, classes after a partial
specialization); several are a guess, so no edge.

The base is read as written, template arguments included, so an alias
template (`bool_constant<…>`) is found. An alias that leads to no project
class (one of `std::integral_constant`, one each `#if` arm declares
differently) is linked itself, as before.

Validated with site-keyed edge diffs on leveldb, fmt, protobuf and
rocksdb, and redis, SDWebImage and gson as controls (dumps
byte-identical): first against main 02d22ae, then again on main
dd70646 after #2405, #2408 and #2400 landed, with identical site
diffs (#2402, merged after, changes only Go's implements synthesis).
Every removed edge was wrong: 9 protobuf
`suite_.ReportFailure` guesses that reached `ForkPipeRunner`, cpp-override
edges from the wrong bases, and two rocksdb bases that are template
parameters.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…on ref (#2401)

* fix(kernel): a Go value like `wm.new` or `raft.None` keeps its function ref

The kernel's Go fn-ref capture checked a selector value's field name
against the name stoplist, so `raft.None`, `compression.None`, cgo's
`C.NULL` and a method value like `sync.Pool{New: wm.new}` were dropped.
The wasm extractor, the reference, applies NAME_STOPLIST to the
normalized name (`raft.None`), which the stoplist never matches. It also
builds that name from the operand and the field, so a line break or
comment beside the dot keeps the candidate; the kernel validated the
selector's whole source text and dropped it.

Build the name from operand and field as normalizeSpecial does, and drop
the field-name check. No other walker has the pattern: each applies the
stoplist to the same name as its TS spec.

kernel-parity.mjs --lang go: prometheus 202 -> 0 function_ref diffs,
etcd 6 -> 0, golang/go 35 -> 0, kapacitor 2 -> 0, plus 0 on gin, grpc-go,
hugo, caddy and go-ethereum. Indexed with both kernels, edges are
unchanged on prometheus, etcd, gin and hugo; kapacitor gains the one
missing edge, NewExecutor -> workerMaker::new.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(kernel): note that a comment before the dot keeps a Go selector value too

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
…the file appears later (#2403)

* fix(sync): a Liquid section or snippet reference links its file when the file appears later

A sync retries a parked failed reference by its name tail, matched against
the names of the nodes the changed files carry; a file's node is named for
the file. Liquid records `{% render 'price' %}`, `{% include %}`,
`{% section %}` and a JSON template's section `type` as references named
`snippets/price.liquid` / `sections/404.liquid`, and a failed one was parked
under the text after its last dot: `liquid`, the extension, which no node is
named. So a section or snippet added after the files naming it (or restored
after a delete) stayed unlinked until a referencing file changed or the
project was re-indexed, even in a repository with a single theme.

A `references` ref whose name is a path ending in a file name with an
extension is now parked under that file name (`price.liquid`), the key path
matching looks the file up by. The existing symbol-name retry then finds it
with no new query, since the changed files' node names already include each
file's own. Calls are left alone: their slashes sit in string arguments,
comments or divisions. Schema v13 rewrites the tails an older version
parked.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test(liquid): compare the synced links with a fresh index of the same files

Indexing again over an existing index skips files whose content is
unchanged, so it kept the synced links of the files that name the new ones
instead of resolving them again.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
…2411)

Three sync-convergence checks used `indexAll()` on the synced graph as
the "full re-index" side. Over an existing index that run skips every
file whose content is unchanged, so the edges the sync wrote survive it
and each check compared the synced graph with itself.

They now rebuild the index from an empty database, as `codegraph index`
does (`CodeGraph.recreate` + `indexAll`, the same oracle the CG-33
convergence suite uses):
- sync-import-retry: the imports of the unchanged importer, all 20
  languages.
- sync.test.ts "the synced graph matches a full re-index" and "rebinds
  the unchanged caller when the symbol moves to another file": every node
  and edge by natural key instead of node and edge counts, which a
  bidirectional difference can net out of. Both now index twice in the
  test body, so they get the 60s bound #1773 gave such tests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…mes for its package (#2410)

An unaliased Go import was named by its path's last element, so the
qualifier in yaml.Node (go.yaml.in/yaml/v3), klog.V (k8s.io/klog/v2) or
sqlite3.Error (github.com/mattn/go-sqlite3) matched none of the file's
imports. The reference then resolved by its bare name to whatever project
symbol shared it.

An unaliased import now also takes the name goimports'
ImportPathToAssumedName gives its path (the last element that isn't a
major version, without a go- prefix, cut at the first non-identifier
character), unless another import of the file is bound to that name or a
second import assumes it too. The last element stays registered:
k8s.io/api/core/v1 really is package v1.

The import scan now reads only the import section, without comments: the
last word of a comment had become the next import's alias, and imports
spelled in doc comments or string literals were read as real ones.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…utward is not dead (#2418)

The viewer's Dead code screen listed TypeScript interfaces that augment a
type declared outside the project: chart.js's `PluginOptionsByType` in
`declare module 'chart.js' { … }` (ghostfolio), `interface Window` in
`declare global { … }` (angular-realworld). The library or the runtime
reads them through the type they merge into, so nothing in the repository
names them, and the index recorded them as unexported file-level
interfaces.

TypeScript exports what such a body declares without the keyword. The
binder makes an ambient module body an export context unless it holds an
export declaration (`export {}`, `export =`, `export default x`), and a
namespace nested in one is ambient too. Both extractors now set isExported
that way: isAmbientExport in languages/typescript.ts, shared by TSX and
ArkTS, and is_ambient_export in the kernel's tsjs walker. The exported rule
then drops them, counted under `exported`, and that count's label now
names them. `declare namespace X` is unchanged: in a module file X belongs
to that file.

The resolver sees them as exported too. A file holding `declare global`
was already treated as exporting, so nothing moves there. A `declare
module` member becomes reachable across files by name.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…udes it (#2421)

isVisibleAcrossFiles hides every symbol in a test-suite file from non-test
code, decided by the file's name. A C or C++ header is part of every
translation unit that includes it, whatever its name: protobuf's conformance
framework lives in conformance/conformance_test.h/.cc and test_runner.h, and
the 9 `suite_.ReportFailure(...)` calls in the suites found
ConformanceTestSuite::ReportFailure through the supertype walk only to have it
dropped by the test-suite check.

The check now lets a C/C++ candidate through when the reference's file
includes it, directly or through other headers. A definition in a source file
counts through the same-named test-suite header it includes
(conformance_test.cc -> conformance_test.h), so a test that includes the
production header it tests (jemalloc's test/unit/hash.c and hash.h) does not
become its implementation. The include graph (new cpp-includers.ts) is read
from the import nodes extraction mints for every #include, each resolved the
way the graph's include edge is, walked from the included file up, from a
narrow query of six columns.

protobuf +675 edges, none removed or retargeted: 112 in conformance/ and 563
from *_unittest.cc files (which isTestPath does not recognize as tests) into
the test utilities they include. redis +1 (expr.c includes fastjson_test.c).
leveldb, fmt, llm.c, jq, rocksdb, typeorm, tokio, okhttp and excalidraw are
byte-identical. The same exception for Strategy 3's receiver-name guess was
measured and left out: it moved rocksdb guesses onto test::StringFS, a test
double production code includes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ess, inside the layout around it (#2423)

Route objects already read `{ index: true, element }` as the page at the
parent's address and a route object around others as their layout. JSX
routes did neither: `<Route path="/" element={<Layout/>}><Route index
element={<Home/>}/>…</Route>` bound `/` to Layout, the index child was never
read, and Layout's links counted only from `/`.

The JSX branch of scanRoutes now:
- reads `<Route index>` / `index={true}` (and a `path=""` leaf with an
  element) as the page at its parent's address, and lets it claim that
  address from the layout or the path-only `<Route path>` around it;
- treats a `<Route element>` with `<Route>`s inside as their layout
  (`layout:` references), a pathless or `path=""` one included, and threads
  those layouts into a table mapped inside it when the table is written in
  the same file;
- reads an index route only where its parent's address is written down: an
  enclosing route with a readable path, or the router itself
  (`createRoutesFromElements(…)`, `<BrowserRouter>`). At the top of a
  component's own `<Routes>` the address is wherever another file mounts the
  component, and under `path={paths.x}` it is not spelled out;
- reads `path={"agents"}` as a path.

Reading what an element renders: tags inside an attribute are no longer the
page unless the attribute hands one over (`component`, `element`, `page`),
so `<Suspense fallback={<Loader/>}>` no longer binds the spinner; the line
break Prettier writes after `element={` no longer hides the element; a guard
that wraps only `<Outlet/>` is the guard; and `element={<Outlet/>}` renders
nothing of its own.

Validated against the merged #2400 build: 16 control repos byte-identical,
578 of 581 new layout edges name a component of an enclosing <Route> (3
inherit a pre-existing default-import resolution bug), 27 of 27 new
navigations precise, no navigation lost.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…g on Node 22 (#2424)

go-ethereum's `codegraph init` sat in "Resolving refs" for good on Node 22
(killed after 1 h 55 min and 9,203 CPU-seconds). It was not Go: two resolver
pool workers each sat on one 500-ref chunk of
graphql/internal/graphiql/graphiql.min.js, a 980 KB bundle on one line
(18,388 refs, 2,234 functions).

Whether a JS function binds a name itself (jsFunctionLocalScope, #2226) is
read off its comment-stripped code with a parameter-list regex that starts
with a `(?<!\b(?:if|while|for|switch|with)\s*)` lookbehind. Node 22's V8
compiles new regexes without optimization once a process has generated about
a megabyte of regex code and holds 16 MB of executable memory, which a pool
worker reaches after some 30,000 refs. Unoptimized, the lookbehind runs at
every position and its `\s*` reads back through the whole run of blanks
before it, so a run of n blanks costs n^2/2 steps. The comment stripper,
which doesn't know regex literals, reads the `//` that closes `/Trident\//`
as a comment and blanks the remaining 961,968 characters of the line.

In a worker's state the regex took 36 ms over 16 K characters of that line,
5.1 s over 32 K and 54 s over 64 K, against 0.23 ms over 128 K in a fresh
process. Compiling 20,000 throwaway regexes first reproduces it in a bare
Node 22 process, and --no-regexp-optimization reproduces it anywhere. Node
24's V8, the runtime release bundles ship, kept optimizing after 60,000, so
released installs finish: 1.6.2 indexes go-ethereum on its bundled Node 24
and stalls the same way on Node 22. The stall is as old as #2226.

- A leading `(?=\()` keeps the lookbehind to where a parameter list opens.
  Every match starts with `(` anyway, so the regex matches exactly what it did.
- The jsx-render pass split the file, sliced a function's lines and scanned
  them for tags once per function: 2,234 times over the bundle's line, 25 of
  the 28 s its linking passes took on Node 22 (9 of 13 on Node 24), for no
  edges. Functions that span the same lines now share one scan, and the file
  is split once.

Graph: go-ethereum dumps (nodes, edges, unresolved refs, files) are
byte-identical between main on Node 24 (main never finishes on Node 22) and
this change on Node 22 and on Node 24. Main and this change also match byte
for byte on bootstrap4, legend-state, takenote, mantis, excalidraw (482
jsx-render edges), qwik, gin, prometheus, etcd and hugo.

Time, go-ethereum: on Node 22 main never finishes and this change takes 36 s;
on Node 24 (three interleaved rounds, medians) 42.1 s -> 38.3 s wall, with
resolution 17.7 s -> 12.7 s and the linking passes 6.5 s -> 1.5 s.

Tests: js-local-binding-backtracking forces V8's unoptimized mode and indexes
a function with a 60,000-character comment before a call (69 s on main, 1.4 s
now); jsx-render-work counts the tag scans of 60 components on one line (60
on main, 1 now).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
… by embedding (#2414)

Since goImplementsEdges counts promoted methods, a struct can satisfy an
interface with methods its embedded types declare. The interface-dispatch
bridge linked each interface method only to a method the struct declares
itself, so a call through such a method reached nothing when the embedded
type does not implement the interface on its own: prometheus's
scraper.Report never reached Target.Report through targetScraper.

For a Go struct's synthesized implements edge, each interface method the
struct does not declare now links to the method Go's selector picks: the
shallowest embedded type that has it, nothing when two occurrences tie at
that depth (one type reached along two paths included), and nothing when
an embedded interface provides it, which is a dynamic call again. These
links come after the per-implementer loop, from what the struct's cap has
left, so every existing edge is unchanged. They keep interface-impl
metadata plus promotedInto, and registeredAt names the struct's embedding;
codegraph_explore labels the hop as a promoted method. The embedding test
is shared with goImplementsEdges.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ppears later (#2422)

A router names the page or layout it loads lazily by the module's path:
React Router's `lazy-import:./pages/Team`, Vue Router's and Angular's
`import:./home/home.component#HomeComponent`, each also behind `layout:`.
Sync retries a parked failed ref by name_tail (#1240), and these were
parked under a fragment of the path ('/pages/Team',
'component#HomeComponent') that no file's keys match. So a route whose
module was added after the router was indexed, or whose module gained its
component in a later edit, stayed unlinked until the router file changed
or the project was indexed again. #2392 fixed the same gap for imports,
#2403 for Liquid's path references.

- referenceNameTail parks a module reference under the stem of its path
  behind 'module:' ('module:Team'), a key no symbol's name can be, so name
  lookups never retry it by a namesake.
- Sync adds the changed files' moduleReferenceKeys (importPathKeys behind
  'module:') to the symbol retry: an added file can be the module, and an
  edit can give the module the component the route renders.
- Schema v14 rewrites the tails parked before. Its select keeps SQLite off
  idx_unresolved_status with `+status`, reading idx_unresolved_name ranges
  instead of every failed row (vscode's index: about 1 s down to ~12 ms).
- The retry resolves in row order, as a full index does, so when a route's
  module reference and its layout reference make the same edge, sync keeps
  the one a fresh index keeps (ghostfolio's markets page).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…2412)

bulletproof-react's router imports its app shell as
`import { default as AppRoot, ErrorBoundary as AppRootErrorBoundary }
from './routes/app/root'`. The import mapping recorded AppRoot as a
named import of an export called `default`, which no module declares,
so resolveViaImport found nothing for it and every reference fell
through to name matching. The route /app bound to AppRoot by
exact-match, and in a project where another file declares an AppRoot
(or a default export is imported under a name another file uses) the
call, route or JSX attribute bound to that file's symbol instead.

The mapping now records `{ default as X }` as the default import, the
same as `import X from`, so every reader of isDefault agrees with it:
the import resolver finds the module's default export, the binding's
own `imports` reference links the module file as a default import's
does, and the JSX-child, Expo native-module, HTTP-client and styled
Link lookups read it as the default import. Re-exports
(`export { default as X } from`) were already chased as the default.
`require('./x').default` is unchanged: `.default` there is a property
of module.exports, which a CommonJS module can set by name.

alan2207/bulletproof-react: 4 edges re-resolved at the same sites (the
route /app reaches AppRoot by import; three binding imports link their
module file). bitwarden/clients: 2 failed Storybook imports now link
their module. bradtraversy/proshop_mern, leerob/next-saas-starter and
t3-oss/create-t3-turbo: byte-identical. Dead-code reports unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…own languages (#2415)

Strategy 1 of resolveOneInner ran every detected framework's resolve() on
every reference, whatever its language: `languages` gated only extraction,
and gateFrameworkLanguage lets every `calls` result through. Express,
detected in etcd by its file-content scan, linked Go `*zap.Logger` result
types to a method named `Logger`, and in harbor and bitwarden it linked
Python and Rust `validate()` / `authenticate()` calls to an unrelated
method or to the calling function itself.

getResolvingFrameworks() narrows the loop to the frameworks whose
`resolveLanguages` (else `languages`) list the reference's language; one
declaring neither stays universal. `resolveLanguages` lets a resolver read
a language it extracts nothing from without running extract() there:
Svelte reads TS/JS ($lib imports, runes in .svelte.ts), ASP.NET reads
Razor (a page's `@model` is the PageModel beside it).

claimsReference() stays universal: protobuf's leading-`::` C++ calls get
past the name pre-filter only on the Swift/ObjC bridge's claim.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…the parser misreads (#2426)

tree-sitter recovers from a construct it can't parse by inserting the token
it expected, and inside a class body that is often a `}`: an unknown macro
in front of a member (`PROTOBUF_FUTURE_ADD_EARLY_NODISCARD absl::string_view
name() const`) or in a class head (`struct ALIGN_AS(64U) HandleImpl`) closed
the class there, and every `}` after it closed the scope one level out.
Recovery also ran the other way, keeping a scope open past its own `}` once
an ERROR swallowed it. The walker scoped declarations by tree nesting, so
qualified names lost their namespace and class segments, or gained ones they
don't belong to: protobuf's `FieldDescriptor`, `MapFieldBase` and
`MutableRepeatedFieldProxyImpl`, fmt's `detail::` buffers (and
`fmt::format_int` put in `detail`), rocksdb's clock_cache classes and the
`Opts` structs nested in them, gtest's `PrettyUnitTestResultPrinter`.

Every such file parses with errors, and the kernel defers those to wasm, so
the wasm walker produced all of them; the kernel's walker has the same
tree-nested stack and reaches erroring trees only under the
CODEGRAPH_KERNEL_CCPP_ERROR_EXTRACT sweep hatch.

For a C++ file whose tree has errors, the walker now takes each
declaration's namespaces, and at declaration level its enclosing classes,
from the source's braces (languages/cpp-brace-scopes.ts: comments,
literals, raw strings, digit separators and preprocessor lines skipped,
each #if branch read from the braces open at the #if). A class-like node
ends at its body's `}`, and a class the tree glued into a namespace-level
declaration's type is walked as a class. A file whose braces don't balance,
and every file that parses cleanly, keeps the tree's scopes. Ported to the
kernel (ccpp/brace_scopes.rs) so the hatch stays parity-true: the same
erroring files diverge before and after (UTF-8/UTF-16 recovery), every
other one is byte-identical.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…er a method of its name (#2416)

A Go type position -- a parameter or result type, a composite literal's
type -- names a type of the package Go reads it from. Name matching (and
the instance-method guess, and the Express resolver reading Go refs) took
whatever declaration shared the name: etcd's
`func (ti *treeIndex) KeyIndex(keyi *keyIndex) *keyIndex` linked both types
to the method `treeIndex.keyIndex`, prometheus's `samples{...}` literals to
`sampleRing.samples`, and `config.URL{...}` from an outside package to
`scrape.Target.URL`.

gateTargetKind now hands every Go type position to goTypePositionTarget:
a method or function target moves to the type of that name in the
reference's own package (bare) or the imported project package (`pkg.T`),
or is dropped when there is none; a bare name that found another
package's type moves to its own package's type when it declares one
(prometheus's `prompb` builds its own `Histogram_CountInt`). A generic
method's receiver (`func (p *Pool[T]) Get() T`) now declares type
parameters for gateTypeParameter.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ater (#2425)

A router's navigation call (`history.push('/login')`, `navigate('/login')`,
`router.push('/x')`, `goto('/x')`) names its route by path and its reference
by the router's method, so sync never noticed a route that changed in another
file. A synced index then kept answers a fresh index of the same files does
not have (CG-33):

- A call parked as failed while its route was missing never matched the #1240
  retry, which keys on the names the synced files define.
- A call bound to a catch-all, a parameter route or the other arm of a
  conditional kept that binding.
- A route renamed in place by runPostExtract kept the calls bound to it.
- Template links from a routes file that matches no synthesis trigger were
  never redrawn.

When a navigation router is detected, sync now reads the route nodes at its
first file change and again after runPostExtract. For each route on one side
only, it puts back the calls the routers' new `navigation` hook (method tails
and app scope) says that route can answer: the failed ones, and the
`navigates` edges a resolver made. The orchestrator's orphan sweep resolves
them, under the same 500-per-name ceiling, and the sync refreshes synthesis.
This covers re-extracted and removed files, postExtract renames and
cross-file table routes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…#2419)

goImplementsEdges (#584) offered only structs as implementers. A Go defined
type over a slice, map, function or basic type declares methods the same
way (gin's `formSource map[string][]string` has TrySet, prometheus's
`staticDiscoverer []*targetgroup.Group` has Run, an adapter
`HandlerFunc func(...)` has ServeHTTP) and is extracted as a `type_alias`
that owns them through go-method-contains edges. So it never satisfied an
interface, and interfaceOverrideEdges, which walks only class, struct and
union, could not link a call through the interface to its methods.

Structs and defined types are now candidates together, in one canonical
(file, line) order. A defined type declares every method it has, so under
the per-interface cap it ranks with the structs that declare theirs, ahead
of promoted-only ones. interfaceOverrideEdges also walks Go `type_alias`
nodes. Every Go type's embeddings come from the batched prefetch (the
per-type lazy lookup for defined types is gone), and a type without a
method is dropped before the per-interface loop. A true alias
(`type A = B`) is not extracted, so it never implements anything itself.

iterateNodesByKindIn takes several kinds, interleaved in its one order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…owercase name calls that type's own member (#2413)

A C++ call on a variable, parameter or member the calling function or its
class declares as a namespace-qualified type the project does not declare,
with a lowercase name (std::string, std::vector, absl::flat_hash_set, ...),
no longer falls through to Strategy 3's guess by the method's name. leveldb's
`std::string saved_key_; saved_key_.clear()` reached Slice::clear, protobuf's
`std::string proto; proto.append(...)` LeftoverBuffer::append.

The gate applies to `.` on a value and `->` through a raw pointer; `->` on a
smart pointer, iterator or optional still reaches the element type. It skips
declarations it cannot trust: read from outside the caller's function and
class (including a class nested in the caller's), contradicted by the call
(`.` on a pointer), shadowed by an unreadable re-declaration (`auto`,
range-for, structured binding), unqualified, with unbalanced `<>` (the tail
of a multi-line declaration), or a `_t` name (scalars, tags, traits like
std::conditional_t). A `std::` name counts as the project's only when the
project declares it in `std` (vendored googletest's testing::internal::string).

Receiver inference now blanks comments before matching declarations, so
`// ... non-null imm_` no longer types leveldb's imm_ as `null`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…t names (#2417)

tree-sitter-go parses `type Event = mvccpb.Event` as a `type_alias`, beside
the `type_spec` of a defined type, and both extractors read `type_spec`
only. etcd's client/v3 `Event` had no node; a use through its package
(`clientv3.Event`, prometheus's `storage.AOptions{…}`) linked to nothing,
and a use inside it linked by name to any type so named, another package's
included.

- Extraction (TS and kernel): an alias is a `type_alias` node referencing
  each type its right-hand side names, at the name so resolution reads the
  package back; an alias of a struct or interface literal is a struct or
  interface. The alias's own type parameters and Go's predeclared types are
  skipped. A generic alias, which tree-sitter-go 0.23 parses as a
  `type_spec` around an error, reads as an alias on wasm; the kernel defers
  its file.
- A method called on an alias-typed value is the aliased type's: an alias
  of a project type is followed in its own package, through pointers and
  further aliases; one of an outside type resolves by name as before.
- An alias's target written through a package that is none of the file's
  imports as indexed stays unresolved, as an embedded type's does, so the
  alias never links to itself or a namesake.
- With Go defined types as implementers (#2419), an alias that owns
  methods written with it as the receiver implements what they satisfy,
  in place of the type it names.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

⤵️ pull merge-conflict Resolve conflicts manually

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant