mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-17 05:07:37 +08:00
b62da73550fea1e153d94116c902ffe7ee7d3af2
1164 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b62da73550 |
fix(sveltekit): route names drop (group) folders and parameter matchers (#2131)
A `(group)` directory shares a layout and is never part of the URL, and a
parameter's `=matcher` checks its value. Kept in the name, shadcn-svelte's
`src/routes/(app)/(layout)/blocks/+page.svelte` was `/(app)/(layout)/blocks`,
so no `goto('/blocks')` or `<a href="/blocks">` ever reached it and its
Screens showed no navigation at all.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
54a1698caa |
fix(vue-router): admin-template route tables, children and layouts; Nuxt 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 <noreply@anthropic.com>
|
||
|
|
92e14eda40 |
fix(extraction): a file-based router reads only its own app in a monorepo (#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 <noreply@anthropic.com> |
||
|
|
1ef1eb886c |
fix(resolution): Vue, Svelte and Astro calls follow the JS/TS rules (#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 <noreply@anthropic.com> |
||
|
|
8987be4254 |
fix(resolution): a bare Java call reaches its class, supertypes or static 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 <noreply@anthropic.com> |
||
|
|
99209b6f3d |
fix(resolution): a Python call reaches a method through its receiver, 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 <noreply@anthropic.com> |
||
|
|
ddc03f7c79 |
fix(resolution): a bare Rust name reaches only what is in scope (#2124)
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 <noreply@anthropic.com> |
||
|
|
c5ba68076a |
fix(resolution): type parameters, def-local types and Scala scoping (#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 <noreply@anthropic.com> |
||
|
|
068c422e1d |
fix: a minified or bundled script is generated, and generated files are 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 <noreply@anthropic.com>
|
||
|
|
e453e820a8 |
fix(resolution): a C#/VB.NET type position names a type, never a same-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 <noreply@anthropic.com>
|
||
|
|
7a51969fb0 |
fix(resolution): JS/TS calls never pick a class member by name alone 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 <noreply@anthropic.com>
|
||
|
|
7cc2edadce |
fix(ui): the viewer server exits when the command it runs under is killed (#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 <noreply@anthropic.com> |
||
|
|
b50c7c0a70 |
fix(swift): a call through a type path lands on the type it names (#2118)
`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 <noreply@anthropic.com>
|
||
|
|
e845b1cd10 |
fix(nestjs): routes carry the app's global prefix and URI version (#2117)
`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 <noreply@anthropic.com>
|
||
|
|
c3efbbf4e7 |
feat(angular): a template's event bindings call the component's methods (#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 <noreply@anthropic.com>
|
||
|
|
761611c18e |
feat(angular): routes, navigation and the component tree for Angular 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 <noreply@anthropic.com> |
||
|
|
7639c787e4 |
fix(swift): what a SwiftUI app's viewer showed that was not so (#2109)
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 <noreply@anthropic.com>
|
||
|
|
e9be68b61d |
fix(swift): a composition continued on & lines no longer swallows its 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 <noreply@anthropic.com>
|
||
|
|
5753de20f5 |
fix(swift): an extension is not the type it extends (#2107)
`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 <noreply@anthropic.com>
|
||
|
|
48b1a19b19 |
fix(vapor): bind a route to the handler on the type it names (#2106)
`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 <noreply@anthropic.com> |
||
|
|
26d6ccece5 |
test(mcp): hold the watcher's sync so staleness-banner edits stay pending (#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 <noreply@anthropic.com> |
||
|
|
85dd8b2a15 |
test(watcher): wait for a live FSEvents stream before the #770 restart 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 <noreply@anthropic.com> |
||
|
|
179ac85bd9 |
fix(steps): draw how a server-rendered endpoint answers; bare PHP calls 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 <noreply@anthropic.com>
|
||
|
|
dcd79ad3c5 |
fix(ui-map): count depth in folders that fork, so a Maven project opens 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 <noreply@anthropic.com> |
||
|
|
0419a51636 |
fix(synthesis): link a Spring or Laravel test's request to the route 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 <noreply@anthropic.com>
|
||
|
|
ffced05e3c |
fix(laravel): name routes by the path a request takes — leading slash, 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 <noreply@anthropic.com>
|
||
|
|
2b9f2f47ef |
chore(ui): version the component package with the engine (1.6.1) (#2098)
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 <noreply@anthropic.com>
|
||
|
|
f4ddf50851 |
docs(changelog): promote [Unreleased] into [1.6.1]
[skip ci] Auto-generated by Release workflow.v1.6.1 |
||
|
|
0695bc47ce |
release: sync package-lock.json to 1.6.1
[skip ci] Auto-generated by Release workflow. |
||
|
|
4a8eb6314e |
release: 1.6.1 (#2090)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2c4d4eb9e4 |
release: withhold codegraph ui until the viewer launches; hold its changelog entries (#2089)
* feat(cli): withhold `codegraph ui` until the viewer launches (CODEGRAPH_UI=1 opts in) The viewer has been exercised on Expo apps only, not on API, Laravel, Spring, Angular or Swift projects, so it stays out of this release. `ui` and its alias `web` — also as `help ui` or `ui --help` — are refused before any startup work, and the command is hidden from `--help`, unless CODEGRAPH_UI=1 is set, which keeps it usable for the people testing it. The viewer assets still ship, so the opt-in works on a released build. Reverting this commit is the launch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(site): say the viewer is not in a release yet The viewer pages have been live on the docs site since they landed, and 1.6.0 answers `codegraph ui` with "unknown command" (#1816, #1900). Until the viewer launches, the guide, the CLI reference and the next-steps link say so. Reverting this commit is part of the launch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): hold the viewer's entries for its launch; new Highlights for the fixes release 53 entries that describe `codegraph ui` and its screens move, verbatim, to docs/viewer-launch-changelog.md along with the launch Highlights. 12 entries whose graph and `codegraph_explore` half ships now (the router navigation edges, the cross-tier hops, explore's `when` conditions, SvelteKit layouts, wrapped Express handlers, …) are reworded around the graph; their original wording is kept in the holding file for the launch. A few kept entries lose a viewer clause. The Highlights are rewritten for this release. prepare-release.mjs promotes the section and extract-release-notes.mjs reads it back with no mention of the viewer; the released sections are untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
09b9646b7e |
perf(context): compute the dominant file once per index state (#1864) (#2086)
* perf(context): compute the dominant file once per index state (#1864) getDominantFile() backs context ranking's core-directory boost. Its answer depends only on the graph, but the whole-graph aggregation behind it (edges joined to both endpoints' nodes, grouped by file) ran on every generic codegraph_explore - seconds per call on a large index. Memoize it in QueryBuilder against a database change stamp: total_changes() (rows this connection wrote) plus PRAGMA data_version (commits by any other connection or process). Both are O(1), so no write path has to remember to invalidate, and a sync made by another process is seen on the next call. Any write forces a recompute; the heuristic's result is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(context): never keep a dominant file read inside a transaction (#1864) total_changes() counts writes that are later rolled back, so a result computed inside a transaction could survive the ROLLBACK under an unchanged stamp and describe edges that no longer exist. getDominantFile now skips the memo while a transaction is open (and on a runtime without isTransaction). Ported from the maintainer's hardening of this fix in #1919. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(db): drop the dominant-file memo when a QueryBuilder is rebound (#1864) The memo's change stamp (total_changes + data_version) is per connection, and fresh read-only connections to two different databases report the same one ("0:2"). A pool worker rebound onto a rebuilt index kept serving the old database's dominant file until something wrote through the new connection. A test rebinds onto a second project and fails without the reset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): credit @danusha2345 for #1864 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b44550e328 |
docs(changelog): credit @danusha2345 for the #1902 fix (#2085)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ee97a57a9b |
fix(directory): a schema-less codegraph.db does not make a project initialized (#1895) (#2083)
* fix(directory): a schema-less codegraph.db does not make a project initialized (#1895) isInitialized() accepted any existing .codegraph/codegraph.db, so an empty or table-less file in an ancestor directory (an interrupted init, a stray touch, a never-populated ~/.codegraph/) captured the upward walk of findNearestCodeGraphRoot / resolveProjectPath / resolveServerRoot for every project beneath it: the real index of a sub-project became unreachable, the sub-project down-scan never ran, and commands either operated on the broken file or failed opening it instead of showing the not-initialized guidance. An initialized project is now one whose db carries the schema: the probe is gated by file size (>= the 100-byte SQLite header), then the header magic, then a read-only node:sqlite open with a single sqlite_master lookup for the `nodes` table, memoized per path + mtime + size so hot callers (the prompt hook, MCP root resolution on every call) never reopen an unchanged db. The handle is closed immediately so the probe never holds the db on Windows; a locked/busy db counts as live. createDirectory() uses the same predicate, so `codegraph init` in the directory with the broken file repairs it in place (the schema is applied with CREATE ... IF NOT EXISTS) instead of refusing with "Already initialized"; the CLI says what it found. Nothing is deleted automatically. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(directory): a database we cannot open read-only still counts as initialized The schema probe failed CLOSED: a real WAL codegraph.db in a directory the process cannot write (a read-only checkout, a mount, another user's tree) made the read-only node:sqlite open throw — SQLite must create `-shm` to open a WAL database — and isInitialized() reported the project as not initialized, where main's file-exists check had been right. The probe now fails OPEN. Once the size gate and the `SQLite format 3\0` header have passed, the file is a SQLite database; only a SUCCESSFUL sqlite_master query proving the `nodes` table absent may return false. Any open/prepare error (locked, busy, cantopen, readonly, disk I/O) returns true, the pre-existing behaviour for a database we cannot inspect. No error strings are matched. The per path + mtime + size memo is unchanged, and schema-less/empty files still return false, so `codegraph init` still repairs them and never touches a real db. Test: a WAL db copied into a `.codegraph/` made read-only with chmod stays initialized — skipped on win32 and when running as root (root ignores mode bits). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(init): refuse a codegraph.db that is not SQLite instead of promising a rebuild Review found that a `.codegraph/codegraph.db` holding bytes that are not SQLite at all counted as "schema-less", so `init` printed "rebuilding it" and then failed with SQLite's "file is not a database". Only an empty file or a SQLite database without the codegraph tables can be rebuilt in place; `hasSchemalessDb` now means exactly that, and the new `hasForeignDbFile` names the other case. `init` stops on it with exit 1, says which file it is and to move or delete it, and never touches it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(directory): ask SQLite what codegraph.db is instead of reading its header ourselves (#1895) The header check opened and closed a descriptor of our own on `codegraph.db`. Closing any descriptor on a database file drops every POSIX lock the process holds on it, including those of a connection it already has open (sqlite.org/howtocorrupt.html §2.2.1) — and the MCP server resolves projects through isInitialized on every call while it holds the index as its writer. mcp-projectpath-lifecycle's takeover tests caught it: after a real owner exits, the taking-over engine's next read failed with "disk I/O error" (2 of 21 fail with the header read; 21/21 on main and with this change). One read-only SQLite probe now answers all three questions: the schema is there, SQLite opened it and the schema is not (repairable), or SQLite refused it as not a database (SQLITE_NOTADB). Anything else — locked, busy, no `-shm` possible — still fails open as initialized. SQLite's own connections share one lock table per file, so opening and closing a second one leaves the first one's locks alone. A test pins that isInitialized and both init helpers never open the file themselves while this process holds it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): keep the #1895 entry clear of #1917's, so the carries merge in any order Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
04ec5f554d |
fix(extraction): never read an oversize file; an MPEG-TS video named .ts is not TypeScript (#1910) (#2082)
* fix(extraction): an MPEG-TS video named .ts is not TypeScript (#1910) `.ts` mapped to TypeScript by extension alone, so MPEG transport-stream fixtures (`testdata/*.ts`) were parsed by tree-sitter: ~28 s of CPU for a 900 KB clip, minutes for a directory of them, for zero symbols. Recognise the stream from the head of the file — the 0x47 sync byte at offsets 0, 188, 376 and 564 plus a NUL byte, which every stream carries in its first packets and UTF-8 source never does — and drop it at discovery: not indexed, not parsed, not counted, not tallied as an unsupported language. The check reads under 1 KB and only for `.ts` files; the batch reader sniffs the bytes it already read, and the single-file path (sync, watcher) does the same head read, so a clip handed in by name is skipped too. The skip happens before parse dispatch, so no kernel mirror is needed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(extraction): a video .ts is never pending on the git path or a scoped sync Review found two places that decide "is this a source file" without the MPEG-TS check the scan applies: the git fast path's candidate loop in getChangedFiles and the scoped-sync path filter (watcher events). An untracked clip in a git repo was reported as `added`, skipped by sync without a record, and reported as `added` again on every status; a tracked .ts that became a clip stayed `modified` with its stale nodes. Both now treat the clip as non-source: removed when tracked, ignored otherwise. Regression tests fail without this change and pass with it, on both arms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(extraction): require a binary head before calling a .ts file video (#1910) Review on #1915: the sniff took four aligned 0x47 sync bytes plus any NUL as proof of a transport stream, so TypeScript with a `G` at four 188-byte strides and one raw NUL in a comment was dropped unindexed. The sniff now needs the sync byte on sixteen consecutive packets (3 KB of head) and at least 1/64 of the head to be control bytes. Compressed payload carries 8-20% of them (checked on ffmpeg-made H.264/AAC, MPEG-2 and MP2 streams); source text carries none. A shorter clip is cheap to parse anyway. Also drops the benchmark figures from the changelog entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(extraction): a file over the size limit is never read (#1910) The size gate ran after `readFile`, so a committed video or blob fixture was decoded in full — 3.4 GB of RSS for a 400 MB file, `Invalid string length` past ~512 MB — only to be stored as skipped. It was then read again by every pass that scans files by name: the framework detectors' `readFile` and the resolver's cached reader, three full passes on the reporter's fixtures. Stat first, everywhere a source file is read by path: the batch reader and `extractFile` store an oversize file with a size stamp in place of its content, change detection hashes the same stamp (so a same-size rewrite of a file nothing is indexed from is not a change, while crossing the limit in either direction is), and both by-name readers return null above the limit. The limit moves to `src/file-limits.ts` so the three sites share one number. strace on a sparse 400 MB `.ts`: 153 603 reads before, 0 after; indexing it next to a real file costs no measurable RSS. Stacked on #1914, which shares the batch-reader hunk. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(extraction): one-purpose size-stamp reader, comments in operation order `readSourceOrStamp` took a `stats` argument no caller passed and returned stats no caller read; it now returns the text or stamp change detection hashes. The batch reader's two comments are back in the order its code runs: the size gate, then the MPEG-TS head check. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(extraction): bound the read itself, not only the stat before it (#1910) A file can grow between the stat that gates the size limit and the read that follows (a log, a download, a build output being written). Every source read now goes through readBoundedSource/readBoundedSourceSync: the open descriptor is re-checked, and the read stops one byte past the limit, so an oversize file is still stamped rather than decoded. The extraction batch reader, single-file extraction, framework detectors and the resolver's file cache all use it. Ported from the maintainer's hardening of this fix in #1919. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(extraction): bound the parse-retry reads too (#1910) The retry passes for files whose worker crashed or timed out re-read the source with an unbounded fsp.readFile. The file can have grown since the first, bounded read; an oversize file is now skipped by the retry instead of being decoded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(extraction): check an oversize file against its size stamp everywhere (#1910) Review on #1915: - The viewer (readFileShape, hasDriftedOnDisk, the source endpoint) and MCP's drift gate hashed a file's real bytes, but the index stores the size stamp for a file over the limit, so every unchanged 1-8 MB file read as "changed on disk". oversizeStamp moves to file-limits.ts with an indexedHashInput helper, and all four checks hash what the index stored. An oversize file is now compared without being read. - git-index-currency's "keeps a committed path pending when sync cannot read it" injected its failure only into readFileSync, which the bounded reader no longer calls, so it failed. The failure now reaches openSync too. - The changelog entry drops its benchmark figures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): check an oversize answer file against its size stamp (#1910) #2043's answer-freshness check hashes each contributing file whole and compares it with the stored contentHash. For a file over the index's size limit the stored hash is the size stamp, so an unchanged oversize file would read as stale. Compare it by its stamp, without reading it, like the viewer and the MCP drift gate do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(extraction): judge a .ts clip from the bytes it is read for, not at discovery (#1910) Discovery sniffed the head of every `.ts` file on every full scan — an open + 3 KB read + close per file, before anything asked for its content. On a 10k-file TypeScript tree that took a warm-cache scan from ~105 ms to ~243 ms; on a slow disk or a network share it is a random read per file per scan, the access pattern #1231 and the SMB reports (#1014, #448) are about. The bytes are now judged where they are read anyway, so the check costs no I/O and runs only for a file that is new or changed: the batch reader (as before), `indexFile`, the sync reconcile (an untracked clip is skipped, a tracked file that became one is removed through the same removal path as a deletion), and both `getChangedFiles` paths. The scan lists `.ts` files by name again; a test pins that discovery opens none of them on the walk or the git path. Scan time is back to main's (~98 ms), and a full index is unchanged within noise. Also collapses the four #1910 changelog bullets (#1914 and #1915 each added both) to two, without the benchmark figures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: danusha2345 <ewidusoc498@gmail.com> |
||
|
|
e5a3d85b0c |
fix(watcher): follow a rebuilt index instead of syncing into the replaced file (#1902) (#1917)
* fix(watcher): follow a rebuilt index instead of syncing into the replaced file (#1902) `codegraph index` rebuilds through `CodeGraph.recreate()`, which unlinks `.codegraph/codegraph.db` and creates a new file at the same path. A running MCP daemon kept its handle on the unlinked inode and its watcher kept "auto-syncing" into it, so no edit reached the on-disk index. The #925 self-heal only ran on the tool-call path and ran no catch-up after reopening. - `sync()` now checks, once it holds the index mutex and the write lock, whether the database was replaced on disk (one stat, the same inode check `reopenIfReplaced` uses). If so it reopens the live file in place and widens a scoped sync to a full reconcile. A failed reopen (rebuild mid-way) returns the lock-busy shape so the watcher keeps its pending files and retries. - `reopenIfReplaced()` refuses while an index/sync holds the mutex, so the tool-call path never closes the handle an in-flight sync writes through. - When a tool call's `freshen()` does reopen, the engine runs its existing catch-up sync (serialized on the same mutex), but only for its own watched instance, so an engine that is not the project's writer never writes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sync): step aside for a rebuild that has recreated the file but not locked it yet `codegraph index` recreates the database, then takes the write lock inside indexAll. With the watcher now following a replaced file, a sync landing in that gap reopened the empty file and started a full reconcile, holding the lock the rebuild was about to request — `codegraph index` could then fail with "another process may be indexing". Review flagged the gap; this closes it. After following a replaced file, a sync that finds no index_state yet (the rebuild has not started indexing) and a file written within the last two minutes returns the lock-busy shape so the watcher retries; the first sync after that reconciles the whole tree once, then syncs go back to scoped. The time bound keeps a rebuild that died before indexing from parking the watcher forever. New test: fails without the guard, passes with it; 136 related tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sync): keep the full catch-up until a full sync completes (#1902) Two gaps in following a replaced database: - Only a reopen done by sync() itself set pendingFullReconcile. The tool-call self-heal (reopenIfReplaced) swapped the handle without it, so the next scoped watcher sync skipped whatever the old handle had absorbed. The flag is now set wherever the database is reopened. - The flag was cleared before the sync ran, so a sync that threw lost the catch-up. It is now cleared only after a full sync completes. Ported from the maintainer's hardening of this fix in #1919. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sync): report a rebuild in progress as lock contention, not an empty sync (#1902) Since #2014 the watcher tells lock contention apart from success only by LockUnavailableError; it no longer recognises the all-zero result. Following a rebuilt index still returned that shape when the reopen failed or when the recreated file had not been indexed yet, so the watcher counted the step-aside as a clean sync and dropped the pending edits. Both paths now throw LockUnavailableError. A watcher test proves the edit stays pending through the rebuild gap and is reconciled once the rebuild finishes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: danusha2345 <ewidusoc498@gmail.com> |
||
|
|
15a5d1d0cd |
fix(c): keep an #undef under an undecidable #if unknown; a macro call never binds to a type (#2069, #2070) (#2081)
Two follow-ups to the unreleased macro-visibility work (#1838/#1839): - #2069: `#undef FLAG` under an `#if` nothing in the source decides made a never-seen FLAG certainly undefined, so `#ifdef FLAG` was read as false and the functions it guards lost their callers (CPython's `Py_GIL_DISABLED`). A name no directive has touched is now unknown; only an #undef on a certain line clears it. - #2070: dropping the macro constant from a call's candidates let the next same-named node win, so expat's `PREFIX(scanRef)(...)` instantiated an unrelated `struct PREFIX`. When the name is a function-like macro in the project, only a function or method is an eligible callee. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fef37765c1 |
perf(resolution): remove the indexing regressions since v1.6.0 (vscode stall, per-ref rescans) — graph byte-identical (#2072)
* perf(resolution): read JS/TS parameter lists without exponential backtracking isLocallyBoundJsName (#1759, |
||
|
|
c3b1348065 |
fix(indexing): reject indexAll after a failed store instead of spinning (#1773) (#2079)
The in-order commit window waits for the commit cursor to advance before dispatching more parses. After a store fails, flushOrdered returns at once and the cursor never moves, so the backpressure loop spun on microtasks forever: indexAll never settled, a core stayed pinned, and no timer in the process could fire. It reproduces whenever a store error (a closed database, disk full, an I/O error) arrives with more files left than the window holds. In the test suite, which runs from source with a window of one, a test that timed out mid-index and closed its graph in afterEach wedged the whole worker, so a loaded Windows run never produced a report. Stop waiting once a store has failed or the run was aborted, and destroy the parse pool on that error path so its worker threads do not outlive the index. The regression test runs the scenario in a child process under a hard timeout. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c405ac5e6c |
fix(windows): make load-sensitive tests wait on real signals; longer pid-file retry (#1773) (#2078)
* test: make Windows load-sensitive tests wait on the tree, not the clock (#1773) Each failure from the loaded Windows run was traced to its mechanism and reproduced deterministically before changing it. No assertion is weakened. - daemon-lock-refresh: the PowerShell holder slept 200ms against a 375ms retry budget. Windows refuses a replace-rename while any handle to the target is open, even a Node read handle, so hold one in-process and close it inside the rename mock after the first real failure. Also asserts the first rename really failed with a retryable code. - mcp-projectpath-lifecycle: the owner daemon armed a 500ms idle timer at start and exited (code 0) before a starved engine reached it. It now starts with idle exit off and is armed over IPC once the engines hold a session. The file waits for catch-up instead of the gate's 3s serve-anyway deadline, and its index-heavy hooks and eviction case get explicit timeouts. - mcp-daemon / mcp-writer-lock teardown: racing launchers spawn detached daemon candidates whose cwd is the fixture; a loser still starting at teardown pinned it (EBUSY) or took over after the winner was stopped. A --require preload records candidate pids, and teardown waits for the losers before stopping the winner. - mcp-writer-lock: writer.pid appears before the daemon binds and rewrites daemon.pid, so cleanup could probe a daemon mid-start ('unverified'); wait for both proxies to attach. Removal uses fs.promises.rm, because fs.rmSync gives up on the first Windows access-denied error despite maxRetries. - mcp-daemon #1963: connect only after the proxy attached (lock before bind). - sync-rebuild-convergence / multi-repo-workspace: explicit timeouts scoped to the cases that build real indexes; the global timeout is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(daemon): give the Windows pid-file replace a longer retry budget (#1773) Windows refuses to replace a file while any handle to it is open, including a plain Node read handle, so an antivirus scan, an indexer, or another session reading daemon.pid can each block the daemon's startup refresh for a moment, and for longer on a loaded machine. The retry waited 375ms in all; it now backs off 25, 50, 100, 200, then 400ms three times, about 1.6s over eight attempts, still well inside a launcher's ~6s connect window. Permanent failures and non-Windows errors still abort at once, and an ownership change still stops the retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
003305f1e7 |
fix(explore): claim complete source only for sections that are complete (#2077)
* fix(explore): claim complete source only for sections that are complete On the tiers with a completeness signal (>= 500 indexed files) every codegraph_explore response ended with "Complete source for N files is included above — do NOT re-read them", whatever the render had cut. A windowed spine method, a shrunk or dropped cluster and the per-symbol view all went out under that line; the oversize-spine window never even set the trim flag the small tiers key off, which is how a 62-line slice of vscode's 968-line rpcProtocol.ts was called complete. The line also said "Reserve Read for a single specific line range", which explore output must never say. Completeness is now measured at emission from the ranges actually sent (plus back-referenced spans) against the symbols each section set out to deliver. A trimmed response keeps the verbatim / already-Read guarantee, names the trimmed files and the most relevant elided symbols, and steers to another codegraph_explore. The note is offered from most to least specific and fitted to the leftover room (CG-26); a trimmed note yields the pointer list's first entry. The lost-pointer, cut and truncation notes drop "complete" the same way, at no greater length, so the epilogue floor is unchanged. The small tiers' trimmed note now also fires for the cuts the flag missed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(explore): offer an elided method as Owner.member in the completeness note The note offered `as_sql` for django's elided SQLCompiler.as_sql — one of 110 definitions — so agents appended a path and line to disambiguate it, and that query shape returns none of the body. `SQLCompiler.as_sql` alone returns most of it. Methods are now offered as `Owner.member` from the indexed qualified name; everything else keeps its bare name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(explore): fit the completeness note and pointer list in one tested function A trimmed note's optional detail (the files, the elided names) never costs a pointer entry the least specific note would have left. The fit moves out of handleExplore into fitExploreEpilogue so both rules are pinned by tests; the second-try branch it had could never fire and is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(explore): completeness note wording after #2068 flags the spine window Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(explore): record the completeness-note replay and agent A/B Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(explore): address review of the completeness note - Judge completeness AFTER the dedup fold: a remainder too small to fence is sent in neither call, so a section with one is trimmed, not complete. The whole-file render now declares its symbols too (clamped to the lines it prints; the file node is not one), so a fold there registers. - Label trimmed files unique among every path the response can name, the pointer list included. - "1 file", and no "0 files" when every section was held from an earlier call. - The lost-pointer, cut and truncation notes are one exported table, EXPLORE_FALLBACK_NOTES, with the length rule tested; the lost note picks its wording from trimmedShown instead of re-joining the response. - The design doc's follow-up list marks the overclaim fixed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
290e03f7bc |
fix(explore): pin only the same-named file that defines the symbols a query names (#2071)
A bare basename several directories share (`editorOptions.ts`, `worker.rs`, `compiler.py`) pinned every match, and the matches split the pinned reservation: on vscode `editorOptions.ts clampedInt cursorStyleToString cursorStyleFromString` gave the file holding all three 8,626 chars, against 15,181 without the path, and spent the rest on a workbench helper defining none of them. Path extraction now takes an injected `symbolFiles` lookup (the module stays DB-free, like `existsOnDisk`). When a span matches several files, the matches defining a precise symbol token the query names (camelCase, PascalCase, snake_case, qualified — the seeder's NL-stopword shape test) are pinned and the rest set aside; the summary line names them. When no match defines one, or every match does, the span resolves exactly as before. The same test resolves a basename shared past the ambiguity budget (django's `models.py`) when it narrows into budget, and a line anchor binds once the span narrows to one file. Replay of 196 queries over 8 indexed repos, main vs this branch: every unambiguous pin is byte-identical (38 unique basenames, 13 dir/basename, 8 file:line, 38 earlier-sweep path queries), as are 26 path-free queries, 13 where every match defines a named symbol and 8 naming no symbol. All 52 changed responses are shared basenames — named FULL 207 -> 231 (11 up, 0 down), named lines 6,045 -> 7,577, none over 25K. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
724b5dee0c |
fix(explore): return named bodies from merged spine clusters; let named files use spare budget (#2068)
* fix(explore): return named bodies from merged spine clusters; let named files use spare budget Re-applied onto main after #2062 and #2063 landed in the same render path (originally e31cc9fc on claude/inspiring-grothendieck-8a7b3e): - the merged-spine hold-back and its top-up are dropped: #2062's hold-back (every cluster leaves room for the protected members ranked below it) subsumes them, and the past-the-pin-cap test passes on it; - #2063's "don't window a cluster holding an exact target" becomes the member rule's "don't window a spine method that is or holds one"; - the shrink orders exact targets ahead of spine members, and the exact branch prices member windows (sectionRangesOf) rather than whole bodies; - the final ceiling fit shortens the file header back to its estimate before it cuts source. With an exact target's overshoot (#2063) spending a named file to its funded line, a lower file's header running past its estimate cost it 109 chars of source (922 of 1,031; 995 on main). The valve test now asserts funding in full and delivery to the line, since #2062's exact rendering stops at whole lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(explore): record the agent A/B for the named-concentration fix Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(explore): record landing named concentration on main after #2062/#2063 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7506dbff69 |
test(explore): guard named symbols in a pinned file past the node cap (#2064)
A file named by path is pinned: its first 300 symbols by start line enter the gather. Past that cap the file splits into a head cluster of pinned filler and, one cluster down, the named symbols the cap cut off, which used to render nothing (`protocol.ts serializeAck`: 0 of 3 lines; the same query unpinned: all of them). #2062's cross-cluster hold-back fixes this shape without knowing about pinning; its own fixture is an unpinned file. This fixture pins the pinned entry path to it: red on tools.ts before #2062 (2 of 6), green on main. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
47840a6ed4 |
fix(explore): return a qualified or line-anchored method's body instead of its signature (#2063)
`SQLCompiler.as_sql pre_sql_setup get_select` on django returned get_select and pre_sql_setup in full and SQLCompiler.as_sql -- qualified, step 1 of the Flow, 226 lines -- as one signature line. compiler.py renders as a per-symbol focused view, which picked bodies in source order within a tier, so the unnamed bridge get_qualify_sql and get_select (both above as_sql in the file) took the cap. The follow-ups agents issue (`compiler.py:776`, `compiler.py lines 900-1003`) pinned the file and dropped the line numbers. Every agent run ended in a Read of compiler.py. - query-paths: keep `:776` / `:12-40` / `#L88-L120` suffixes as line anchors and bind prose ranges (`lines 900-1003`, `L900-L1003`, `lines 900 to 1003`) to the nearest single-file path. - EXACT targets: a qualified name resolving to <=3 callables, or the callable enclosing a single-line anchor. Multi-line anchors render as that span. - Focused view: tiers exact > named spine step > unnamed bridge > unique named > family co-named, spine in call order. A tier 0-1 body that does not fit is windowed (head + calls into the question's other symbols) with the explore range that returns each hole, never reduced to a signature. Priced in rendered chars net of the signature lines, so the section keeps its promise. - Cluster path: exact clusters rank first with the spine's bounded overshoot, shrink by exact rendered size, and hold back what lower-ranked exact clusters still owe; files holding one are funded like spine files. - Blast radius leads with exact targets. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
adad8407ff |
fix(explore): pay named members before incidental ones across clusters (#2062)
Clusters of equal max importance rank by density, and density counts every member, so a named function with unrelated helpers merged around it outranked a cluster holding a named function alone. Taken whole, the higher clusters spent the file's reservation on the helpers and the isolated named function rendered nothing (lib/response.ts repro: sendBody 0 of 36 lines). Reordering alone only moves the victim. Two rules, both scoped to incidental members (not named, not an entry point, not on the call path): - a cluster's incidental members may only use what is left once every lower-ranked cluster's protected members are paid; its own protected members are never held back, so named-vs-named stays rank's call; - in a cluster holding a protected member, incidental members are never kept past the render's ceiling in rendered size, so the source-order window back to the ceiling no longer cuts a named body's tail (gin's handleHTTPRequest) on selection or on any re-render. 47 deterministic queries over the 7 README repos plus express vs main: named bodies complete 198 -> 234 (17 responses up, 0 down), named lines +10%, responses over the 25K cap 5 -> 1. One new final-cut truncation (django-bag) comes from owedPayableBelow's MIN_CHARS threshold, recorded in the design doc. On main + the named-concentration branch: 235 -> 279. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c13bd0890f |
fix(wsl): keep WSL off Windows' index on /mnt drives, and explain the I/O error (#995) (#2061)
When Windows-native and WSL CodeGraph both open the same .codegraph/codegraph.db under /mnt/<drive>/, SQLite's locking and -shm shared memory don't hold across the 9p/DrvFs bridge and WSL fails with a bare "disk I/O error". - A SQLite I/O error on the default index of a WSL /mnt/<drive> project is rewritten (in the SQLite adapter, so every open and query is covered) into the CODEGRAPH_DIR=.codegraph-wsl instruction. MCP answers it success-shaped, and a failed default-project open is reported instead of "no project loaded". - On WSL + /mnt/<drive> with CODEGRAPH_DIR unset, the data dir is resolved per root: an existing .codegraph-wsl/ wins, then an existing .codegraph/ index (kept, no silent re-index), else a fresh index goes in .codegraph-wsl/. `codegraph init` says where it went. Every other host is unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e63fe2ec5b |
test(wal-heal): await the heal open() started instead of a wall clock (#2056)
`open() itself kicks off the heal` polled the WAL size for 30s and then asserted it. On a loaded machine the heal was still running when the deadline passed, so a slow heal failed as a size assertion, reading like a regression (#1773). Await the pass open() started instead: it still fails at once if open() stops kicking off the heal, and reports the heal's own result rather than the clock's. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
66aebd17e2 |
fix(explore): three retrieval regressions found against v1.6.0 (#2057)
The pre-release agent A/B (main vs v1.6.0, 7 README repos, 3 runs each) had main returning 15% less source on vscode for the same queries, with Reads in 2 of 3 runs where v1.6.0 had none. Replaying every query both builds' agents issued traced it to three causes: 1. Named gaps (#1711) displaced source. Every cluster fit measured the gap names, which on a long path run to hundreds of chars, so a class shrunk into its file's room overran it by the names alone and was dropped whole: rpcProtocol.ts emitted 222 of 5,496 funded chars (v1.6.0: 5,194), and the test fixture came back as an empty fence. Fits now measure bare gaps; names are added at assembly from what the file's budget has left. 2. Interface members (#1638) corroborated English words. The NL-stopword guard lets a bare word seed a definition when another query word names a symbol in the same file; `readonly host` in an options interface made "main" seed `main()` in agentHostServerMain.ts, which took the named-first tier from the answer files. Members declared inside an interface no longer count as corroboration. 3. The interface-dispatch note announced dispatch through any common base, declared or not: "`extension` -> runtime dispatch to 2706 types implementing Disposable" opened 9 of 31 vscode answers. A supertype now has to declare the member (it, an ancestor, or a same-named Swift extension). When absence can't be judged, because a protocol's requirements aren't indexed, the note stays as before. Replayed queries, source lines vs v1.6.0 (main -> fixed): vscode 10,744 -> 12,577 (12,617), excalidraw 13,065 -> 13,698 (13,764), django 8,420 -> 8,803 (8,954), tokio 13,618 -> 14,151 (14,600), okhttp 6,511 -> 6,883 (7,090), alamofire 7,019 -> 7,249 (7,120); no query below 60% of v1.6.0. Real dispatch notes (Alamofire adapt/retry/asURLRequest, django, okhttp) are unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
49607201d6 |
fix(resolution): bind implements to the interface of a value+interface pair (#2055)
The inheritance target-kind gate (#1796) rejected any extends/implements resolution whose target cannot be a supertype. VS Code declares every service twice under one name, `export const IFoo = createDecorator<IFoo>(…)` beside `export interface IFoo`, and the import resolver takes the first export of that name, which is the value. The gate then dropped the edge instead of taking the interface the same file declares. Found by the pre-release v1.6.0 vs main comparison: 864 `implements` edges lost on vscode (AccessibilityService -> IAccessibilityService and ~860 like it), plus the ~5.8k interface-dispatch call edges synthesized from them. The gate now moves a TypeScript constant/variable target to the one same-named supertype in its file; with no such sibling it still drops the edge. vscode: implements 4,139 -> 5,003 (v1.6.0: 5,436, 254 of them on non-type targets; now 0), node count unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |