mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-17 05:07:37 +08:00
83f54a18532ca9fa8cc3d7c14d425ca8bcaa0e08
1355 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
83f54a1853 |
fix(dart): a library links the files its part directives name (#2391)
A Dart `part 'x.g.dart';` now mints an `import` node (name = the URI,
signature = the directive) and an `imports` ref from the library's file
node, in both extractors. The ref resolves through `dartDirectiveFile` by
the rules an import or export follows, so the library's file links the
part's file. A library and its parts are one library, so a change to a
part, generated or hand-written, now reaches the library and what imports
it in `codegraph affected` and the files listed as using the part. Before,
neither `part` nor `part of` left a trace, and the two files were joined
only by whatever symbol edges happened to cross between them: riverpod's
framework.dart, which holds nothing but `part` lines, by none.
`part of` still records nothing: the library's edge already joins the two
files, and a part -> library edge would list every pair on both import
rails of both files and make each a file 2-cycle.
The kernel's directive arm now goes on into the directive's children, as
the TS ladder does. Its `import_or_export` arm used to return first, so an
annotated directive's arguments (`@Tag(f) import 'a.dart';`) were fn-ref
candidates on the wasm path only.
Validation on rrousselGit/riverpod @4ba1be2 and felangel/bloc @b9be1e2
with the kernel loaded, base
|
||
|
|
837a1866f1 |
fix(dart): a call through an import prefix reaches only what that import exports (#2389)
A Dart call written through an import prefix - `http.get(uri)` after `import 'package:http/http.dart' as http;` - was resolved like a method call on a receiver named `http`, so it linked to any project member that fit the name: riverpod's docs' `http.get(...)` / `http.post(...)` calls went to a docs example's `Http::get` / `Http::post` by the receiver's capitalized name, and `fmt.describe(...)` to a class's `describe` method. Now `p.name(...)` calls a top-level declaration a library imported with that prefix exports (export chains, show / hide and parts followed through the library model), or nothing when the prefixed library is a package outside the repository. A constructor call (`p.Widget()`) instantiates the prefixed library's class. The same holds where the prefix heads a chain - `p.Box.create()`, `p.Report.empty()` - and for `p.Box<int>.named(...)`, which arrives as `Box.named`: the type is the one the prefix brings in. A parameter, local or member named like the prefix still hides it, and `const p.Box.named()`, which the extractors record as `p.named`, links nothing. The library model's one prefix reader is `dartImportPrefixes`: Dart annotations (#2390's `matchDartAnnotation`) read prefixes through it too, in place of the `isDartImportPrefix` it had alongside. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d2fb323e51 |
fix(dart): an annotation links the constant or constructor it names, even behind a comment (#2390)
A Dart annotation is a constant expression: a const variable (`@riverpod`, `@meta.immutable`, a type's static constant or enum value) or a call of a const constructor (`@Riverpod(keepAlive: true)`, `@Foo.named(…)`). It was ranked like a Python decorator, which favours a function or method of that name over a class and scores a constant not at all, so riverpod's 506 `@riverpod` annotations went to riverpod_analyzer_utils' extension getter `riverpod` (0.4) instead of riverpod_annotation's `const riverpod = Riverpod();`. matchDartAnnotation (name-matcher.ts) now takes every Dart `decorates` ref before any other strategy, and nothing falls through. It reads the annotation as written from its `@` and links a constant or (called) a class the file's library can see, through the import prefix it is written with (isDartImportPrefix, dart-libraries.ts); a type's own static constant, enum value or (called) named constructor (isDartConstructor); or, on a member, a static constant of the type around it. Never a method, getter or function. The decorator scan also stopped at the first sibling that was not an annotation, so a comment between an annotation and its declaration (`@override` `// ignore: must_call_super` `void f()`, riverpod_lint's `@riverpod` `// expect_lint: …` fixtures) hid the annotation. Dart's decoratorStepOverTypes lists its two comment kinds, which the scan now steps over, and the kernel's extract_decorators_for steps over the same. Validated on rrousselGit/riverpod @4ba1be2 and felangel/bloc @b9be1e2 with the kernel loaded: riverpod's 506 `@riverpod` edges move to `const riverpod` and 16 annotations behind a comment add an edge each, with nothing removed; bloc gains 7 `@override` refs and no edge changes. Kernel and wasm full-index dumps are byte-identical, and the parity sweeps show 0 diffs. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
631df0138e |
fix(dart): an import or export links the file its URI names, not a namesake (#2388)
A Dart `import` / `export` now resolves its URI through the library model in src/resolution/dart-libraries.ts: `package:<name>/<path>` is `<root>/lib/<path>` of the project package whose pubspec.yaml says `name: <name>`, any other URI is a path from the importing file, and a `dart:` library, a package from outside the project or a file the index does not hold links nothing. The ref never reaches the name-matcher, which took the URI's last segment for a file name (`package:flutter/widgets.dart` reached riverpod_analyzer_utils' widgets.dart from 130 files, bloc_tools' `package:args/command_runner.dart` its own command_runner.dart) or bound it to the importing file's own `import` node. When packages share a name and neither the importer's own package nor one enclosing it settles which, the importing package's `path:` dependency of that name (pubspec_overrides.yaml, then pubspec.yaml) does: bloc's two example apps each keep an authentication_repository. Library visibility reads the same answer. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9924684899 |
fix(dart): a member keeps the dartdoc written above its annotations (#2387)
A Dart member's annotations are siblings that stand between it and the
dartdoc written above them:
/// Builds the widget.
@override
Widget build(BuildContext context) { ... }
Both extractors read a docstring from the comments directly before the
declaration (or its `declaration` wrapper) and stopped at the first node
that was not a comment, so the annotation ended the run before the `///`
was reached. Members behind `@override`, `@protected`, `@mustCallSuper`,
`@pragma(...)`, `@internal` and the like lost their documentation, and
searching for words in it didn't find them.
Extraction (TS + kernel, at parity):
- getPrecedingDocstring takes an optional list of sibling types to step
over, and Dart sets `docstringStepOverTypes: ['annotation']`. Stacked,
multi-line and inline annotations are passed, and comments on either
side of one still join (`/// a` `@x` `// b` gives "a" and "b" on two
lines), as adjacent comments already did. Any other node still ends
the walk: the previous member's body, a field, a top-level variable,
an import or another declaration.
- The kernel's shared docstring.rs gains preceding_docstring_stepping_over.
preceding_docstring passes an empty list, so every other language is
unchanged; only Dart's docstring_of passes ["annotation"].
- Class-like declarations needed nothing: a class, mixin, extension,
extension type, enum or typedef node starts at its first annotation,
so the dartdoc above it is already the node's previous sibling.
Validated on felangel/bloc @b9be1e2 and rrousselGit/riverpod @4ba1be2 at
main
|
||
|
|
0c860c00b3 |
fix(dart): a bare call or type name reaches only what its library can see (#2386)
A receiver-less Dart call, constructor call or type name now links only to a top-level declaration its library can see: one in the library itself (the file and its parts) or one exported by a library it imports without a prefix, through export chains and show/hide. A type written through an import prefix (`p.Report`) means what that import exports. It is a candidate filter ahead of the ranking in matchByExactName and matchFuzzy, so a visible namesake wins, and a library's own declaration shadows an imported one. The library model (pubspec packages, package: and relative URIs, part / part of, conditional imports) is src/resolution/dart-libraries.ts. A bare call also never reaches another type's enum constant or static constant, a generic `extension<T> on X` counts as unnamed, and an unnamed extension applies in every part of its library. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
249d9a860e |
fix(dart): a comment inside a member chain no longer loses a call or a read (#2385)
tree-sitter-dart keeps a comment as a named node wherever it is written, so in a member chain broken over lines (`tester //`, then `.state(…)` on the next line: the empty `//` keeps dart format from joining the lines) the comment, not `tester`, is the sibling right before the `.state` selector. Both extractors (the TS walker and the native kernel) read that sibling as the receiver or the callee: - A call with a comment between the member and its arguments (`box.grow /* by */ (3)`) was lost, and recorded as a read of `box.grow`. - A getter read (`box //` + `.area`) or a static access (`Config //` + `.instance`) written after a comment was lost. - A call after a comment kept only its bare name. Since #2376 the resolver reads such a chain link back past comments, so those calls already linked correctly; they now carry the receiver in the name. Every sibling step along a member chain now skips `comment` and `documentation_comment` nodes: the callee before an argument part, dartReceiverOf / receiver_of, dartCalleeOfArgPart, the type in front of a constructor's type arguments, and the member read's receiver and next selector. The misparsed-generic-call gate keeps reading raw siblings, and now rejects a comment against the `<` (`ref.read /* c */<Repo>(p)`), so that shape stays a comparison instead of losing its read. Validated with the kernel loaded on felangel/bloc @b9be1e2, rrousselGit/riverpod @4ba1be2 and flutter/samples @63411527. No edge is added or removed: the 14 comment-split chains there are all in riverpod, and its 5 calls among them gain their receiver and stay unlinked. Kernel/wasm parity has 0 diffs on all three. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5f5b8db620 |
fix(js,ts): find a store exported by an export { } list when its name starts or ends with $ (#2384)
TreeSitterExtractor.isExportedLater and its kernel mirror is_exported_later
bounded the name in the `export { ... }` branch with `\b`, which takes a `$`
for a separator. It never fires beside a leading or trailing `$`, so
`const items$ = create(...)` + `export { items$ };` (or `export { $items as
default };` for a `$items` store) never matched, and the store's actions
were not extracted on either engine. It also matched inside a longer name:
`export { useStore$ }` or `export { $useStore }` counted as exporting a
store named `useStore`.
The clause branch now bounds the name with a character that can't continue
an identifier. The regex crate has no look-around, so the bounds consume,
and both sides spell the class in ASCII, since the crate's `\w` is Unicode
while JS's is ASCII. Plain names decide as before apart from the two false
positives. A non-ASCII letter written against the name (`export {
éuseStore }`) used to split the engines (the kernel's Unicode `\b` said not
exported, the wasm path exported); both now give the wasm answer.
Adds kernel/wasm parity cases (ts/tsx/js/jsx) inside 'a store exported by a
later statement', and the same cases in store-exported-later.test.ts, for
`items$`, `$items as default` and both false-positive shapes. They fail with
the unfixed kernel and with the unfixed TS on the wasm path, and pass with
both fixes.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
d500e58a52 |
fix(dart): link a call chained on a generic lookup like BlocProvider.of<T>(…) (#750) (#2383)
When a generic call parses as a call (a named argument after a positional
one, as in `(context, listen: false)`, or a type argument like `<A?>`,
`<A, B>` or `<List<A>>`), the Dart extractor records a chain on it as
`BlocProvider.of().increment`. The resolver typed that receiver by what
`BlocProvider.of` is declared to return: `T` for `static T of<T>(…)`,
which names no type, and nothing at all when the lookup comes from a
package, as flutter_bloc and provider do in an app. So
`BlocProvider.of<CounterCubit>(context, listen: false).increment()` and
provider's `Provider.of<Cart>(context, listen: false).add(item)` linked
nothing.
The declared return type is still tried first, so `Foo.create().bar()`
with a concrete return type resolves as before. When it gives no edge,
the chain is read at the call site the way #2376 reads a later link of a
chain: the lookup's own type parameter is the type argument the call
gives, and an outside `of<T>(…)` lookup hands back its T. The edge stays
on the encoded ref, as in Go's fallback (the gin runaway).
The extractors are unchanged. A chain on a call parsed as two
comparisons keeps its bare name (#2375), and now reaches the same
method; the comments that gave the old reason are corrected.
Validated before/after with the native kernel at
|
||
|
|
1b752de2c0 |
fix(dart): a member with no body keeps its dartdoc and annotations (#2382)
A Dart member written without a body - a constructor like `Foo._();` or
`Point.origin() : x = 0;`, a `const` constructor or redirecting factory
such as flutter_bloc's `const BlocProvider.value(...)`, an abstract or
`external` method - parses as a `declaration` wrapping its signature,
and its `///` dartdoc and `@annotation`s are siblings of that wrapper.
Both extractors looked for them before the signature, inside the
wrapper, and found nothing: the docstring was lost, and
`@visibleForTesting Foo._();` emitted no `decorates` reference.
Extraction (TS + kernel, at parity):
- A Dart-only `getDeclarationWrapper` hook returns the `declaration`
when the signature is its first named child. `docstringFor` and
extractDecoratorsFor's preceding-sibling scan start from it; the
kernel's `declaration_wrapper` does the same for every docstring
lookup and for the annotation scan.
- The shared DOCSTRING_WRAPPER_TYPES / `is_wrapper` list is untouched:
C/C++ share it, and their `declaration` wraps declarators. All 35
non-Dart parity fixtures extract byte-identically before and after.
Validated on felangel/bloc @b9be1e2 and rrousselGit/riverpod @4ba1be2
at main
|
||
|
|
3f96f80fcb |
fix(dart): link generic calls the parser reads as two comparisons (#2375)
tree-sitter-dart parses a generic call with arguments as two comparisons about as often as it parses it as a call, depending on the code around it: `ref.read<Repo>(p)` comes out as `relational_expression(relational_expression(ref.read < Repo) > (p))`. Generic constructors (`Provider<int>((ref) => 0)`), named arguments (which then parse as a `record_literal`) and calls after `await` or a prefix operator are hit too. Neither extractor recorded a call for that shape, only a member read of `ref.read` that linked nothing, so providers, repository reads and bloc lookups written with a type argument had no callers. Across felangel/bloc, rrousselGit/riverpod and flutter/samples there are 2,853 such calls, against 2,836 generic calls with arguments that parse as calls. - Both extractors (the TS walker and the native kernel) recover the call at its `<` when the type argument names a type and the code is laid out as a call: `<` against the callee and `(` against the `>`. A comparison such as `a < b` is left alone. The recovered call gets what a parsed one does: the call at the `<`, a reference to the type argument, and no member read of the callee or of a prefixed type argument. - A call chained on a recovered call keeps its bare name. The `BlocProvider.of().increment` encoding a parsed chain gets resolves through what `of` returns, which for a generic factory is its type parameter, so it would drop the edge the bare name finds. - When the grammar ends an arrow function in front of a generic call (`(ref) => ref.watch<int>(p)` as `((ref) => ref).watch<int>(p)`), the receiver is read from the end of the arrow's body, so the call is `ref.watch` rather than a bare `watch`. Validated with the kernel loaded: bloc +85, riverpod +1,580 and flutter/samples +54 edges, none lost; kernel/wasm parity has 0 diffs on all three repos. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
652c36b72a |
fix(dart): index const constructors and redirecting factories; a call through a type stays on that type (#2380)
* fix(dart): index const constructors and redirecting factories; a call through a type stays on that type Both Dart extractors (the TS walker and the native kernel) skipped `const` constructors and redirecting factories (`const Foo.bar(...)`, `const factory Foo.bar() = _Bar;`), which parse as signature kinds of their own. A call such as flutter_bloc's `BlocProvider.value(...)` then found no method on BlocProvider, and the resolver guessed another type's `value`: on felangel/bloc, 89 calls linked to `RepositoryProvider::value`. Extraction (TS + kernel, at parity): - Named const constructors and redirecting factories are methods named by the constructor, returning their class, with a parameters-only signature; the unnamed ones stay unindexed. The name is read only from before the parameter list, so a redirect target (`= _Impl.named`) never names it. - A redirecting factory references the class it redirects to, not the target constructor's name or an import prefix. - A constructor called with type arguments keeps its type: `BlocProvider<CounterCubit>.value(...)` is `BlocProvider.value`, as a statement and as an expression (`constructor_invocation`, which produced no call at all before). Resolver: - A Dart call through a type's name reaches only that type's own constructor, static member or static constant, or nothing: no guess by name, no inherited lookup, no same-file look-alike, no instance member of the same name. SDK calls like `Uri.parse` no longer land on a project `parse` past an `extension on Uri`. - A named constructor is never reached by a bare name, the last link of a chain, a function value or an annotation (riverpod's 210 `@internal` annotations). Validated on felangel/bloc @b9be1e2 and rrousselGit/riverpod @4ba1be2 with the kernel loaded: bloc 18,338 -> 18,495 edges, riverpod 85,606 -> 85,854, every removed edge triaged. Afterwards every edge into a named constructor is written `Owner.ctor`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dart): read a type's static members one way for calls and chains - A call through a type's name now finds its member with dartStaticMember, the lookup a chain's static link uses since #2376, so both pick the same member: the call site's own file first, then the nearest copy. - isDartStaticMember's constructor test is isDartConstructor, anchored at the declaration: an instance getter declared just above a same-named factory (`Object? get error` over `const factory AsyncValue.error(...)`) read the factory from the next lines and counted as static. - clearNameMatcherMemos drops DART_CONSTRUCTORS with the other source-derived Dart memos. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4d6168a701 |
fix(dart): a bare call to a parameter or local links nothing (#2379)
A receiver-less Dart call, or a function passed by name, whose name a parameter, local, loop or catch variable, or pattern variable around it binds now links nothing, instead of a same-named top-level symbol in another file or a member the class inherits; a local function declared there still links. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0571286907 |
fix(js,ts): link calls through an import whose name contains $ (#2378)
The ES import regex in extractJSImports captured default and namespace
bindings with \w+, which stops at a `$`. So `import items$ from './store'`,
`import $ from './dom'` and `import * as ns$ from './ns'` failed the whole
statement and produced no import mapping, and `items$.getState().inc()`
never reached the store action. The `as` split of named imports, the
`a: b` split of a CommonJS destructuring and the re-export checks had the
same \w-only shape. Some bound a cut-off name instead (`{ a as b$ }` bound
`b`, `{ default as items$ }` bound `items`), and `export { a$ } from` was
dropped from barrel chasing.
Capture every binding with the ASCII identifier class [A-Za-z_$][\w$]*,
keeping the `type`-modifier lookahead: `import type from './x'` still
binds `type`, and `import type $T` / `import type from$` now bind. A
default binding is never followed by `{`, so the `${` of a code
generator's template literal (`import ${x} from '${src}'`, as in qwik's
optimizer) is not taken for a binding.
importShadowedAt and hasParameterBinding bounded the name with \b, which
never fires beside a `$`, so a local `const items$ = ...` or an `items$`
parameter did not shadow the imported store. Named `$` imports already
got those wrong edges on main, and the mapping fix would have extended
them to default and namespace imports. Bound the name with (?<![\w$]) and
(?![\w$]) instead.
Resolution-only: the kernel has no import-mapping mirror. A store
exported by `export { items$ }` (the \b in isExportedLater's clause
branch, left out of #2366) is a separate extraction fix.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
5a11cd7ab7 |
fix(vbnet): link fields and properties read or written through a value (#2377)
VB.NET linked a member read only through a type or module name (#2355), so a read or write through an instance -- a parameter `x.Normal = 3`, a local `h.Normal`, a field `_h.Title`, `Me._h.Normal` -- produced no edge, and `codegraph callers` on an instance field or property listed almost none of its uses. - Extraction (TS only; VB.NET is not kernel-routed): every member read or write is sent with its receiver as a path, whatever the receiver's case: `x.Normal`, `Me._h.Normal`, `MyBase.Count`, `.Value` in a `With` block (nested `With .Inner` included), `.Switch` in an object initializer `New BoolParam With {...}`, and `{T}.X` for a value whose type the code writes (`DirectCast(o, T).X`, `New T().X`). A `With` block's `.Run()` call, which recorded nothing, is sent the same way. The grammar splits `New System.Drawing.Size(...)` into members read through `New System`; those, anonymous types' `New With {.X = 1}`, and paths that start with System, Microsoft or My are not sent. - Resolution (vbnet-receivers.ts): each link of the path is typed by the rules calls use since #2351 -- declarations, casts, `For Each` over a typed collection, call results, type-parameter constraints, Module variables, namespaces and Imports -- and the read links the member the type declares or inherits. A method named without parentheses is called (unless AddressOf or NameOf only names it), and `x.Items(0)`, an index that VB.NET writes as a call, reads `Items`. An outside, `Object`-typed, ambiguous or untyped receiver links nothing; nothing is guessed by name. - "Color Color": a value named like its type (`theme As Theme`) now reads an instance member through the value, so only the member is linked; a Shared member, a Const (read from its declaration) or an Enum case still links the type as well, as #2355 did for every such read. - membersNamed looks a type's members up in its parts' files rather than with one query per member name. The graph is identical on SCrawler; on staxrip, whose AutoCrop tool redeclares `ServerInfo`, 35 reads that main typed through the other project's field, and so left unlinked, now link (70 edges). Verification: vbnet-instance-member-refs.test.ts (11 tests) fails 9/11 on main and passes; one vbnet-shared-member-refs assertion now expects `OtherSession::SessionId` from `Shadowed(appSession As OtherSession)`; the VB.NET suites, tsc and the full suite pass (462 files, 5,859 tests; 22 kernel/platform files skipped). The issue's repro on the built CLI links every read and write, and `codegraph callers` lists `Run` for `SharedCache`, `Normal` and `Title`. Validation, main vs this branch: nodes unchanged, self-loops unchanged. SCrawler 17,437 -> 31,344 edges, 1 removed (same edge, new metadata). staxrip 32,569 -> 71,362 edges, 585 removed: 318 same edge with new metadata (Color Color reads of instance members, qualified-name -> instance-method) and 267 class links of those reads, every one beside an instance member. About 42k added two-link reads were re-derived from the source text by a separate check; its 837 disagreements were all its own blind spots (constraints, Imports aliases, sibling-block locals, call results, staxrip's global `p`). No added edge crosses between staxrip and its AutoCrop tool. Cost (5-run medians): CPU 16.1 -> 19.3 s and 20.2 -> 25.4 s, DB 28.1 -> 43.2 MB and 34.9 -> 59.0 MB; the DB growth is the new edges plus reads through outside types kept as failed refs for re-resolution (#1240). Not covered: unqualified reads, reads through an index or a call result (`list(0).X`), Handles clauses, and calls through a chained receiver written directly (`g.MainForm.Update()`), which still resolve by name. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b7e05f3ccd |
fix(dart): link a chained call only through its receiver's declared types (#750) (#2376)
The Dart extractor keeps one receiver level, so the later links of a call chain reach the resolver by their bare name (`Provider.autoDispose.family(…)` → `family`, `events.map(mapper).transform(…)` → `transform`). They then fell to name matching and bound to whichever project method shared the name: on felangel/bloc a Stream's `transform` went to angular_bloc's `BlocPipe::transform`, and on rrousselGit/riverpod every `X.autoDispose.family(…)` went to one builder's `family` whatever X was. A chain link now reaches only the member of what the chain before it evaluates to, typed from evidence: the head (a type, a constructor call, a string, `this` / `super`, an import prefix, a local, parameter, field or getter with a declared type) and each link's declared type (a method's or getter's return type with the method's own type parameters substituted, a field's or abstract getter's type, a static constant's initializer). A lookup nothing declared answers for hands back its one type argument (`context.read<LoginCubit>()`). Without evidence it links nothing. Along the way: an annotated extension (`@internal extension X on Y`) is recognized as one, so its members reach member reads and chains; `super.m()` reaches the superclass's method instead of the caller itself; an extension on a type parameter of its own applies to the project's types; a spread's `...foo()` is a receiver-less call. Validated before/after with the native kernel: bloc 337 name-only chain edges on main → 123 kept (now typed), 106 retargeted, 106 removed (68 on a typed receiver lacking the member), +75 `super.` calls; riverpod 1,254 → 171 kept, 383 retargeted, 696 removed (137 provably wrong), +263. Kernel and wasm graphs are identical with the fix. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6421644ef2 |
fix(dart): link calls in top-level and field initializers (#2367)
Both Dart extractors (the TS walker and the native kernel) skipped the code in initializers. A `final`/`const` declaration minted its constant and stopped, and a field's or `var`'s initializer only had its types recorded (#2360). In Riverpod apps, where most wiring lives in provider closures (`final repoProvider = Provider((ref) => Repository(ref.watch(dioProvider)));`), what a provider builds with had no callers, and impact and explore flows stopped at the provider. - A `final`/`const` initializer is walked like a body, for its constant: calls, instantiations, static and member reads, types, closures with block bodies and local functions, function values. - A field's (instance, `static var`, typed, `late`) or a top-level `var`/`late final` initializer is walked for its class or the file, since neither mints a node. A local declaration's second variable (`for (var i = 0, j = n(); ...)`) stays its function's. - Each initializer is walked once: a new `walkInitializer` context method marks it, and the dispatcher's function-as-value scan skips it. Other languages' hooks are unchanged. - Resolver: a Dart call through a type's static constant (`FutureProvider.autoDispose(...)` on `static const autoDispose = ...Builder();`) links that constant, not another type's same-named method. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5b73d0fd46 |
fix(js,ts): find a store exported on a later line when its name contains $ (#2366)
TreeSitterExtractor.isExportedLater put the binding name into its regex
unescaped. A `$` in the name (`items$`, `$store`, `a$b`) became an
end-of-line anchor, so `const items$ = create(...)` + `export default
items$;` never matched and the store's actions (e.g. `function inc`) were
not extracted, while the kernel mirror escapes the name with regex::escape
and extracts them. The same anchor also matched where it should not: a
store named `items$` read as exported when a multi-line `export { ... }`
ended a line with `items`.
The kernel routes TS/TSX/JS/JSX by default, so the wasm path is what files
with parse errors (deferred to wasm), Svelte/Vue/Astro script blocks and
runs without the kernel get. Escape the name the way regex::escape does;
the guard limits names to [A-Za-z0-9_$], so only `$` changes. `\b` stays
as is on both sides: next to a leading or trailing `$` it never matches
(`export { items$ }`), the same in both engines.
Adds a kernel/wasm parity case (ts/tsx/js/jsx) for `items$`, `$store` and
`a$b` plus the false-positive shape; all 16 fail with the name unescaped
and pass with the fix.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
fe7542b6f6 |
fix(vbnet): index names that begin with a keyword (SharedCache, Dimension, Sub NewItem) (#2365)
The vendored grammar lexed the member modifiers as one prec-10 token and `Sub New` as one prec-100 token. Neither went through keyword extraction, and both outranked the prec(-1) identifier, so the lexer stopped at the end of a modifier spelled at the start of a name: `Public SharedCache` indexed a field `Cache`, `Public Dimension` a field `ension`, `Public Shared Shared1` was lost to a parse error, and `Sub NewItem()` became a constructor. `Dim` and `Const` were part of that token, which also kept the local-declaration `Dim`/`Const` out of keyword extraction, so `ConstVBV.Value = False` and `ConstructPath()` inside a method parsed as `Const` declarations. The modifiers and `Sub New` are now plain keywords. The old tokens also folded a newline into the keyword after it, which two parses relied on by accident: `Option` lines under a comment banner may now follow newlines, and upstream's top-level `file_attribute_section` is gone, so attribute lines above a top-level declaration belong to it. `Protected Friend` and `Private Protected` are two modifiers now; the extractor reads them as protected and private. Rebuilt with tree-sitter-cli 0.25.10 and emscripten 4.0.4. On SCrawler and staxrip, 3 fields get their whole names, staxrip recovers one method, and every other graph difference is an `imports` edge or a node start now on its own line instead of the comment line above. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
aeb8f95581 |
fix(kernel): find a store exported on a later line in CRLF TS/JS files (#2364)
The kernel's is_exported_later mirrors TreeSitterExtractor.isExportedLater's multiline regex, but JS's `m` flag treats `\r` as a line end while the regex crate's `(?m)` sees only `\n`. On a CRLF checkout (Windows, autocrlf) a line like `export default useStore;\r\n` matched in wasm but not in the kernel, so `const useStore = create(...)` + `export default useStore;` lost its actions (e.g. `function inc`) on the default kernel route for TS/TSX/JS/JSX. Compile it in CRLF mode, `(?mR)`: `^`/`$` then treat `\r` as a line end too, and the pattern body stays byte-identical to the TS regex. It agrees with the JS regex on CRLF, trailing whitespace and lone `\r` line ends, where an optional `\r?$` would still miss a lone `\r`. This was the only `(?m)` regex in the kernel. The other tsjs regexes are whole-string anchors, and the minified-bundle check splits lines on `\n` exactly as the TS does. Adds a kernel/wasm parity case (ts/tsx/js/jsx, LF and CRLF) that fails on the unfixed kernel for all four CRLF variants and passes with the fix. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f40db4b9cc |
fix(js,ts): index the functions in every named object literal, not only exported ones (#2300) (#2363)
Issue #2300: a function written inside an object literal became a symbol
only when the object was an `export const`. A plain `const api = {...}`,
an object declared inside an IIFE or a function, and a namespace hung on
the page (`window.WS = {...}`, `ns.mod = {...}`) produced no member nodes,
and no node for `WS` either. Calls made inside those members were credited
to the enclosing constant or lost, and calls into them resolved to
nothing. Script-tag JavaScript, written almost entirely this way, was
mostly missing from callers and impact.
Cause: the TS/JS extractor, and the kernel's tsjs mirror of it, minted
members only for exported object-of-functions; any other literal was
walked as one opaque initializer, and an assignment to a member path was
never a declaration. Resolution had no way to reach a member through
`App.init()`, `window.App.init()` or `App.utils.pad()`.
Fix (extraction, TS and kernel byte-identical): a named object literal
owns its function members (method shorthand, `key: function`, `key: () =>`,
generators; static keys only), whether it is declared at module scope,
in a function body or IIFE, assigned to a path (`window.App = {...}`,
`App.utils = {...}`) or assigned to a plain name at module level
(`dw_page = {...}`). Members are `function` nodes qualified under the owner
(`api::load`, `window.App::init`, `App.utils::pad`), the exported case
included; a path owner is named by its last link and qualified by the
path. Calls in a member are the member's; other values are walked where
they were before. CommonJS exports, prototypes, `this.x = {...}`,
call-argument literals, a name reassigned inside a function, and
generated or minified files keep the old shape. Minified files are now
also recognised by content, in both extractors, and the kernel's
`.min.js` pattern matches the TS one.
Fix (resolution): a member is reached through its object only:
`App.init()`, `window.App.init()`, `App.utils.pad()`, a sibling's
`this.render()`, a `const { init } = App` binding; never a bare `init()`.
A same-file holder is chosen by lexical block (an IIFE's own `App` first);
otherwise a global one (`window.App = {...}`, or a classic script's
top-level `App`) when the caller neither imports nor binds the name.
Arrow members keep the `this` of the method around the literal, in the
`this.x` resolvers too. A dotted-path holder is never reached by its last
name alone, and Svelte's `$store` rule now applies only in `.svelte`
components, so the new local holders are not taken for `$n` in plain
scripts. The lookups read each file once (destructuring patterns only
when the raw text has a `} =`) and keep only the last 32 files' scans.
Contributor PR #2310 (@danusha2345): adopted its model (owner-qualified
members exported or not, path owners, IIFE/local owners, static keys,
no bare-name reach, `<script setup>` contains edges, SFC languages, test
scenarios), re-implemented on current main. Not taken: the
EXTRACTION_VERSION bump (already 28), the extraction-time binding oracle
with its ref/edge metadata and kernel ref patching (replaced by
resolution-time lookups), naming the owner `window.WS`, rewriting
`window.X.m()` ref names, flipping exported members' isExported, and
unrelated bare-call changes.
Verification: new js-object-literal-members suite (native + wasm, 18
tests); the issue's shapes fail on main and the guard test fails without
the guards. kernel-tsjs-parity passes with CODEGRAPH_KERNEL_EXPECT=1
(object-literal owners LF/CRLF, minified bundles); kernel parity sweeps
show 0 diffs on DokuWiki, vue-realworld, TodoMVC, excalidraw and this repo.
Validation (kernel loaded, before = origin/main
|
||
|
|
023fc3111b |
fix(axum,actix): link a route to a handler written below its call (#2326) (#2359)
With rustfmt's wrapped form
.route(
"/multi",
get(handlers::multi_line_handler),
)
the route was never linked to `multi_line_handler`, even when it was the
only function of that name; the single-line form worked. Rustfmt wraps
every `.route(` past the line width, so most routes of a real router were
affected (31 of 364 handler references resolved in the reporter's router).
Cause: the Axum block of the Rust framework extractor recorded the handler
reference at the line of `.route(`, column 0. Every resolved reference then
passes the Rust scope gate (isRustNameInScope), which reads that one line
to see how the name is written (`handlers::f` names the `handlers` module).
On a line holding only `.route(` it found nothing and dropped the edge. The
same position also broke single-line routes whose handler name appears
elsewhere on the line: in `.route("/login", post(handlers::user::login))`
or `delete(handlers::user::delete)` the gate read the path string or the
method router as a bare use of the name. Actix's `web::resource(..)` chains
and App-level `.route("/p", web::get().to(h))` had the same weakness.
Fix (src/resolution/frameworks/rust.ts):
- The handler reference carries the line and column of the handler's own
name (its last path segment), for Axum and the three Actix forms; the
route node stays on the `.route(` / `web::resource(` line. The gate is
unchanged and still reads one line, the one the handler is written on.
- Axum's method routers are the top-level chain of `.route(`'s second
argument: a `get(` nested inside it is a call in a closure handler's body
(`post(|h| async move { h.get(CONTENT_LENGTH) })`), not a route. With the
reference moved, such a call would otherwise gain an edge.
- An Actix resource's method chain also ends at the `)` of the call it is
an argument of, so in `.service(web::resource("/").to(index))
.route("/count", web::get().to(get_count))` the resource keeps
`.to(index)` instead of taking `/count`'s handler.
Adopted from #2329 by @danusha2345 (the fix and the tests are theirs),
without its EXTRACTION_VERSION bump (already 28 on main); added a case for
a single-line route whose handler name the line also writes elsewhere.
Verification: __tests__/rust-route-handler-line.test.ts: 10 of 13 fail on
main in both wasm and kernel modes; 13/13 pass with the fix in both modes.
The issue's repro now prints `GET /multi [references] src/main.rs:7`.
Validation (main -> branch, route nodes and route->handler edges keyed by
route name/file/line and target QN/file):
- rust-lang/crates.io: 1 -> 5 linked (+4 wrapped routes).
- atuinsh/atuin server: 7 -> 12; +5 newly linked and 2 wrong targets
replaced (`GET /api/v0/me` user::get -> v0::me::get, `GET
/api/v0/record/next` a TUI `Events::next` method -> v0::record::next).
- launchbadge/realworld-axum-sqlx: 18 -> 19 (+1 wrapped chain link); 11
same-file edges now carry the handler's line.
- tokio-rs/axum (whole repo): 204 -> 204, 19 edges carry the handler's
line; 2 bogus `GET /` route nodes from `headers.get(..)` in closure
handlers in routing tests are gone.
- actix/examples: 116 -> 116; `ANY / -> index` x2 replaces two wrong `GET /`
edges (websockets/chat took the next routes' handlers; basics/hello-world
now records its real `.service(web::resource("/").to(index))`).
No other edge changed in any of the five repos.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
ed1a89fb0f |
fix(rust): a variant path references the enum that declares it (#2328) (#2354)
Using an enum only through its variants (`mode::Mode::A` in an expression,
`mode::Mode::A => 1` / `Mode::B => ...` in a match, a `Mode::C(x)` or
`Mode::D { .. }` pattern) recorded no reference to the enum, so code that
builds or matches variants was missing from the enum's callers and impact.
Only a type annotation (`fn takes(_m: Mode)`) produced one.
Cause: Rust was not in the static-member pass, which turns `Enum.value` /
`Type::CONST` reads into references for Java, C#, Kotlin, Swift, Scala,
Dart, PHP and C++, and a path to a variant that is not a call or a struct
literal reaches no other extractor.
Fix:
- Extraction (wasm extractor and native kernel, mirrored): a path whose
last segment is capitalized emits a `references` ref named by its
receiver (the segment before it; `Self` read as the impl's type, nothing
in a trait), positioned at the receiver, so a `mode::` prefix scopes it
exactly as it scopes a type annotation. Covers value reads, match arms,
`|` / `if let` patterns, tuple- and struct-variant patterns and a variant
passed as a value. A call's callee (`Mode::C(1)`, `Foo::new()`), a struct
literal, a longer path's prefix, a `use` tree and a lowercase receiver or
member emit nothing.
- Resolution (isRustNameInScope): a reference written `Name::Variant` or
`Self::Variant` lands only on an enum that declares that variant, after
the usual Rust scoping (module path, `use`, glob, prelude, std and
dependency imports). ripgrep's `Match::None` reaches `ignore`'s enum, not
the matcher's `struct Match`; std variants (`Ordering::Less`,
`Option::Some`) never reach a same-named project enum; associated consts
and functions (`Limits::MAX`, `Mode::ALL`, `Foo::new()`) reference no
type. `Self::X` is read as the impl's type, never through a path written
elsewhere on its line.
Adapted from #2330 by @danusha2345: the extraction half (both walkers) and
most of the tests are theirs. Changed here:
- The resolver accepts a declared variant only. #2330 also accepted any type
whose file holds a variable or const of the item's name; impl consts are
file-level variables in the graph, so that cannot tell a type's associated
const from another type's or a top-level one.
- The walkers check the node kind before looking at its parent, and the
body walk hands the parent down: #2330 called `parent()`, which walks down
from the tree's root, for every node of every body, which nearly doubled
the kernel's Rust extraction time on tokio (2.1 s -> 3.9 s); now within
noise of main.
- EXTRACTION_VERSION is not bumped again (already 28 on main).
Verification: __tests__/rust-enum-variant-reference.test.ts (10 tests):
6 fail on main in wasm mode, in kernel mode, and with this TypeScript over
main's kernel binary; 10/10 pass in both modes. The kernel parity case
covers every shape and asserts the refs exist; the kernel-parity sweep over
ripgrep, alacritty and tokio is 1004/1006 files byte-parity, 0 diffs (2
parse-error files defer to wasm by policy).
Validation (native kernel loaded, main -> branch, edges keyed by
QN/kind/file/line): ripgrep +822/-0, alacritty +917/-0, tokio +665/-0, all
`references -> enum`, node counts unchanged. Every added edge's source
reads `Name::Variant` / `Self::Variant` at its column and its target enum
declares that variant; no std enum targets. New symbol->enum pairs:
ripgrep +220, alacritty +253, tokio +254; enums with no referencer before
and one now: 31 / 13 / 31. One wrong target on tokio
(`oneshot::error::TryRecvError::Empty` -> mpsc's `TryRecvError`) comes
from the existing path rule that checks only the last module segment, which
type annotations share. Index time on tokio stays within noise of main
(interleaved runs; resolveOne time 27.7 s vs 31.2 s summed over workers).
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
7210d2fd4c |
perf(resolution): remove the JS indexing regressions since 1.6.2; the summary time covers the whole run (#2334) (#2362)
Indexing pretix took 2.2x as long since #2163 (issue #2334), and the bundled minified d3 was what #2344 left of CPython's 1.6.2 slowdown. Two JS/TS checks that first shipped in 1.6.2 redid whole-file work for every reference: - matchDestructuredCallResult (#2163) ran for every bare call in a file holding `const {` / `let {` / `var {`: per call it re-tested that pattern on the source, then stripped, blanked and brace-walked every line above the call and matched the binding regex over it. - jsFunctionLocalScope (#2226) re-stripped the calling function's lines above each reference. In a minified script every function's text runs to the end of its one line, so each strip was the whole file. CPU profiles, main -> this change: matchDestructuredCallResult 82.4 s -> 1.7 s and jsFunctionLocalScope 12.3 s -> 5.2 s on pretix; 35.4 s -> 0.37 s and 23.3 s -> 1.0 s on CPython's Lib/profiling + Doc/_static. Now: - A file's `const { … } = f(…)` bindings are read once per resolution: the blanked code, each binding's end, callee and keys, the block it sits in and where that block closes, and the spans the blanker read as regex literals. A call looks up its own name; only a call through a destructured name scans the code between that binding and itself for shadowing, and only once the callee is found to return the key (a `require(…)` destructuring, for one, needs no scan). - Blanking the text above a call on its own gives the file's blanked code up to the call: the stripper and blanker look past a character only for a comment opener's second character and for a regex literal's closing `/`. So the prefix's bindings are the file's that end at or before the call (a match never reads past its `(`, and one running past the call holds no `{`…`(` to start another), and its brace stack follows from where each block closes. For a call inside a span the file reads as a regex literal (a division, in minified code) the prefix is the code up to the `/` plus the short tail blanked on its own; where a binding statement could run across that `/` (inside its `{ … }` or `< … >`), and for out-of-range positions, the old per-call scan still runs. - jsFunctionLocalScope strips a function's lines once and cuts at the reference's line end; functions that start on the same line share the regex answer by (file, first line, reference line, name). - The new memos, and JS_FN_LOCAL_MEMO (never cleared before), drop with the other per-file memos in clearNameMatcherMemos. The summary line printed orchestrator.indexAll's durationMs, which covers extraction only, beside node and edge totals recomputed after resolution: the issue's 19.9 s run printed "in 1.7s". CodeGraph.indexAll and sync now report the whole run's wall time, which `codegraph init`, `index` and `sync`, the MCP auto-sync log and the telemetry duration bucket read. Verification: nodes, edges and unresolved_refs dumps keyed by file|kind|qualified_name|start_line are byte-identical to origin/main |
||
|
|
deac7710f2 |
fix(go): resolve unexported receivers and nested modules within the right package (#2323, #2322) (#2361)
Calls through a method receiver or parameter of an unexported type
(`func (s *server) Create() { s.service.AddItem() }`) had no edge: the
Go receiver inference only accepted PascalCase types (#2323). And only
the project-root go.mod was read, so a module in a subdirectory, or one
beside other modules as in etcd, treated imports of itself as
third-party (#2322).
Fixing either alone sends calls to the wrong package, because Go type
names are unique only per directory while types and methods were looked
up by name across the project. The receiver pattern alone sent 1,042 of
harbor's 1,998 new calls to another package's same-named type; reading
nested modules alone made harbor's caching wrappers call themselves and
put about 180 of etcd's type references on same-named methods. Both
share one fix:
- A lowercase receiver or parameter type is read only where a parameter
list puts it (receivers, parameters, multi-line lists, var blocks).
- Receiver inference also reports the type as written. For Go,
resolveMethodOnType takes the declaring package's directory (the
caller's for a bare name, the import's for a qualified one) and counts
only that package's methods, then the types its declaration embeds
(struct and interface embedding, read from the source) instead of the
name-based supertype union. The field-chain matcher scopes its struct
and field types the same way.
- The resolver reads the nearest go.mod of every directory holding
indexed Go files (no disk walk; a module under testdata/ or a _/.
directory serves only its own files) and maps an import path to the
module with the longest module path, the importing file's own module
breaking a tie. getGoPackageDir replaces getGoModule.
- A bare name written through a package (`job.OPCommand`) resolves
through the import into that package's directory, and a name-matched
candidate outside the qualifier's package is rejected; a method reached
through a variable that shadows an import is exempt.
Verification: new go-unexported-receiver (3) and go-nested-module (11)
tests: 12 fail on main (the 2 controls pass), all 14 pass. The issues'
repros go from 0 to 1 caller; the root-go.mod and `Server` variants are
unchanged.
Validation, main
|
||
|
|
9f69e47836 |
fix(dart): link getter reads, enum-extension members and more type positions (#2338, #2327) (#2360)
Issue #2338: a Dart getter read (`x.area`, `s.label`) linked nothing, so `codegraph callers area` was empty while `x.grow()` worked, and a member an extension adds to an ENUM (`s.shout()` through `extension ShapeInfo on Shape`) never resolved. Issue #2327: `callers Report` saw only parameter and return types; the type an extension is `on`, field types, and types named in bodies and initializers (`Future<Report?>.value(null)`, riverpod's `final reportProvider = Family<Report?, String>()`) recorded nothing. Causes: - The Dart body walker only emitted calls (a selector with an argument list); a member read is a selector without one, so it produced no ref. - For a typed receiver, resolveMethodOnType looks for `Type::member` and the type's supertype edges; an extension's member is `ShapeInfo::shout`. On a class receiver the call fell through to name matching, which found a unique `twice` by luck; on an enum the "type the project doesn't declare" guard (it only counts classes) returned null. - Type references were taken from function and method signatures only. Fix (TS extractor and the Rust kernel, mirrored): - A member read `x.area` / `x?.area` / `Type.getter` whose receiver is a plain name, and that is not a call, is sent as a `references` ref named `x.area` on the member. The resolver (matchDartMemberRead, ahead of every other strategy) types the receiver — a static read through a project type, a parameter or local in scope, else a field of the enclosing class — and links the getter that type reaches: declared on it or a type it extends, mixes in or implements, else added by an extension on one of those (an unnamed extension only in its own library). The edge is `calls`: a Dart getter is a method node, reading it runs it, and `calls` is what callers, impact and explore's flow follow (as Ruby's paren-less `x.area` and VB.NET's paren-less calls through #2355 already are). A field read, a getter of a type outside the project and an untyped receiver link nothing; there is no name-only fallback. Kotlin/Swift/C#/TS link no instance property reads today. - resolveMethodOnType, for Dart, falls back to a member an extension on the type (or a supertype) adds when no declaration of the type has it. - `references` refs for the type an extension is `on`, a field's declared type and initializer (from its class — Dart fields mint no nodes), an enum field, a top-level variable's type (from the file), a constant's initializer (from the constant), and every type a function body names: locals, generic arguments, casts, type tests, catch clauses. Skipped: a `new`/`const` constructor's class (already instantiates/calls), an import prefix (`p` in `p.Foo`), built-ins, and lowercase names, which in Dart are built-ins (`num`, `dynamic`) or error-recovery artifacts around syntax the grammar predates (dot shorthands). A class whose field holds its own type does not get a self-edge. Verification: __tests__/dart-getter-reads.test.ts (7 tests) and __tests__/dart-type-positions.test.ts (3 tests) fail 8/10 on main (both the TS path and main's kernel) and pass on both arms here; the 2 that pass on main are the controls. New kernel parity fixture TortureReadsTypes.dart (+CRLF); kernel-dart-parity passes with CODEGRAPH_KERNEL_EXPECT=1, and scripts/kernel-parity.mjs over bloc/riverpod/flutter-samples is 0 diffs (2,103 files byte-parity, 300 parse-error files defer as before). tsc and build clean; the issues' repros now list c/a/b for area/label/shout and Holder, ReportX, reportProvider and run for Report. Validation (kernel loaded, main vs this branch): nodes unchanged, 0 edges removed, no new self-loops. - felangel/bloc: 16,372 -> 18,276 edges (+550 getter-read calls, +1,354 type references) - rrousselGit/riverpod: 76,432 -> 81,909 (+598 getter reads, +13 extension-method calls, +4,866 type references) - flutter/samples: 26,133 -> 26,943 (+142 getter reads, +668 type refs) Spot-checked 16 getter reads and 16 type references against source. Index time within noise on riverpod (median of 5 interleaved runs); reads that link nothing are kept for re-resolution like failed calls (riverpod db 92.9 -> 103.2 MB). No EXTRACTION_VERSION bump: it already moved to 28 this release. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1b56cb1992 |
fix(explore): find COBOL copybooks by name, with every include site (#2342) (#2358)
`codegraph explore MYCOPYBOOK`, the `codegraph_explore` MCP tool and `codegraph context` answered "No relevant code found" for a COBOL copybook named in the query, while `query`/`node` found its include at once. Cause: the COBOL extractor records `COPY X` and `EXEC SQL INCLUDE X` as `import` nodes, and findRelevantContext filters every channel by HIGH_VALUE_NODE_KINDS, which leaves imports out on purpose (a JS/TS/Python import statement is noise in explore). With the include out of reach the text search fell through to its fuzzy fallback, so a member with a digit in it (never extracted as a symbol) came back as an unrelated program one edit away: CVACT01Y -> CBACT01C, CVCUS01Y -> CBCUS01C on CardDemo. Making COBOL imports "high value" alone would not have helped either: explore's renderer skips import nodes, and the import-to-definition step drops a COBOL include because its `imports` edge leaves the enclosing scope, not the import node. Change: - src/graph/cobol-copybooks.ts (new, shared derivation): resolves the COBOL words of a query to copybooks - every COPY / EXEC SQL INCLUDE of the member (case-insensitive) and the indexed copybook file (.cpy, or a file an include resolved to; a same-named program is not one). Gated on the project having COBOL files at all (one seek on idx_files_language), so no other language's query does extra work or changes. Plain lowercase words in a multi-word query are English, not member names. - findRelevantContext: a named copybook's top-level declarations, then its include statements, become entry points ahead of the kind-filtered channels, like an exact filename (at most half the slots when the query matched other things, include sites in those files first). They are not walked (BFS from a record layout spent the answer on whichever program MOVEs into it), the member is not re-searched as text (the fuzzy decoy), and an explicit nodeKinds filter without `import` is honored. - codegraph_explore: the copybook file is pinned like a path in the query; an include statement that is an entry point is grouped and rendered (a window around the COPY line) instead of skipped; a new "COBOL copybook" section lists every include site (path:line, COPY vs EXEC SQL INCLUDE, 25 lines then a count) or says the member has no indexed source (DB2 DCLGEN, compiler-supplied such as SQLCA/DFHAID). - QueryBuilder: three indexed lookups; CodeGraph.findNamedCopybooks exposes the derivation so explore and the context builder share one answer. No extraction change; no re-index needed. Verification: __tests__/explore-cobol-copybook.test.ts (8 tests, through ToolHandler codegraph_explore and buildContext): with src/ reverted to origin/main 6 fail (the 2 guard cases pass on both), 8/8 pass with the fix. tsc clean, npm run build OK. Issue repro fixed on the built CLI (explore + context) and through a real `serve --mcp` tools/call. Full suite on a heavily loaded box: 5716 passed, 25 failed in 15 files; 12 of those files pass alone, and the other 3 (function-ref, git-index-currency, worktree-detection) are 5 s / 60 s timeouts that fail on clean main as well in interleaved runs. Validation (both builds on one index per repo; query-side change only): - expressjs/express: 14 explore + 4 context queries byte-identical. - aws-mainframe-modernization-carddemo: 10 non-copybook queries byte-identical; 15 copybook queries all now return the copybook (when indexed) and every include site - before: 9 "No relevant code found", 4 unrelated programs, 2 without the copybook. - cicsdev/cics-genapp: 6 non-copybook queries byte-identical; 6 copybook queries fixed (LGCMAREA's 25 sites incl. multi-line EXEC SQL INCLUDE, lowercase lgpolicy, SSMAP with no indexed source). - In-process explore latency on express unchanged within noise. Thanks @popolusiak for the report. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8998697f1d |
fix(vue,ts): link calls in Vue templates and in top-level destructuring (#2340) (#2357)
`codegraph callers <composable>` missed most of the components using it in a Nuxt app (Issue #2340), through two extraction gaps: - A destructuring declaration at module or `<script setup>` scope (`const { a } = useFoo(1)`, `const [b] = useFoo(1)`) mints no symbol, and extractVariable skipped its declarator with `continue` before walking the initializer, so the call was never recorded (inside a function the body walker reached it). The declarator is now walked the way a function body walks it, with the enclosing scope on the stack: the file for module code, which a `<script setup>` block folds into its component. Defaults in the pattern and calls inside callbacks in the initializer count too. Mirrored in the native kernel (tsjs/extractors.rs), which extracts TS/JS in released builds. - The Vue extractor parsed only `<script>`, so calls in the template (`{{ useBar(link) }}`, `:to="useBar(link.location)"`, `v-if`, `v-for` sources, `@click="save(item)"`) made no reference. The new vue-template-calls scanner reads the root `<template>` quote-aware, collects interpolations and directive values, and names each callee the way a script call is named (`useBar`, `store.fetchUsers`). It skips `v-for` aliases and slot props in their element's subtree, arrow and function parameters, `$`-helpers, Vue's template globals, strings, regex literals, `new X()`, object-method shorthand, `v-pre` subtrees, custom blocks and non-HTML templates. Each call is a `calls` reference from the component at its own line and column; a bare call to the component's own Options API method is linked at extraction, since resolution only binds those through `this.`. Two resolution follow-ons, found validating on elk: - The vue-handler synthesizer resolved the leading identifier of every `@event` value, so `@click="save(item)"` got a second, line-1 edge, and `@click="query = ''"`, a slot prop's `hide()` or a `refresh()` destructured from useAsyncData were bound to any same-named function in another file. It now resolves only a bare handler name (`@click="save"`) and keeps its composable-destructure mapping. - The Nuxt auto-import rule answered a call to a file's own `clearError` or `navigateTo` with an edge from the caller to itself. A same-file declaration of that name now shadows the auto-import. Verification: new tests in vue-template-calls.test.ts (scanner shapes and four indexed projects, incl. the issue's repro), extraction.test.ts (module-scope destructuring in ts/tsx/js/jsx; script-setup + template refs, LF and CRLF) and kernel-tsjs-parity.test.ts (destructuring, kernel vs wasm). On main (src + kernel reverted) the 10 indexed/extraction tests fail and the 4 parity cases fail with the main kernel; all pass with the fix (CODEGRAPH_KERNEL_EXPECT=1). Issue repro on the built CLI with the kernel: callers useFoo 2 -> 5 (A.vue, B.vue, F.vue, G.ts, D.ts), callers useBar 0 -> 2 (H.vue, I.vue). kernel-parity.mjs over elk, nuxt/movies and fastify: 467/471 files byte-identical, 0 diffs, 4 deferred. Validation (kernel loaded, main vs branch, edges by natural key): elk 6282 -> 6633 (+443 / -92), vue-element-admin 2337 -> 2372 (+60 / -25), nuxt/movies 939 -> 959 (+26 / -6), fastify (plain JS control) no edge change. Every removed edge is a line-1 vue-handler edge whose pair is now linked at the handler's line (101), a name-collision guess (8), or an auto-import self-loop now resolved to the local function or route (14). Self-loops: elk 44 -> 30, others unchanged. Builds on #2341 by @drakeo338 (walk the destructuring initializer) and the template scan in #2073 by @L0garithmic. Co-authored-by: drakeo338 <paranoyouz@gmail.com> Co-authored-by: L0garithmic <L0garithmic@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a192df40f9 |
fix(telemetry): count late index uploads, treat usage as live ingest, purge before catch-up (#2333) (#2356)
Three bugs in the self-hosted telemetry services (telemetry-worker/ and
telemetry-dashboard/), reported against
|
||
|
|
6c61dd74ca |
fix(vbnet): link Shared fields and properties read through their class (#2305) (#2355)
VB.NET emitted a reference for calls only, so a value read or write through a type name -- `AppSession.SessionId`, `AppSession.CurrentUser = "demo"`, `Logger.Level` -- linked nothing, and `codegraph callers` on the field, the property and the class came back empty. The reporter's central session class showed 6 callers against ~460 referencing files. - Extraction (TS only; VB.NET is not kernel-routed): the static-member pass now covers VB.NET. A value read through a Capitalized name is sent as one `references` ref, `Name.Member`, the receiver kept as a call's is. Namespace roots (`System`, `Microsoft`, `My`, `Global`) and the built-in type keywords (`String.Empty`) are not sent. - Resolution (vbnet-receivers.ts matchVbMemberRead, ahead of the framework, import and name strategies): the receiver is looked up with #2351's VB.NET scoping, case-insensitively. A local, parameter, field, property or Module member of that name holds a value and links nothing; one typed as the type of its own name ("Color Color") reads that type. Otherwise the class, Module, Structure, Interface or Enum the namespaces, Imports and aliases around the read see is the type: the read links the member it declares or inherits (field, property, constant, event, Enum value, nested type; Shared before a same-named instance member, never the member the read is in) and, from outside the type, the type itself. A method named this way is a call made without parentheses (`calls`) unless AddressOf / NameOf only names it. - `AppSession.Items(0)`, an index into a Shared field that VB.NET writes as a call, now reads the field and the type instead of linking nothing. Contributor PR #2321 (@ChrisPrapas) had the right idea -- enable the static-member pass for VB.NET with a member-level ref -- and its test scenarios are adopted. Its resolution is replaced: it name-matched the receiver's bare name and checked a member only against the receiver's spelling. Ported onto main it passes its own tests, but binds a parameter `appSession As OtherSession` to AppSession's members, misses import aliases, and adds 8,652 / 13,554 edges on SCrawler / staxrip, about 2,000 per repo binding a receiver to another class's same-named member (a plugin form's local `Dim CONTAINER_MAIN` went to another form's control field). Verification: new vbnet-shared-member-refs.test.ts (7 tests) fails 6/7 on main and passes; the other VB.NET suites pass; tsc and build clean; the issue's repro lists Consumer.Run and Consumer2.Describe for SessionId, CurrentUser and AppSession. Validation, main vs this branch: nodes unchanged, 0 edges removed, no self-loops. SCrawler 14,893 -> 17,437 edges, staxrip 26,647 -> 32,568: Enum values 803 / 1,448, Shared fields and properties 363 / 1,459, the type read through 1,374 / 2,879, Shared functions called without parentheses 3 / 126, AddressOf / NameOf 1 / 9. Spot-checked against source: per-site SiteSettings, import aliases, escaped `[Date]` / `[New]`, nested enums, "Color Color" fields; designer `Point.Empty` and staxrip's AutoCrop duplicates stay unlinked. Index time within noise; value reads that link nothing are kept for re-resolution like failed calls (db 22.6 -> 28.9 MB, 28.3 -> 35.8 MB). Not covered: reads through Me / MyBase, unqualified reads, instance reads through a typed variable, Module variables as receivers, namespace-qualified receivers and With blocks. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: ChrisPrapas <129202926+ChrisPrapas@users.noreply.github.com> |
||
|
|
69ee3b9a85 |
fix(ui): fall back past a port Windows refuses with EACCES, and say why (#2299) (#2353)
On Windows, a port another program holds exclusively (SO_EXCLUSIVEADDRUSE)
or one inside an excluded port range fails listen() with EACCES rather than
EADDRINUSE. The `codegraph ui` port fallback only moved on after EADDRINUSE,
so it stopped at the first such port. The error then blamed the POSIX
privileged-port rule ("Ports below 1024 usually need elevated privileges")
for ports like 4747 or 49912. Confirmed on Windows 11: a .NET listener on
0.0.0.0:P with ExclusiveAddressUse makes Node's listen(P, '127.0.0.1') fail
with EACCES.
Fix:
- shouldTryNextPort moves past EADDRINUSE on every platform. It moves past
EACCES at any port on Windows, which has no privileged ports, and from
1024 up elsewhere.
- Below 1024 off Windows, EACCES is the privileged-port rule, and the next
port falls under it too. The walk stops there with the message it always
gave, rather than trying 20 privileged ports.
- describeBindFailure takes the platform:
- Windows names the exclusive hold or reserved range and how to list the
ranges (netsh int ipv4 show excludedportrange protocol=tcp).
- POSIX keeps the privileged-port text below 1024. Above it, it says the
system refused permission.
- A walk that ran out says "in use or reserved" (Windows) or "in use or
not allowed" when any port was refused rather than in use.
- Both helpers take the platform as a parameter, like browserOpenCommand,
so the tests check every platform's answer on any host.
This builds on PR #2319 by @sx4im, which advanced on EACCES, used the
Windows wording and added the issue's listen-spy test. That PR advanced on
EACCES on every platform and printed "Windows refused port N ... netsh" on
Linux and macOS too. On POSIX it would also walk 20 privileged ports and
end with "Ports 80-99 are all in use or reserved". This change restricts
both to where they are true. The changelog entry goes in the viewer-launch
file, because the viewer is not released yet.
Verification:
- __tests__/ui-server.test.ts gains three kinds of test:
- The issue's end-to-end test: port N is taken and N+1 is refused through
a listen spy, so the server must land past N+1 and serve 200.
- A pinned-port message test.
- Unit tests of the decision and the messages for win32, linux and darwin.
- Results on __tests__/ui-server.test.ts:
- Main source: 9 failed / 40 passed. The walk fails with "Not allowed to
listen on port 57423. Ports below 1024 usually need elevated privileges".
- With the fix: 49 passed / 2 skipped (existing POSIX-gated tests).
- Built CLI on Windows, with 0.0.0.0:<port> held exclusively:
- `codegraph ui` while 4747 is held: main exits 1 with the privileged-port
message. With the fix it serves on 4748 (GET / 200).
- `codegraph ui --port <held>`: prints the Windows message.
- A 3-port walk over held ports reports "in use or reserved".
- Re-listening on the same server after a real EACCES works on Node 22.22
and 24.16.
- tsc --noEmit is clean and npm run build succeeds. The 20 viewer test
files pass (439 passed, 5 skipped).
- Full suite: 5732 passed, 10 failed, 319 skipped. The 10 failures are
timeouts and EBUSY teardown under load in function-ref,
git-index-currency, index-daemon-rebuild and rust-self-owner. All four
files pass when run alone.
Issue #2299 reported by @ijbranch.
Co-authored-by: Saim Shafique <msaimshafique4664@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
26e8488aed |
fix(windows): the kernel-parity sweep loads the engine as file:// URLs (#2352)
scripts/kernel-parity.mjs handed import() a path from path.join(). On Windows that is `C:\...`, which Node's ESM loader rejects with ERR_UNSUPPORTED_ESM_URL_SCHEME, so the sweep could not run there at all and contributors kept patched scratch copies. The three engine imports now go through pathToFileURL(...).href, as the agent-eval probe scripts already do. On macOS and Linux an ordinary path resolves to the same file:// URL as before. Checked on Windows 11 after npm ci, npm run build and scripts/build-kernel.sh: the unmodified script threw at its first import; with the fix, the C# fixtures (3/3), src/search (4/4), all of src/ as TypeScript (261/263, 2 deferred to wasm) and every fixture language (42/42) run to the summary line and exit 0. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c9944147bb |
fix(vbnet): resolve receiver calls by declared type; guard .NET names, bare calls and ties (#2351)
VB.NET's resolver guessed a receiver call's target from the receiver's name whenever no class matched it, without any of the guards C# has. Indexing Structure members (#2345) gave those guesses new targets: 23 SCrawler `xxxFile.Delete()` calls on PersonalUtilities' SFile went to a nested struct's `TempFileConversion::Delete`, and staxrip's main app sent 90 calls to its AutoCrop tool's duplicate `ColorHSL`. - Declared types (new src/resolution/vbnet-receivers.ts): a receiver's `As` type decides the call (local, parameter, field, property, `For Each`, `As New`, ...). The candidates are that type's member, one it inherits, or an extension method written for it. A type from outside the project gets no link. - .NET standard method names (`Add`, `Contains`, `Clear`, `Dispose`, `ToString`, ...) on an untyped receiver need a receiver named after the owner, as in C#. VB.NET matches them case-insensitively. - A bare call, or one on `Me`, reaches the enclosing class, what it inherits, or a Module. A nested type is matched by its bare name only from inside its owner. Types resolve through enclosing namespaces, `Imports` (aliases included) and the caller's project. A tie between equally good guesses goes to the caller's file, then its project, then the nearer directory, or gets no link. - vbReceiverOf reads the name at or after the reference's column, so `Me.Size = New System.Drawing.Size(...)` is read through `System.Drawing`, not `Me`. Before/after on main (nodes unchanged): SCrawler 15,092 -> 14,893 edges, staxrip 28,002 -> 26,647. Of the non-containment edges removed, 855 / 2,802 are the same link with new resolver metadata. The rest: - .NET names on untyped receivers no longer guessed: 196 / 1,378. 731 of staxrip's are StringBuilder `sb.Append` calls that had gone to `LogBuilder::Append`. - Other receiver guesses dropped: 86 / 103, including the SFile.Delete calls. - Bare calls out of scope: 38 / 834. 590 of staxrip's are `New Point(...)` that had gone to a class nested in `ButtonEx`; others are WinForms `Refresh()`, `Focus()` and `Activate()`. - Retargeted: 227 / 532, e.g. 208 bare `Add(...)` calls in encoder classes now reach the inherited `CommandLineParams::Add`. 121 / 958 call sites resolve that did not before, e.g. `cms.Add` -> ContextMenuStripEx and `td.AddButton` -> TaskDialog. Designer -> Size links fall from 5 / 1 to 0; designer -> Add stays 0. Known trade-off, shared with C#'s guard: about 31 staxrip calls like `switches.Join(BR)` that may be the project's own `MiscExtensions` extension methods are no longer linked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
355afc70b1 |
fix(csharp): walk field and property initializers; a constant never names a type (#2337) (#2348)
* fix(csharp): walk field and property initializers as the member Field declarators and property `= initializer` values were skipped by both extractors, so `private readonly ILogger _log = LogManager.GetLogger(typeof(X));` linked nothing, and a method or class used only from an initializer looked unused. - tree-sitter.ts / csharp.rs: walk each C# field declarator whole with its field on the stack, and every property `value:` (not only `=> expr`) with the property on the stack. Calls, instantiations, static reads, lambda bodies and fn-ref candidates (varinit included) belong to the member; the fn-ref scan skips the walked subtrees, so each candidate is captured once. Attributes stay unwalked. - A target-typed `new()` that is the initializer instantiates the declared type (`List<Foo> _items = new();` -> List). In bodies it stays invisible, as before. - name-matcher.ts: a .NET type position never names a `constant` (the kind `const` / `static readonly` fields get). Walking initializers exposed `new Version(5, 18)` binding to a `const string Version` and `static readonly Meter Meter = new(...)` instantiating itself. Kernel/wasm parity: 0 diffs on serilog, Newtonsoft.Json and jellyfin, and full-index dumps byte-identical between the two arms. No EXTRACTION_VERSION bump: #2345 already moved it to 28 this release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): credit the report and contributor behind the constant change The type-position change here is what issue #2337 reports (`new Station { … }` unresolved beside a `private const string Station`) and the same one-line change as @drakeo338's #2339. Verified on the issue's own three-file repro: the `instantiates` edge to `Demo.Core::Station`, missing on main, is present with this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0c7d0f7d42 |
perf(resolution): remove the Python indexing regressions since 1.6.2 — graph byte-identical (#2332) (#2344)
Indexing CPython took about three times as long on 1.6.2 as at |
||
|
|
dea076fd1e |
fix(mcp,extraction): exit a daemon whose install changed; never store a grammar-load failure (#2335, #2336) (#2346)
* fix(mcp,extraction): a daemon whose install changed exits; grammar-load failures are never stored (#2335, #2336) A daemon that outlived an upgrade loaded grammars lazily from an install that no longer existed, and stored every file its watcher re-indexed as a zero-node row under the file's new content hash: no hash-based sync ever revisited those rows, and `codegraph status` called the index up to date. - The daemon checks its own package.json every 30 s (CODEGRAPH_DAEMON_INSTALL_CHECK_MS, 0 turns it off) and exits once the file is gone or carries another version, so the next session starts a daemon from the current install. - A result whose grammar failed to load (`parser_error`) is never stored: the file keeps its previous data and the next sync or index retries it, and a row an older engine stored that way is re-indexed even though its hash matches. The tag-based CFML path reports a missing grammar with the same code. - A launcher whose own install was deleted no longer crashes on the spawn error of the daemon it tried to start. - `codegraph status` and `status --json` (filesNeedingReindex, filesWithParseErrors) report indexed files whose symbols are missing although their content is current (#2336), and `files --json` carries each file's recorded errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): a session whose own install changed never claims the writer lock (#2335) A daemon now exits once its install is upgraded or removed, and nobody holds the writer slot for a successor at that point. Its sessions run the same replaced code and serve themselves in-process the moment it goes; one that found the slot free claimed it as its writer even though its engine could not load (a real npm upgrade: "Cannot find module './sqlite-adapter'"), and so kept a daemon from the current install from starting. Such a session now falls back read-only and says to restart it. Also adds the CHANGELOG entries for the install check and grammar-load failures (#2335) and for the status report (#2336). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
511d86e94d |
fix(mcp): replace a daemon left running by an older install (#2335) (#2343)
A daemon keeps running the code it started with. The 1.6.1 daemon in #2335 outlived an upgrade, kept loading grammars from the install the upgrade removed and wrote every file it re-indexed as empty, while every session from the new install served itself in-process, read-only, because the old daemon held the writer lock. Daemons from before this change cannot notice an upgrade, so the launcher acts: - A launcher that finds a daemon of an older plain X.Y.Z release stops it the way `codegraph daemon` does (daemon.pid and the socket hello must agree; SIGTERM, SIGKILL only if it still answers) and starts one from its own install. It finds the daemon by its hello on the launcher's own socket, or by the project lock when it listens elsewhere: on Windows, daemons before #2278 named their pipe after the root as typed. A daemon of the same, a newer, a prerelease or an unknown version is never touched, so two installs cannot take turns stopping each other's. - The writer slot never falls free on the way. The launcher swaps writer.pid to itself (mode `handover`) before the signal, clears the old daemon's leftovers while it holds the slot, and the daemon it spawns takes the slot over (CODEGRAPH_DAEMON_HANDOVER). Sessions of the old daemon that fall back in-process find the slot held and serve reads only, instead of claiming it with the removed install's code. A daemon that does not exit gets the slot back, and a successor outwaits a racing candidate that took daemon.pid first. Validated on Windows with real npm upgrades from the published 1.6.1 and 1.6.2 to this build packed as 1.6.3, with the old session calling throughout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
489b4749c7 |
fix(vbnet,csharp): index every Structure member; walk property bodies and VB initializers (#2345)
VB.NET's grammar tags each member of a `Structure` as its own `body`
field, so extractAggregate's field lookup saw only the first member.
SCrawler's UserMedia indexed none of its members, and a structure that
opened with a nested enum indexed only the enum. Aggregates now resolve
their body the way classes and enums do (resolveBody, which VB maps to
the declaration itself). Objective-C, the only other struct language
with that hook, is unchanged.
Property bodies were never walked: the propertyTypes branch set
skipChildren after extractProperty, so calls, instantiations and reads
in a VB `Get`/`Set` block, `= initializer` or `As New`, and in a C#
accessor body or `=> expr`, were lost.
- propertyBodies() names the parts of a property that run code (VB: the
whole declaration, as its methods are walked; C#: each accessor's body
and an arrow value), walked with the property on the stack. The
candidates-only fn-ref scan skips the walked subtrees by node id, so a
function value is captured once, from the property. C# `= initializer`s
stay unwalked, as C# field initializers are.
- The C# kernel mirrors it (property_bodies, scan skip list). The parity
suite and a new micro pass; a serilog sweep is 209/214 byte-identical
with 0 diffs (5 deferred for parse errors).
- VB field declarators (`= expr`, `As New T`) and Custom Event
AddHandler/RemoveHandler/RaiseEvent blocks are walked as their field or
event.
Before/after (nodes / edges): SCrawler 9,950 -> 10,427 / 14,336 -> 15,092;
staxrip 13,440 -> 13,644 / 24,120 -> 28,002, most of it its
`Property X As New NumParam With {...}` settings; serilog edges
6,642 -> 6,654. All 23 removed edges are the same call sites re-resolved
by VB's name-guessing resolver now that struct members are candidates:
10 wrong edges dropped, 9 correct ones now guessed wrong, 4 wrong either
way. Designer -> Add/Size edge counts are unchanged.
EXTRACTION_VERSION 27 -> 28.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
6560052a6f |
docs(changelog): promote [Unreleased] into [1.6.2]
[skip ci] Auto-generated by Release workflow.v1.6.2 |
||
|
|
0f0af7bf22 |
release: 1.6.2 (#2318)
Bumps package.json and ui/package.json (the component library is versioned with the engine; ui-package.test.ts pins it) to 1.6.2, and the matching version fields in package-lock.json — the top level, packages[""] and the ui workspace, whose entry was still at 1.6.0. Only those three fields change; `npm ci` accepts the lock. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
217190680d |
fix(telemetry): recover from the D1 10 GB outage; dashboard starts on today (#2317)
The telemetry database hit D1's 10 GB cap on 2026-08-11 and refused nearly every write for seven weeks: the dashboard kept showing Aug 9 as the latest day, and its activation query (a cohort join over raw events, ~55 s per week) stalled D1's single query lane until every panel failed. Root cause was the client: usage counters were aggregated per process and appended on exit, so every `serve` launch, CLI command and prompt hook uploaded its own `count: 1` line - 30.3M of the 30.8M event rows. - Client: merge count lines per (day, kind, name, client) on append, stale-claim recovery (which also bypassed the size cap) and send. - Ingest: usage counters ADD into usage_daily, one row per machine x day x tool (migration 0003), so storage no longer depends on upload frequency. - Rollup: reads usage from usage_daily, folds legacy usage rows out of events in 50k-row transactions, maintains machine_first_seen.first_index_day (0002), catches up on days a failed run missed, purges usage_daily with retention. 0004 drops the now-unused events_machine_day index. - Dashboard: ranges end today; rolled-up series return null past the rollup's last day instead of a phantom zero; no panel reads raw events (activation reads first_index_day); min/max lookups stay index-only; one retry on transient 5xx; banners when ingest stalls or the rollup falls behind. - scripts/backfill-rollup.sh re-runs the rollup over a day range. Already applied in production: DB 10 GB -> 467 MB, all usage counts kept, rollups current through 2026-10-02. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
dbb3d63cb1 |
fix(mcp): wait for a stopping daemon, and for the proxy's fallback engine (#2311) (#2316)
* fix(mcp): wait for a stopping daemon, and for the proxy's fallback engine (#2311) Now that QueryPool.destroy() waits up to 15 s for a query worker still starting up (#2307), two callers gave up too early: - stopDaemonAt() gave a daemon 3 s after SIGTERM, then re-probed its identity and reported it still running once the daemon had closed its socket, so a daemon waiting on a starting worker made `codegraph index` abort. A daemon that has closed its socket but still holds its lock is now given 17 s more to exit; nothing is signalled during that wait. - The proxy's in-process fallback called engine.stop() and exited on the next line. It now exits once its engine, and any engine still being retired after a handback to the daemon, has stopped, with a 20 s backstop. Once shutdown starts it ignores client input and starts no new engine. Extracted from bompus/codegraph@cc87ec34da, a fork merge commit that cannot be cherry-picked whole; only these four files are taken, unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): a stopping daemon is waited for whether it closed its socket or released its lock (#2311) Builds on the previous commit: - stopDaemonAt: one rule after the first 3 s. If the daemon's identity can be re-proven (same lock, socket answers), escalate to SIGKILL as before. Otherwise, whether it has stopped answering its socket or already let go of its lock, it is partway through its own shutdown: signal nothing more and wait, bounded, for the PID to exit. The previous commit reported a daemon that had released its lock but not yet exited as still running, a race at the end of every slow shutdown. The cleanup that follows compares the lock with the one signalled, so a successor's is kept. On Windows, where SIGTERM is TerminateProcess, the same wait covers a termination still settling. - The grace is WORKER_START_SETTLE_MS + 2 s and the proxy's backstop WORKER_START_SETTLE_MS + 5 s, so they move with the settle wait they cover. stopDaemonAt takes `shutdownGraceMs` so a test can bound it. - The proxy says so on stderr when its backstop fires. Tests: the daemon fixture's setup and cleanup are shared, and the fixture process is always killed on cleanup (a failing run used to leak it). New: a daemon that released its lock mid-shutdown is waited for; a daemon that never finishes stopping is still reported after the grace and never SIGKILLed; the proxy waits for an engine being retired at shutdown; and client input arriving once shutdown has begun starts no engine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Aaron Queen <bompus@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ad60dccd04 |
test(windows): the windowsHide guard fails closed on spawns it can't trace (#2312) (#2315)
the watcher's `git config --get core.excludesFile` call. That report is against 1.6.1; the call (and the `ls-files --exclude-standard` one next to it) already runs with windowsHide on main since #2280, which has not been released yet. An audit of every child_process call in src/ found nothing else missing it: 31 call sites, 29 hidden, 2 documented exceptions (macOS-only vm_stat; the detached browser opener). What #2280's AST guard could not see is how the next regression would slip in. It only followed static named/namespace imports and passed silently on anything else, so each of these shipped green with no windowsHide: - child_process loaded by a default import, `import = require`, `require()`, `import()`, or a re-export - a spawner aliased or passed as a value (`const run = execFileSync`, `promisify(execFile)`, `const { spawn } = cp`) - options in a `let`, or a later spread that may set windowsHide back - an options parameter of an exported wrapper (callers in other files), a wrapper used as a callback, or a wrapper call that omits the options The guard now fails closed on all of them, and a second describe pins each shape (plus the compliant ones it must keep passing) so the guard itself can't go blind again. Also credit #2312 on the existing changelog entry and drop the duplicate copy of that entry #2280 left under [Unreleased]. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2965b7a349 |
docs(changelog): ready [Unreleased] for 1.6.2; release verify waits up to 5 min (#2314)
- Adds the Highlights block and groups the 164 entries the way 1.6.1's notes are: New Features for the newly supported routes and newly indexed code, Fixes under sub-headings (Windows; the background server and CLI; indexing; across languages; then one per language family) so a skimmer finds their area. Duplicates left by merges are folded together (the Windows console window entry twice; two overlapping Vapor closure-route entries; three Windows crash entries into one). No entry's wording is otherwise changed. - The release workflow's "Verify every package is actually on the registry" gave each package 60 s. 1.6.1 published all seven packages fine, but the first took longer than that to appear and the run went red. It now waits up to 5 minutes per package. No viewer entries: those stay in docs/viewer-launch-changelog.md, and `codegraph ui` stays behind CODEGRAPH_UI=1 (checked on a fresh build: absent from --help, refused with the "not in this release yet" message). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
70fe0ecc52 |
fix(windows): helper threads never end mid-start or with GC marking in flight (#2309)
On Windows a worker thread that ends - terminate(), its own process.exit(), or the process exiting - while V8's concurrent marker is marking its heap crashes the whole process (exit 3221225477 / 0xC0000005, no error, no dump). Bisected on the Windows 11 ARM64 VM: --no-concurrent-marking alone stops it (18/480 -> 0/480; no other concurrency flag does), but it costs about a third of indexing speed, so CodeGraph avoids the dangerous moments instead: - src/worker-teardown.ts: owners terminate a worker only once it has started (its first message, posted after its modules load - mid-load is by far the worst moment), and a worker about to exit runs a full collection first, which finishes any marking in flight (gc reached at runtime, never global). - Resolver pool / store writer: the worker collects before exiting on 'close' (the normal path - main 21/480 crashed on the VM, now 2/1200), and the 5 s close fallback never terminates a worker still starting. - codegraph_status change counts: the worker posts 'loaded' after its heavy require and collects before answering; a deadline answers unknown at once but terminates only once loaded (3/720 -> 0/1440), and MCPEngine.stop() waits for measurements in flight. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3d86bcc037 |
fix(windows): the query pool never terminates a worker still starting up (#2307)
Terminating a worker thread while it is still loading its modules can crash the whole process on Windows (exit 3221225477 / 0xC0000005, no error, no dump). QueryPool.destroy() terminated every worker at once, and the daemon destroys its pool whenever it stops - a session that ends within a few seconds of starting had its eager worker in exactly that state. MCPEngine.stop() didn't even wait for the pool before the daemon called process.exit(). - QueryPool tracks each worker's start (settled on 'ready', error or exit); destroy() answers outstanding calls first, then terminates each worker once it has started, capped at 15 s - the parse pool's #2301 mechanism. - MCPEngine.stop() awaits the pool. - #2301's explanation is corrected: on the Windows VM, terminating workers mid grammar compile never crashed (0 in 2,161), but terminating them while they load CodeGraph's extraction modules did. The parse pool's wait already covers that window, so only its comment and changelog wording change. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9d4e805bec |
test(windows): daemon-pid-reuse cleanup retries a still-held temp dir (#2304)
afterEach SIGKILLs the servers and the detached daemon, waits 50 ms and removes the temp dir. A killed process releases its handles asynchronously on Windows, and under full-suite load the removal hit EPERM on the VM (the test itself passed; it passes 3/3 alone). Retry the removal like the other process-spawning suites do. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e0f9c95e2f |
fix(resolution): a busy machine doesn't boot the resolver pool for a small project (#2303)
The resolve loop boots the worker pool mid-run when the remaining refs' projected sequential settle (observed per-ref rate x refs left) clears 400 ms. Load inflates the observed rate, so on a loaded 4-core Windows VM a ten-file project (19 refs) projected 506 ms for its last 11 and booted three workers in 122 of 240 runs under 24 parallel inits (0 of 80 at 8). The pool only fans out batches of 1,000 refs, so it resolved nothing; synthesis then waited out its boot (dedupe-merge 6.3 s vs a 6.5 s boot) and its three extra database connections pushed the VM's free commit to 11 MB. Adaptive engagement now also needs 20,000 refs left. tokio-class Rust repos (~55k refs at ~36 us) still engage; a smaller remainder never repays a seconds-long boot. With the floor, the same load booted the pool in 0 of 240 runs. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
91b9bba133 |
fix(extraction): never terminate a parse worker while it is loading grammars (#2301)
On a loaded Windows machine `codegraph init` / `index` sometimes died right after the parse phase with 0xC0000005 (STATUS_ACCESS_VIOLATION), no WER report and no output past the last parsed file. The parse pool's destroy() terminated every worker at once, and a short index often ends while a late-spawned worker is still compiling its grammar WASM; terminating a worker with that compile in flight crashes the process. Reproduced on a Windows 11 ARM64 VM without CodeGraph's pipeline: starting three parse workers with preloaded grammar bytes and terminating them mid-load crashed 5 of 720 child processes, terminating them only after grammars-loaded crashed 0 of 360. Under the real load (24 parallel `codegraph init` runs on 4 cores) main crashed after parsing in 8 of 1,200 runs; with this change 0 of 1,080 did. destroy() now waits for a worker that is still loading to report grammars-loaded or exit before terminating it, capped at 15 s so a wedged load can't hold teardown forever. Recycling only ever touches idle workers, and a hung parse is still killed by the hard timeout. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8abebda02c |
test(windows): two #1910 tests no longer time out on Windows (#2298)
oversize-file-not-read's drift test wrote a 1.4 MB `.js` made of one statement repeated 110k times. Windows Defender's script scan of that freshly written file takes about 54 s on its first read (the viewer's read in the test), so the test always timed out on Windows and its cleanup then hit EPERM on the still open file. A varied 2.4 MB `.js` and a real 2 MB bundle scan in 35 ms and 144 ms, so this is the fixture, not something users hit; the fixture now has a different statement on every line. The mpeg-ts git-path cases build a git repository and index it, the last one twice. At ~100 ms per git process on Windows that is 5–6 s, past vitest's 5 s default; the describe now allows 30 s. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |