mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 13:53:01 +08:00
74b599c86b013dfd3c272e1c4e4474ffb7ff3b0c
425 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
74b599c86b |
feat(impact): RN native→JS events via the sendEvent(ctx, "X", body) wrapper
Many React Native libraries (react-native-device-info and others) emit events
through a custom `sendEvent(reactContext, "eventName", body)` helper that
internally calls `…emit(eventName, …)` with the event name as a VARIABLE — so
RN_JVM_EMIT_RE (which matches `.emit("literal", …)`) saw only the variable and
produced no event edge. The literal event name lives in the wrapper CALL.
Add RN_NATIVE_SENDEVENT_RE — captures the first string literal inside a
`sendEvent(...)` call (multi-line tolerant; `[^;{}]*?` keeps it to one statement
so the wrapper DEFINITION, whose `(` is followed by `… ) {`, never matches).
Wired for Java/Kotlin and Swift. A native method firing `sendEvent(…, "X", …)`
now links to the JS `addListener('X', handler)`.
Measured on react-native-device-info: the Java-side battery/power/headphone
events now connect to their JS hooks (usePowerState, useBatteryLevel, …) —
previously only the ObjC `sendEventWithName:` side did. Full suite green.
(Expo's constant-based events — `sendEvent(batteryLevelDidChange, …)` where the
name is a `const` — still need constant resolution; tracked as a follow-up.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
d06a5ec890 |
feat(impact): extract RCT_EXPORT_METHOD / RCT_REMAP_METHOD as ObjC method nodes
`RCT_EXPORT_METHOD(foo:(...))` — the most common way to declare an iOS native module method — parses as a macro-expression (an ERROR node), NOT a `method_definition`, so the ObjC extractor never made a node for it. The iOS half of a native module was invisible: a JS call to it couldn't resolve, and the cross-platform pairing had nothing to pair. Only regular `- (void)foo` ObjC methods were nodes. The React Native bridge resolver now implements the `extract()` framework hook for `.m`/`.mm` files: it reuses the existing `RCT_EXPORT_MODULE` / `RCT_EXPORT_METHOD` / `RCT_REMAP_METHOD` source parser to emit a method node per exported method, named by its JS-visible name (the selector's first keyword, or the explicit `RCT_REMAP_METHOD` JS name) so it lines up with the Android `@ReactMethod` method and the JS callsite. Node id is prefixed `rn-export:`. Measured on react-native-device-info: JS→ObjC `calls` went 7 → 37, and Java↔ObjC cross-platform pairs 22 → 29 (ObjC↔C++ 8 → 24) — the iOS bridge surface is now visible and connected. Full suite green; node count grows only by the newly- visible iOS methods. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4a64ca56cd |
feat(impact): classic RN NativeModules cross-platform pairing (Android @ReactMethod ↔ iOS impl)
The classic React Native bridge links a JS callsite to a native module method, but only ONE platform's impl — the other (the iOS method when JS resolved to Android, or vice versa) looked like nothing called it and editing it showed no blast radius. Same gap fixed for Expo, now for the classic bridge. `rnCrossPlatformEdges`: a native method (java/kotlin/objc/cpp) that HAS a JS-side `calls` edge is a confirmed bridge method; link it to the same-named native method in another language (the other platform's impl), both directions. Names are normalized to the first selector keyword (`getFreeDiskStorage:` → `getFreeDiskStorage`) so an iOS selector lines up with the bare Android name. RN runtime-infrastructure methods (addListener / getConstants / getName / … — present on every module) are excluded so they don't cross-link unrelated modules in a multi-module repo. Measured on react-native-device-info: 152 cross-platform pairs (Java↔ObjC↔C++); the classic JS→Java/ObjC bridge itself is unchanged. Heuristic edges (provenance `heuristic`, `synthesizedBy:'rn-cross-platform'`). Full suite green; node count stable. (Note: iOS methods written with the `RCT_EXPORT_METHOD` macro parse as a macro, not a method node, so only regular ObjC method impls pair today — macro extraction is a separate follow-up.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
dbc4862940 |
feat(impact): React Native / Expo cross-language bridges — generic Expo Functions, cross-platform pairing, type-ref precision
Validated the cross-language bridge graph on real RN/Expo repos (expo-battery,
react-native-device-info) and fixed three gaps:
- Generic-typed Expo Functions: Android Expo Modules write `AsyncFunction<Float>
("getBatteryLevelAsync")`, but the framework extractor's regex allowed no
generic type parameter between the keyword and `(`, so EVERY Android Expo
method was silently dropped — a JS call resolved only to the iOS Swift impl.
The regex now accepts an optional `<…>`.
- Cross-platform pairing: an Expo Module exposes the same JS method from BOTH a
Swift (iOS) and a Kotlin (Android) impl, but a JS callsite name-resolves to
only one. New `expoCrossPlatformEdges` links the two platform impls of the same
`<module>.<method>` (both directions), so a JS call reaching one reaches the
other and editing either surfaces the JS caller.
- Cross-language type-reference precision: a `Type.member` static read in native
code (`BatteryManager.EXTRA_LEVEL`, the Android system class) name-matched to a
coincidentally same-named TS class. `references` (type-usage) edges are now
gated to the same language family (JVM java/kotlin/scala, Apple swift/objc, web
ts/js, C c/cpp) at the name-match + import strategies — framework resolvers are
NOT gated, so deliberate config→code bridges (Drupal routing.yml→PHP, Spring
@Value→YAML) and JS↔native `calls` bridges are unaffected.
The classic RN NativeModules bridge (`@ReactMethod`/`RCT_EXPORT_METHOD`) already
worked and is unchanged (118 JS→Java + JS→ObjC `calls` on device-info). Full
suite green; node count stable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
33ce43194c |
feat(impact): Objective-C — single-arg selectors, class-message receivers, #import, class-method resolution
Objective-C was the worst-covered README language (~33-50%). Four gaps: - Single-argument selectors: `[cache storeImage:key]` (the most common call form) was named `storeImage` — without the colon — at the call site, so it never matched its `storeImage:` method. A single keyword now gets its colon when the message has an argument (distinguished from the unary `[c reset]`). - Class-message receiver: `[SDImageCache sharedCache]` / `[[Foo alloc] init]` resolved the method but never referenced the CLASS — whose @interface lives in the header. Emit a `references` edge to a capitalized message receiver, so a class used via class messages (alloc/init, singletons, factories) and its header record a dependent. - `#import "Foo.h"`: the import created a node but never resolved cross-file (a bare `Foo.h` has no slash). matchByFilePath now resolves a bare filename with a short extension to the header by basename — so every imported header is covered. - Class-method calls: `SDImageCache.storeImage:` didn't resolve because the method-call regex rejected the trailing-colon selector; broadened to allow colon keywords (a no-op for other languages). Measured (fair cross-file dependent coverage): AFNetworking 50.0% → 90.0%; SDWebImage (Core) 33.8% → 91.6%. Node count stable. Residual = public-API category methods called only by app code (a frontier). Full suite green; the shared matchByFilePath / method-regex changes are no-ops for other languages (only bare-filename refs and colon-bearing selectors are newly handled). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
857baf76b8 |
feat(impact): cross-language static-member / value-read pass — link Enum.value, Type.CONST, Foo::BAR
The recurring residual across Dart, Java, C#, Swift, Kotlin, Scala, PHP: a type used only through a static member or enum VALUE recorded no edge. The body walker handled CALLS (`Type.method()`) and `new`, but reading an enum value (`MediaKind.video`), a static field (`Colors.red`, `JsonScope.EMPTY_DOCUMENT`), or a class constant (`Foo::BAR`) produced nothing — so an enum/constants type used purely by value looked like nothing depended on it. `extractStaticMemberRef` (run in the function-body walk) emits a `references` edge to the capitalized receiver of a member-access VALUE read. Handles each language's member-access node (`field_access` Java, `member_access_expression` C#, `navigation_expression` Kotlin/Swift, `field_expression` Scala, `class_constant_access_expression`/`scoped_property_access_expression` PHP, `qualified_identifier` C++, and Dart's `identifier` + sibling `selector`). Skipped when the access is a call's callee (already linked) and gated to languages where types are Capitalized by convention — so a lowercase `obj.field` / `pkg.func` never triggers. TS/JS/Python are deliberately excluded: already high-coverage and they drive the retrieval-performance benchmark. Measured: flutter/packages (Dart) 92.4% → 93.2% (the enum-value files); additive across the other languages. Node count stable (edges only). Verified per-language (Java/Kotlin/PHP/Swift/Dart/Scala emit the ref to the right type). Full suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
94879546bd |
feat(impact): Dart mixins (with) + method type references
Two Dart gaps, both gated to `language === 'dart'` in tree-sitter.ts: - Mixins: `class C extends Base with M1, M2` packs the `with` mixins into a `mixins` child of the `superclass` node. The generic inheritance path read `superclass`'s first child as the base and dropped the mixins entirely — and `class C with M` (no extends) even had its `mixins` node misread as the superclass. Mixins are Dart's core composition mechanism (Flutter is built on them). Emit `extends` for the base type and `implements` for each mixin. - Method type references: Dart was in the type-annotation languages but produced ZERO `references` edges, because a `method_signature` wraps the real `function_signature` (where params + return live) and the return type is a bare `type_identifier` child, not a `type` field — so the generic getChildByField walk found nothing. Walk the inner signature; a class or enum used only as a parameter/return type now records a dependency. Measured (fair cross-file dependent coverage, symbol-bearing source files): flutter/packages 88.8% → 92.4%, dio 86.4% → 87.9%. Node count stable (edges only). Residual is genuine frontiers: see-through export barrels (`lib/<pkg>.dart`, `types.dart`), platform-conditional files (`_io.dart`/`_web.dart`/`_stub.dart`), and enum-value access (`MediaDeviceKind.videoInput` — the cross-language static-member/value-read frontier, deferred). Full suite green; Dart-only change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ec8fe3f1f4 |
feat(impact): C++ free-function name extraction — stop naming functions after a param/return type
The C++ qualified-name resolver BFS-searched the whole declarator for a `qualified_identifier`, INCLUDING the parameter list and the trailing return type. So a plain free function `std::string TableFileName(const std::string& dbname)` was named `string` (from the `std::string` parameter), and an `auto to_string(...) -> std::string` was named `string` (from the trailing return) — calls never resolved, `codegraph_node` couldn't find them, and the defining file looked like nothing depended on it. This is how most free functions in a namespaced C++ library look, so it was a broad miss. Skip `parameter_list` and `trailing_return_type` when locating the function's own qualified name; a non-qualified name correctly falls back to the default declarator-name extraction (`TableFileName`, not `string`). Measured: leveldb 91.7% → 94.8% fair cross-file dependent coverage; fmt went from 32 functions mis-named after a bare type to 1; redis (C, unaffected — uses no qualified-name resolver) held at 92.2%, its residual being genuine frontiers (generated command tables, macro-reached code, function-pointer command dispatch, example modules). Node count stable (functions were already nodes, just mis-named). Full suite green; qualified methods (`Foo::bar`) unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5bccab6afe |
feat(impact): Ruby require/require_relative resolution — link to the required file
Ruby's other half of cross-file dependency (besides mixins) is `require` — but require imports created an import node that never resolved to the required file, so a file pulled in only by `require` (config-loaded components like Sidekiq's BasicFetch/systemd, or any gem that doesn't rely on autoloading) recorded no dependent. Mixins alone left Sidekiq at 76.8%. Emit an `imports` ref for `require "lib/foo"` (load-path — suffix-matched against the file path, the same mechanism template imports use) and `require_relative "../foo"` (resolved against the requiring file's directory). Bare gem/stdlib requires (`require "json"`, no slash) are skipped as external. The ref resolves to the required FILE node, so the requiring file → required file dependency is recorded even when the required file's class is reached only dynamically. Measured (fair cross-file dependent coverage, symbol-bearing source files): sidekiq 76.8% → 100.0%, rails/activerecord 93.0% → 96.8% (Rails autoloads but still has explicit requires for sub-components). Node count stable (the import nodes already existed; this only resolves them). Full suite green; Ruby-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
44fb978597 |
feat(impact): Ruby mixin coverage — link include/extend/prepend to the module
Ruby's primary composition mechanism is the mixin (`include`/`extend`/`prepend` a module), but these parse as a bare `call` to a method named `include` with the module as a constant argument, so they created no edge — a concern module mixed into a class recorded no dependent unless its methods happened to be called in-repo. On a concern-heavy codebase (Rails) that was a large share of modules, and ActiveRecord is built almost entirely on `include`d concerns. Detect `include`/`extend`/`prepend` (bare, no receiver) in the Ruby extractor's visitNode hook and emit an `implements` edge from the enclosing class/module to each module argument (`constant` or `scope_resolution`), so editing a concern surfaces every class that mixes it in. Skips `extend self` / dynamic args, and `arr.include?(x)` (has a receiver) is unaffected. Measured (fair cross-file dependent coverage, symbol-bearing source files): rails/activerecord 84.8% → 93.0%, sidekiq 71.0% → 76.8%. Node count stable (edges only). Residual is genuine frontiers: classes instantiated by class-string `constantize` (associations, Arel nodes), generators, version files, and config/`require`-loaded components. Full suite green; Ruby-only change. (Follow-up: resolve `require`/`require_relative` to files — helps explicit-require gems like sidekiq; Rails autoloads so it benefits less.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
acfb4445a1 |
feat(impact): PHP namespace capture + use-import resolution + type-hint refs
PHP ignored namespaces entirely: every class's qualified name was its bare simple name, so a framework's many same-named classes (Laravel has 7+ `Factory` interfaces, several `Dispatcher`s, across namespaces) collapsed into one arbitrary match, and `use` imports created nodes that never resolved to a definition. An interface imported and constructor-injected (the DI pattern) showed zero dependents. - Capture `namespace Foo\Bar;` as a file-level package (like a Java/Kotlin package) so each class is scoped to `Foo\Bar::Class` — same-named classes are now distinguishable. - Resolve `use Foo\Bar\Baz;` (single and grouped) to the namespace-qualified definition via an `imports` ref the resolver matches by exact qualified name — so an imported-but-DI-injected contract records a precise cross-file dependency. - Add `php` to the type-annotation languages with a PHP-aware walker: parameter / property / return type-hints are `named_type`/`union_type`/… wrapping `name`/`qualified_name` (never `type_identifier`), and `variable_name` ($x) param names are never mis-emitted. Measured (fair cross-file dependent coverage, symbol-bearing source files): guzzle 95.2% → 100.0%, laravel 80.5% → 94.9%. Node count grows only by the new namespace nodes (one per file, a real symbol — like JVM package nodes), not duplication; edges added precisely. laravel residual is genuine frontiers (service providers / controllers / middleware registered by class-string, console view components dispatched by magic `__call`, mixin traits for app consumption). Full suite green; non-PHP languages unaffected (changes gated to `language === 'php'`). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b5489d916c |
feat(impact): Scala type-graph coverage — parameterized extends, type refs, implicit params
Scala extraction produced symbol nodes but almost no edges for typeclass-style
code: every supertype/type/implicit relationship was dropped, so a trait used as
a type, extended with type parameters, or required as an implicit looked like
nothing depended on it — on cats/algebra that was most of the graph.
- extends/with: `extends A[X] with B with C` packs all supertypes into one
`extends_clause`; the generic path took only namedChild(0) and kept the full
text (`A[X]`) so parameterized supertypes (every typeclass) never matched.
Iterate every supertype and unwrap `generic_type` → base type name via a new
shared `scalaBaseTypeName` helper.
- type references: add `scala` to the type-annotation languages so method
parameter/return types link; walk EVERY curried parameter list (the trailing
`(implicit M: TC[A])` list is where instances are passed); walk
`type_parameters` for context bounds (`def f[A: Monoid]`); and emit refs for
`val`/`var` type annotations from the Scala extractor. Capitalized Scala
primitives are skipped to avoid resolution noise.
- instantiation: `new T[...] { ... }` (`instance_expression`) now emits an
`instantiates` edge to the base type.
Measured (fair cross-file dependent coverage, symbol-bearing source files):
cats 48.9% → 89.2% (scalafix tooling + JMH benches excluded; 82.1% raw),
gatling 76.3% → 91.2%. Node count stable (edges added, not nodes); residual is
genuine frontiers (cross-build scala-2.12/2.13+/3 variants, laws test-support,
wildcard-import barrels, infix-type evidence). Full suite green; changes gated
to Scala so no cross-language regression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
d8a2e91b2f |
feat(impact): Kotlin Multiplatform expect/actual linking — bridge common decls to platform impls
A KMP `common` source set declares `expect fun foo()` / `expect class Bar`; each platform source set (jvm, native, js, wasm) provides an `actual` implementation with the identical fully-qualified name in a different file. Callers in common code resolve to the `expect` declaration, so every platform `actual` ended up with zero dependents — invisible to impact/`affected` and showing an empty blast radius even though editing it can break every caller. - Capture the `expect`/`actual` platform modifiers at extraction via a new generic `extractModifiers` hook (Kotlin reads `modifiers > platform_modifier`), persisted onto the node's decorators list. - Synthesize a heuristic `calls` edge from the common declaration to each platform `actual` (mirroring the interface-impl bridge: abstract → concrete), matching by qualified name + the `actual` marker. The declaration side is the same-FQN, non-`actual` node, which also gates out plain cross-file overloads. - Widen kind matching so an `expect class` fulfilled by an `actual typealias` (a core KMP idiom: CancellationException, ReentrantLock, …) links too. Measured (fair cross-file dependent coverage, symbol-bearing source files): kotlinx.coroutines 76.8% → 93.5%; OkHttp holds at 96.2% (barely uses KMP). Node count stable (edges added, not nodes); residual is genuine frontiers (expect-decl sides with no in-repo callers, ServiceLoader/agent SPI, test infra). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d111f26573 |
feat(impact): Swift property wrappers & attributes — link @Argument/@Published/etc.
Swift is otherwise healthy (init calls -> instantiates, protocol conformances -> implements). The systematic gap was property wrappers / attributes: - A Swift `@X` parses as `property_declaration -> modifiers -> attribute -> user_type`. `extractDecoratorsFor` didn't recognize the `attribute` node type (only decorator/annotation/marker_annotation) nor extract the name from its `user_type` child, so every Swift attribute usage was dropped. - Swift stored properties inside a type aren't extracted as their own nodes, so the decorator helper never ran on them. Added a dispatcher branch that runs extractDecoratorsFor + type-annotation extraction on Swift property declarations inside a type. Now `@Argument`/`@Option`/`@Published`/`@State`/custom `@propertyWrapper` usages (and `@objc`/`@MainActor` on types/methods) link to the wrapper/attribute type. Alamofire 93%->95.3%, swift-argument-parser 84.6%->96.2%. Reuses the same extractDecoratorsFor path as the Java annotation fix, unifying annotation/attribute/property-wrapper handling across Java/C#/Kotlin/Swift. New test + CHANGELOG; full suite green (1151). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
badb12481f |
feat(impact): Java annotation coverage — index @interface defs, link @Foo usages
Java was otherwise healthy (FQN imports, new/extends/implements all resolve); the systematic gap was annotations, and both halves were missing: - Annotation USAGES (`@Foo` on a class/method/field) live inside the declaration's `modifiers` node, which `extractDecoratorsFor` never descended into — so every annotation usage was dropped. Now it descends into `modifiers` (also fixes C#/Kotlin annotations, which use the same shape). - Annotation DEFINITIONS (`@interface Foo` = `annotation_type_declaration`) weren't in Java's interfaceTypes, so they weren't nodes and the usages had nothing to resolve to. Now `@SerializedName` / `@GetMapping` / `@Entity` style annotations link to their definition (a `decorates` edge), so the annotation file shows its real users and the annotated class depends on it. gson 78%->85%, retrofit 80.5%. New test + CHANGELOG; full suite green (1150). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2ac7df58f7 |
feat(impact): Rust module-path resolution for qualified use/path references
Resolve a Rust qualified path `A::B::C` by mapping the module prefix `A::B` to a file (crate/self/super-anchored; each segment -> `<dir>/seg.rs` or `<dir>/seg/mod.rs`) and finding the leaf `C` there, instead of name-matching the leaf. `emitRustUseBindingRefs` now emits the full path so this can fire. Fixes common-name re-export collisions: `pub use self::read::read` now links the correct `fs/read.rs` rather than a stray same-named `read` (ripgrep 81%->87%). Returns null for non-module prefixes (`Widget::new`, enum variants) so those fall through to method/name resolution. New collision test; full suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b538aee853 |
feat(impact): Rust cross-module coverage — struct literals, trait dispatch, use-binding linking
Continues the cross-language blast-radius work for Rust (ripgrep 63%->81%,
tokio src 70%->83% file-dependent coverage).
- Struct literals: add `struct_expression` to the instantiation kinds so
`Widget { n: 1 }` / `m::Widget { .. }` record an `instantiates` edge.
- Trait dispatch (#584 for Rust): extract trait method declarations
(`function_signature_item`) as method nodes so a trait's method set is
first-class, and enable Rust in the interface-override bridge — a struct's
explicit `impl Trait for T` now links `Trait.method -> impl.method`, so a type
reached only through `&dyn Trait` gets dependents.
- `use` / `pub use` binding linking: emit the leaf binding of each use path so a
`pub use self::sub::Item` re-export hub depends on the module it re-exports
(the largest lever) and items imported but used in non-call positions link.
Known remaining Rust frontier: common-name re-export collisions
(`pub use self::read::read`) need Rust module-path resolution; plus macros and
external-trait-only impls. New tests + CHANGELOG.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
16b5633749 |
feat(impact): fix affected/blast-radius and expand cross-file dependency coverage
The file-level dependency queries followed only `imports` edges, which are
same-file in this engine, so `codegraph affected` and getFileDependents returned
0 dependents for every file. Project the symbol-level edge graph instead, then
close the cross-file edge gaps that left real dependencies unlinked per language.
Queries:
- Add getDependentFilePaths / getDependencyFilePaths (one indexed JOIN over all
cross-file edge kinds except `contains`); getFileDependents/getFileDependencies
and circular-dependency detection now use them.
Extraction & resolution, by language:
- TypeScript/JS: extract in-body local type annotations; link every named,
default, re-export (`export {X} from`), and namespace import to its definition
(incl. tsconfig `@/` aliases), with a namespace-import file backstop.
- Python: link `from module import X` bindings (value imports, aliases, __init__
re-export barrels); resolve `from . import sub` + `sub.func()` module-member
access; fix Python dotted-relative import path resolution.
- Go: extract cross-package struct composite literals (`pkg.Type{...}`, incl.
package-level var registries) and `(*T)(x)` type conversions; implicit
interface satisfaction — extract interface methods, synthesize `implements`
edges via method-set matching, bridge interface dispatch to impls (#584).
- C#: index `record` / `record struct` declarations as types (#237).
Measured file-dependent coverage on real repos: this repo (TS) 62.5%->95.8%;
requests (Py) 54%->100%; gin (Go) 62.7%->96.6%; MediatR (C#) 81.5%->85.2%.
Node counts stay stable (edges added, not nodes, aside from real interface/record
symbols). Adds tests and CHANGELOG entries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
629d8472b1 |
fix(extraction): index Vue <template> component usages (#629 follow-up) (#659)
Vue's extractor parsed only the <script> block, so a component used solely in another component's <template> (`<MyButton />`) produced no reference — and thus showed a false 0 callers, even after the barrel-resolution fix in PR #657. This is the Vue analogue of Svelte's extractTemplateComponents. extractTemplateComponents() now scans the template (everything outside the <script>/<style> blocks, which also handles nested <template> tags for v-if/slots) for component tags: - PascalCase tags (`<MyButton/>`) — captured as-is. - kebab-case tags (`<my-button/>`) — converted to PascalCase so they match the imported component's name. Safe: an unmatched name creates no edge during resolution, so native custom elements just don't resolve. - Native HTML elements (lowercase, no hyphen) and Vue built-ins (Transition, KeepAlive, …) are skipped. Adds no nodes — only `references` — so node counts stay stable. With this plus #657, a Vue component re-exported through a barrel and used only in a template now resolves end-to-end (callers/impact/callees). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bdfd55e69c |
fix(resolution): resolve Svelte/Vue component barrels & workspace imports (#629) (#657)
Component barrels (`export { default as X } from './X.svelte'`) and
monorepo workspace imports (`@scope/ui/widgets`) left the consumer↔component
edge uncreated, so live components showed a false `0 callers` — the canonical
dead-code signal — risking deletion of live code.
The Svelte default-barrel case broke at FOUR layers, each of which alone left
it unresolved:
- findExportedSymbol matched only function/class for a default export, never
`component` (Svelte/Vue SFCs are kind 'component').
- extractImportMappings had no svelte/vue branch, so SFC consumers produced
zero import mappings and resolveViaImport never ran.
- EXTENSION_RESOLUTION had no svelte/vue entry, so relative imports from an
SFC (`./lib` -> `/index.ts`) resolved to nothing.
- getReExports parsed the barrel in the CONSUMER's threaded language, so a
.svelte consumer made extractReExports bail on a .ts index barrel.
Workspace package-subpath barrels get a new workspace-packages module
(mirrors go-module/path-aliases): reads package.json `workspaces`
(npm/yarn/bun) + pnpm-workspace.yaml, maps member name->dir, resolves
`@scope/ui/widgets` -> `packages/ui/widgets`. Gated behind the workspaces
field so single-package repos are unaffected.
Bare `./`/`.` directory imports already resolved; covered with a regression
test. Verified both directions (callers/impact AND callees) for Svelte; Vue
script-level imports also resolve. 4 new tests; full suite green (1126).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
7b62356f53 |
feat(cli): add version, indexPath, lastIndexed to status --json (#329)
Adds `version`, `indexPath`, and an ISO `lastIndexed` to `codegraph status --json`, plus a `CodeGraph.getLastIndexedAt()` library method. `agentCount` dropped (no clear consumer). Reworked from contributor PRs #333 and #480. Co-Authored-By: Javier Gómez <199902626+12122J@users.noreply.github.com> Co-Authored-By: Ran <8403607+eddieran@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ddb1a8f72d |
fix: issue-triage quick wins (extraction, MCP probes, gitignore, CJK, impact) (#654)
Batch of small, localized fixes from an open-issue triage: - .codegraph/.gitignore now ignores everything but itself, so the database, daemon.pid, sockets, and logs stop showing up in git status (#492, #484) - MCP server answers resources/list and prompts/list with empty lists instead of -32601, clearing scary log lines in opencode/Codex (#621) - index SAP HANA .xsjs/.xsjslib as JavaScript (#556) and TS .mts/.cts (#366) - visit anonymous AMD/CommonJS/IIFE wrapper bodies so their inner functions and calls are indexed instead of coming up empty (#528) - batch the changed-file lookup so a huge first sync no longer hits "too many SQL variables" (#540) - list files with `git ls-files -z` so non-ASCII/CJK paths survive core.quotepath and are no longer silently skipped (#541) - attach Go methods on generic receivers (*T[P]) to their type (#583, RC1) - impact no longer climbs the structural `contains` edge, so a leaf symbol stops dragging in its sibling methods (#536) - README: explicit `codegraph install` step, run in a new shell (#631) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2a22f9f55a |
fix(resolution): stream node-kind scans in synthesis to fix OOM on dense files (#610) (#653)
The callback/observer synthesizers loaded every function and method node into
memory at once (getNodesByKind('function'/'method')) before scanning them down
to a tiny matched subset. On a symbol-dense project that array is gigabytes, so
indexing spiked the JS heap and aborted with "JavaScript heap out of memory".
Add QueryBuilder.iterateNodesByKind (a lazy node:sqlite cursor) and stream the
synthesizer scans instead of materializing them. Parsing and reference
resolution were already bounded; only the synthesis enumeration wasn't.
Measured on 80 files x 14k functions (~1.1M nodes): peak RSS 3717 MB -> 1318 MB,
no OOM. Full suite green; synthesized edges unchanged.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
c9559d9991 |
fix(watcher): bound fd/watch cost with a native fs.watch hybrid (#644, #496, #555, #628, #579) (#650)
chokidar v4 holds one OS file descriptor per watched file on macOS (libuv's kqueue backend registers an fd per vnode; fsevents is installed but v4 no longer uses it). On a large project the `serve --mcp` daemon accumulated tens of thousands of open REG descriptors and exhausted kern.maxfiles — crashing unrelated processes system-wide with ENFILE. #276 only trimmed the count by ignoring directories; the source tree still cost one fd per file. Replace chokidar with a pure-JS native fs.watch hybrid, keeping codegraph's zero-native-addon "any OS builds any bundle" invariant: - macOS / Windows: a single recursive fs.watch (one FSEvents stream / ReadDirectoryChangesW handle) -> O(1) descriptors regardless of repo size. - Linux: one inotify watch per directory (O(dirs), dynamic add for new dirs, capped via CODEGRAPH_MAX_DIR_WATCHES) instead of per-file watches. Validated empirically: macOS 0 extra fds at 6k and 12k files; Linux 31 inotify watches at 6k files (per-file would be 6k); Windows recursive catches nested and new-directory edits. Full test suite green. Tests drive the watcher through an inertForTests seam (no OS watcher) for determinism under parallel vitest, with one real-fs end-to-end test exercising the genuine native path. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
434be1da58 | Merge remote-tracking branch 'origin/main' | ||
|
|
0bc13231f7 |
docs(claude): add handoff notes for explore-overhaul arc and related sessions
Captures three development session checkpoints: per-symbol adaptive sizing (PR #569), trace relevance + cold-start fix (PR #580), and the explore-overhaul arc (explore as sole primary + Zustand store coverage + budget/render tuning). Documents root causes found, gotchas (stale-daemon foot-gun, Mac sleep corrupting benchmarks), validation methodology, and open threads for each session. |
||
|
|
92797535e5 |
docs(changelog): promote [Unreleased] into [0.9.9]
[skip ci] Auto-generated by Release workflow.v0.9.9 |
||
|
|
0cc7ba902f |
chore(release): bump version to 0.9.9
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
68eaf0dbd8 |
feat(mcp): codegraph_explore as the sole primary tool + store coverage + overload disambiguation (#647)
## Summary
Completes the explore-overhaul arc: `codegraph_explore` becomes the single primary tool an agent reaches for, and its coverage + output shape are tuned so flow/architecture questions resolve with near-zero Read/Grep.
### What changed
- **explore is the sole primary tool** — removed `codegraph_context` (the fuzzy-input Read-trigger) and `codegraph_trace` (under-picked by agents); explore already surfaces the call flow among the symbols you name. A plain natural-language question now works as the query.
- **Store/handler coverage** — functions defined inside object literals (Zustand `create((set, get) => ({ … }))`, Redux/Pinia/MobX, exported handler/route maps) are indexed as real symbols, including calls through `useStore.getState().fn()` and destructured `const { fn } = useStore.getState()`. A general AST rule, not a per-lib hack.
- **Overload disambiguation** — explore leads with the *right* definition when a method name is overloaded across types (a PascalCase type token in the query biases to that type's own def); `codegraph_node` returns *every* overload's body in one call, with an optional `file`/`line` selector to pin one.
- **Method-atomic render** — explore never returns half a method; at the size budget it drops whole methods/files (and lists what it dropped) instead of truncating a body mid-method.
- **Native-read-shaped output** — per-call output is capped to ~24K with a 25K hard ceiling and concentrated into ~150–250-line flow windows, mirroring how the agent natively reads; repo size scales the *call* budget, not the per-call size (a larger response just gets externalized to a file the host Reads back).
- **Blast radius** folded into explore (dependents + covering tests, locations only).
### Benchmark (refreshed on this build)
Re-validated the 7-repo A/B on 2026-06-02 (Opus 4.8, effort=high, median of 4). WITH arm re-measured on this build, WITHOUT reused:
**~16% cheaper · 47% fewer tokens · 22% faster · 58% fewer tool calls** — 0 file reads on 6 of 7 repos (Gin ~1).
The arc trades larger, cache-heavy explore responses for guaranteed near-zero reads, so cost/token margins soften vs the prior build (Excalidraw and Tokio land at cost break-even) while time and tool-calls stay clear wins everywhere — consistent with the project's stated optimization target (latency + tool-calls, not token cost).
### Validation
- Full suite green: **1112 passed, 2 skipped**.
- 28/28 plain WITH runs across the 7 README repos completed clean; reads median 0 on 6/7.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||
|
|
8629f7ab4c |
docs(changelog): promote [Unreleased] into [0.9.8]
[skip ci] Auto-generated by Release workflow.v0.9.8 |
||
|
|
081a6fcaca |
chore(release): bump version to 0.9.8 (#604)
Cuts the 0.9.8 release, which carries the restored embedded/programmatic SDK API (#354) along with the rest of the [Unreleased] changelog. The Release workflow promotes [Unreleased] -> [0.9.8] and appends the link reference; only the version fields are bumped here. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
89d4d37a29 |
feat(npm): restore programmatic/embedded SDK API (#354) (#603)
The 0.9.x thin-installer turned @colbymchenry/codegraph into a bin-only
shim: require("@colbymchenry/codegraph") threw MODULE_NOT_FOUND and no
types shipped, breaking embedded library consumers (e.g. Electron apps)
upgrading from 0.8.0.
Restore programmatic use without re-bloating the thin shim or duplicating
the ~49 MB of grammars the per-platform bundle already carries:
- main -> npm-sdk.js re-exports the installed per-platform bundle's compiled
library (lib/dist/index.js) at runtime, reusing that bundle's own deps; it
falls back to a self-healed cache bundle, else throws an actionable error.
- types -> ship the .d.ts tree only (~590 KB) in the main package, built from
the same release so it can never skew from the runtime it re-exports.
- exports map resolves the `types` condition (nodenext) and the default entry.
- DatabaseConnection + QueryBuilder are now top-level exports, so embedded
callers get the building blocks from the package entry instead of deep
dist/ imports (which the shim no longer ships).
The CLI/MCP `bin` keeps execing the bundled Node; only library consumers run
on their own runtime, which must be Node 22.5+ for the built-in node:sqlite.
Validated end-to-end: built a real darwin-arm64 bundle, packed the npm
packages, installed them into a throwaway consumer, and confirmed require()
plus a full init/indexAll/searchNodes round-trip and the low-level
DatabaseConnection/QueryBuilder path all work on the host Node; types resolve
under both nodenext and classic node resolution; and the CLI shim still
launches. New hermetic tests cover npm-sdk resolution, cache fallback, and the
missing-bundle error.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
3a1ddf41cd |
feat(mcp): trace relevance + closure-collection + god-file rendering + cold-start handshake (#580)
Trace endpoint relevance (overloaded names resolve to the real implementation instead of an empty protocol/delegate stub), Swift closure-collection synthesizer, multi-phase god-file explore rendering, and serve --mcp cold-start handshake sped ~811ms→~90ms (proxy answers initialize/tools-list locally). Full suite green (1090 pass). |
||
|
|
b026e64b41 |
feat(mcp): per-symbol adaptive codegraph_explore sizing (#569)
Sizes codegraph_explore to the answer, not the file count: shows the mechanism + the exact methods you named in full (even buried in a large file) while collapsing redundant interchangeable implementations to signatures. Adds uniqueness-aware spare, per-symbol focused rendering of family files, all-tier test-file exclusion, and named-method cluster survival in non-sibling god-files. Validated A/B (Opus 4.8, 7-repo sweep): avg 25%% cheaper / 57%% fewer tokens / 23%% faster / 62%% fewer tool calls. Django 9->23%% cheaper (0 reads), OkHttp 4->11%% cheaper; gains across small/medium/large, inert repos unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f1b14f021b |
feat(mcp): adaptive codegraph_explore sizing — skeletonize redundant polymorphic siblings (#564)
codegraph_explore now skeletonizes off-spine, redundant members of a polymorphic family (OkHttp's interceptor chain, Django's SQLCompiler family) to signatures instead of shipping every full body, while keeping the dispatch mechanism, the orchestrator/base, and any method the agent named in full. Sizes the response to the answer rather than the budget cap, so interface-heavy flows stop costing more than plain grep/read. Default on; CODEGRAPH_ADAPTIVE_EXPLORE=0 disables. Gate: off-spine + >=3-impl sibling + not-spared, where spared = the agent named a callable in the file UNLESS the file defines the family's supertype (a huge base+subclasses file is Read-anyway, so skeletonizing frees explore budget). Validated headless A/B (Opus 4.8): both former README cost outliers flipped — OkHttp and Django went from costlier-than-native to cheaper; full 7-repo average 22%% cheaper / 47%% fewer tokens / 20%% faster / 50%% fewer tool calls, every repo cost-positive, inert repos unchanged. 7-case regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f58de8a391 |
feat(resolution): gin middleware-chain synthesizer + Opus 4.8 benchmark refresh (#547)
* fix(agent-eval): detect idle by content-stability, not spinner absence Opus 4.8's extended-thinking TUI shows no spinner / interrupt hint / timer while it streams its final answer — those appear only during the thinking and tool-use phases. The old detector treated ~5s of not-busy + prompt-present as done, so it killed interactive runs mid-answer, silently truncating both arms of the tmux A/B (low tool counts; the final assistant message left as a mid-investigation preamble). Now a run is done only when the captured pane stops changing for ~8s; while streaming, the pane changes every poll so stability never accrues. BUSY_RE stays as the immediate busy-reset for the thinking/tool/live-timer phase. Content-stability is model-agnostic — it survives future spinner re-wordings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): refresh VS Code benchmark on v0.9.7 + Opus 4.8 Re-ran the VS Code A/B (headless median-of-4) on the current build and model. Cost savings held at 26% ($0.66->$0.89), but token/time/tool-call savings narrowed (78->63%, 52->20%, 85->69%) because Opus 4.8's without-CodeGraph arm explores far more efficiently than 4.7's did (16 tool calls vs 55, no Explore-subagent fan-out); the WITH arm is unchanged at 5 calls / 0 reads. Recomputed the average row and noted that the VS Code row is now a different model/version epoch than the other six. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(resolution): synthesize gin middleware-chain edges (Next -> registered handlers) Gin runs its entire handler chain through one dynamic line in (*Context).Next -- c.handlers[c.index](c), a slice-index dispatch tree-sitter can't resolve. So callees(Next) dead-ended at the len() helper and the flow ServeHTTP -> handleHTTPRequest -> Next stopped at the exact symbol a 'how does the middleware chain work' question is about, sending the agent to re-query and Read/grep (a measured gin WITH-arm rabbit-hole: 2/4 headless runs spiraled to ~5min, one mis-firing the opt-in Workflow orchestration tool). Find the chain dispatcher (a Go method invoking a handlers slice by index) and link it -> every HandlerFunc registered via .Use/.GET/.../.Handle, so callees(Next) and trace(ServeHTTP, handler) connect end-to-end. Gated on the dispatcher existing (inert on non-gin Go repos), named handlers only (inline closures skipped), capped; provenance heuristic / synthesizedBy gin-middleware-chain, registeredAt = the registration site. Validated: gin callees(Next) now surfaces Logger/Recovery/ErrorLogger + handlers (node count stable at 2,544; 5 precise edges); agent A/B (headless median-of-4, Opus 4.8) flipped gin from -58% cost / -129% time to +7% cost / +35% tokens / +8% time / 38% tool calls, all 4 WITH runs clean (0 Read/Grep/Bash). 167/167 unit tests pass incl. the new gin-middleware-chain test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): publish uniform Opus 4.8 benchmark + per-repo breakdown accordion Refresh all 7 benchmark rows to the v0.9.7 / Opus 4.8 headless median-of-4 (was a mix of the 4.8 VS Code row + six 4.7 rows). New average 18% cheaper / 51% fewer tokens / 16% faster / 57% fewer tool calls; headline + methodology note updated 4.7->4.8. The gap is smaller than the prior 4.7 numbers because Opus 4.8's native grep/read is more efficient (the without-arm no longer fans out into large Explore-subagent sweeps) -- not a codegraph regression; CodeGraph still cuts tool calls and tokens on all 7 repos, with cost marginal/negative only on django + okhttp. Adds a top-level 'Per-repo breakdown' accordion (per-metric Time/Reads/Grep-Bash/Tool calls/Tokens/Cost, WITH vs WITHOUT, per repo) directly below the condensed summary; methodology/queries/why-wins move to a second accordion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(changelog): note Gin middleware-chain synthesizer under [Unreleased] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7a75c82dd9 |
feat(cli): codegraph init builds the initial index by default (#483) (#546)
`codegraph init` now runs the initial index automatically. The -i/--index flag is kept but is now a no-op, accepted for backward compatibility so existing muscle memory and scripts don't break. Addresses #483, where a user asked why -i wasn't implicit. README and site/ docs are intentionally NOT updated in this commit — they describe the currently-released 0.9.7 behavior (where -i is still required). Update them at the 0.9.8 release so users on 0.9.7 aren't misled. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5fbb9f6787 |
docs(readme): clarify what init -i does vs plain init (#483) (#545)
A user asked what the `-i` flag does and why it isn't the default. Add a short note under the Initialize Projects quickstart: plain `init` only scaffolds the `.codegraph/` dir, `-i`/`--index` also runs the first index, and without it you run `codegraph index` afterwards. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cdbf451440 |
fix(extraction): count all file-level-tracked langs (incl .properties) as indexed (#544)
Completes #357. The no-symbol file-level class is yaml/twig/properties, but the count fix only covered yaml/twig — so a .properties-only project still printed "No files found to index" even though the files were stored. Introduce a single isFileLevelOnlyLanguage predicate (the canonical set behind the tree-sitter no-symbol branch, xml excluded since its MyBatis extractor emits a file node) and use it at both count sites and the extraction dispatch so the list can't drift. Adds .properties regression coverage for indexAll() and indexFiles(). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
839cf63dcb | fix: count file-level tracked yaml and twig as indexed (#357) | ||
|
|
54caceae31 |
docs(claude): codify friendly New Features / Fixes changelog format
Update the 'Writing changelog entries' guidance to match the rewritten CHANGELOG: friendly New Features / Fixes sections (Breaking Changes / Security only when present), one plain sentence per bullet, strip internal paths/symbols/benchmarks, keep #PR refs + contributor thanks. Notes why multi-word headings are safe on the normal release path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2e19234578 |
docs(changelog): rewrite all release notes into friendly New Features / Fixes format
Distill every release's engineer-facing entry into plain-language, user-readable notes (New Features / Fixes, with Breaking Changes / Security surfaced where they apply). Also reconcile the mislabeled [0.7.8] block to [0.7.9] to match the published GitHub release tag and fix its dead link reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f29825c090 |
docs(changelog): promote [Unreleased] into [0.9.7]
[skip ci] Auto-generated by Release workflow.v0.9.7 |
||
|
|
15dbcdbac0 |
chore(release): bump version to 0.9.7
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a9c9e76d8c |
fix(installer): stop duplicating agent instructions; MCP server is the single source of truth (#529) (#538)
The installer wrote a `## CodeGraph` usage block into each agent's
instructions file (CLAUDE.md / AGENTS.md / GEMINI.md / .cursor/rules /
Kiro steering) that duplicated, almost verbatim, the guidance the MCP
server already emits in its `initialize` response — so agents that
surface MCP instructions (Claude Code) read the same playbook twice
every turn.
All 6 instruction-writing targets (claude, cursor, codex, opencode,
gemini, kiro) now stop writing the block. install self-heals by
stripping a block a previous version wrote (uninstall already did), so
the next `codegraph install`/`uninstall` cleans up existing installs;
upgrading the package alone does not (the leftover block is harmless).
server-instructions.ts is now the single source of truth — the two
steers unique to the old template ("trust codegraph, don't re-verify
with grep" and the not-initialized -> `init -i` hint) are ported there.
Removes the now-dead INSTRUCTIONS_TEMPLATE / CLAUDE_MD_TEMPLATE,
claude-md-template.ts, writeClaudeMd / hasClaudeMdSection, and the
Cursor-only wireProjectSurfaces bootstrap. The install log learned a
"Removed" verb. Tests rewritten to the new contract + self-heal
coverage (140/140 installer tests pass).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
cea78ceb1b |
fix(windows): suppress console popup on child_process calls (#498)
On Windows, v0.9.5's detached shared daemon (#411) has no inherited console, so any console-subsystem child it spawns gets a fresh visible console window unless the spawn passes `windowsHide: true`. The fix adds the flag to all ten `spawnSync` / `execFileSync` / `execSync` call sites across extraction, sync, installer, and the WASM-flags relaunch. macOS/Linux ignore the option, so this is a no-op elsewhere. Fixes #485, #510, #530. Co-authored work: - #498 (csw-chen) — full sweep across extraction, sync, installer, and wasm-runtime. **This is the change being merged.** - #505 (yushengruohui) — independently identified and fixed the 7 git execFileSync sites. Superseded by #498's broader sweep; same diagnosis. - #521 (JirA44) — independently identified and fixed the WASM-runtime spawnSync re-exec. Superseded by #498's broader sweep; same diagnosis. Validated on Windows 11 ARM64 (Parallels): a detached parent's 15 git spawns produce 15 visible black flash-windows without the fix and 0 with it. |
||
|
|
71935e37c2 |
feat(mcp): multi-module Go trace-quality + small-repo retrieval tuning (#494)
* feat(go): generated-file down-rank + gRPC stub-impl bridge + trace-failure inlining
Multi-pronged fix to make codegraph competitive on Go multi-module repos
(cosmos-sdk, etcd) where it previously lost or tied. Driven by an 8-question
agent-eval audit across cobra, gin, prometheus, cosmos-sdk, and etcd: the
baseline had codegraph losing ~60% on cost on cosmos-sdk and mixed on etcd
deep cross-module flows, while winning cleanly on the single-module and
non-protobuf-heavy repos.
Diagnostics ruled OUT `go.work` parsing as the gap (prometheus crushes
without it). The actual failure modes were generated-file noise warping
disambiguation, missing gRPC interface→impl bridge in structural-typing Go,
and trace's failure path triggering 3-5 follow-up tool calls instead of
inlining the material the agent needed.
Changes:
- New `src/extraction/generated-detection.ts` — path-pattern classifier
for `.pb.go`, `.pulsar.go`, `_grpc.pb.go`, `_mock.go`, `_mocks.go`,
`mock_*.go`, `.generated.[jt]sx?`, `_pb2(_grpc)?.py`, `.pb.{cc,h}`,
`.g.dart`, `.freezed.dart`. Applied as a stable sort tiebreaker in
`findSymbol`, `findAllSymbols`, `codegraph_search` (MCP + CLI),
`codegraph_explore` file ranking, and context formatter Entry Points /
Related Symbols / Code blocks. Cosmos's `msgServer.Send` now ranks #3
instead of #9 on a `Send` search.
- New `goGrpcStubImplEdges` synthesizer in `callback-synthesizer.ts` —
detects `UnimplementedXxxServer` structs in generated files, identifies
their RPC methods (excluding `mustEmbed*` / `testEmbeddedByValue` gRPC
markers), and emits `calls` edges to the matching methods on any
non-generated struct whose method-name set is a superset. Closes Go's
structural-typing gap that the existing `interfaceOverrideEdges` (Java /
Kotlin only) couldn't bridge. 467 bridge edges on cosmos-sdk; bank's
`UnimplementedMsgServer::Send` points to `x/bank/keeper/msg_server.go`
only, not to `msgClient` siblings or mock files.
- Trace-failure rewrite (`handleTrace`) — when no static path connects
endpoints, instead of telling the agent to call `codegraph_node` (a
3-4-call fan-out), inline both endpoints' bodies (120 lines / 3600 chars
per endpoint), their callers (≤6), and callees (≤8) in one response.
- Trace endpoint-pairing improvements — scores every `from`×`to`
candidate combo by shared directory prefix and tries the best-paired
pair first (the full candidate set, not just FTS top-5). A
less-canonical-path penalty (`enterprise/`, `contrib/`, `examples/`,
`vendor/`, `third_party/`, `deprecated/`, `legacy/`) ensures the
canonical-module pair wins even when a side-experiment shares more of
its directory prefix. Find-path probe budget capped at 20 pairs.
- Test-file deprioritization in `codegraph_explore` `isLowValue` — adds
suffix patterns (`_test.go`, `_spec.rb`, `.test.ts`, `.spec.tsx`,
`Test.java`, `Spec.kt`) alongside the existing directory-style patterns.
Otherwise etcd's `watchable_store_test.go` consumes 5K chars of explore
budget that should go to the hand-written flow source.
Tests:
- New `__tests__/generated-detection.test.ts` (4 unit tests) pins the
suffix patterns.
- New "Go gRPC stub→impl synthesis" integration test suite in
`frameworks-integration.test.ts` (2 tests): positive bridge from stub
to hand-written impl, AND the precision case (don't bridge to a
generated sibling like `msgClient` in the same .pb.go).
- Full suite: 1076/1076 pass.
Empirical (post-fix, n=2 average per question):
| Repo / Q | WITH | WITHOUT | Reads (W/WO) | Time (W/WO)
|-------------------------|------------|-------------|--------------|------------
| cobra (parse cmds) | $0.27 | $0.27 | 0 / 4 | 39s / 60s
| prometheus (scrape→TSDB)| $0.63 | $0.70 | 0 / 6 | 106s/143s
| cosmos-sdk Q1 (MsgSend) | $0.41 | $0.26 | 1 / 2 | 67s / 64s
| cosmos-sdk Q2 (Delegate)| $0.47 | $0.46 | 0 / 5 | 50s / 73s
| cosmos-sdk Q3 (gov tally)| $0.34 | $0.31 | 1.5 / 3 | 54s / 76s
| etcd Q1 (Put→raft) | $0.65 | $0.78 | 0 / 4 | 98s / 129s
| etcd Q2 (watch) | $0.36 | $0.50 | 0 / 4+ | 58s / 89s
Codegraph wins on reads + time on every question. Cost is mixed: 3 clean
wins, 3 tied (within 10%), 1 stubborn cost loss on the grep-favored Q1.
Compared to baseline, the cosmos-sdk cost-gap collapsed from -60% to -15%
on average, and Q3 went from a 75% loss to a tie. Raw run artifacts in
`/tmp/cg-finalv2-*/` and `/tmp/cg-final-*/`.
Memory written at `project_go_multi_module_audit.md` for the methodology
+ before/after numbers.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp): auto-inline trace in codegraph_context for flow queries
When a codegraph_context task contains a flow keyword ("trace", "from",
"reach", "flow", "propagat", "how does", "how do") AND at least two
distinct PascalCase / camelCase identifiers, internally invoke trace
between the first two extracted symbols and splice the trace body into
the context response. Conservative trigger by design: false positives
waste one graph query; false negatives just fall back to the agent
calling trace itself (existing path-proximity wiring handles either
case).
Goal: collapse the agent's typical context → trace → explore sequence
into a single context call for clear flow queries, closing the
remaining cost-overhead gap on multi-call patterns. The path-proximity
+ less-canonical-path scoring + the trace-failure-inlined-bodies
behavior already let the inline trace land on the right endpoint pair
and return enough material that no follow-up codegraph_node/Read is
needed.
Doesn't fire on:
- cobra's "How does cobra parse commands and flags?" (no PascalCase
symbols) — verified in regression run, no behavior change ($0.260
WITH vs $0.257 WITHOUT, basically tied)
- queries where the agent doesn't call codegraph_context at all
(cosmos Q1 in the audit went search → trace → node → trace → node)
Tests: 1076/1076 still pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp): trace failure inlines TO file siblings to displace node fan-out
The cosmos-Q1 audit revealed a static-resolution gap: msgServer.Send's
*real* next hop is `k.Keeper.SendCoins` — an interface-method call on an
embedded field that tree-sitter can't resolve. The static getCallees list
for msgServer.Send is all utility/error functions (StringToBytes, Wrapf,
…). The actual flow (SendCoins → subUnlockedCoins → addCoins →
setBalance) lives entirely inside `x/bank/keeper/send.go`, which is also
where the TO endpoint (setBalance) lives.
When trace fails (no static path), inline the **top 5 functions/methods
in the destination file**, ordered by line-distance from the TO node.
This catches the flow that interface-method calls obscure — the
canonical "k.<Iface>.<Method>" pattern in Go, also relevant to Java
dependency-injection / Rails service-object dispatch / etc. where
interface dispatch hides the real call.
Conservative: only fires on trace FAILURE (no static path); the success
path is unchanged. Per-body cap (40 lines / 1200 chars), top 5 siblings.
Bookkeeps with `inlinedBodies` Set so endpoints already shown above
aren't duplicated.
Result: cosmos-Q1 — historically the most stubborn cost loss (-2.2× to
-39% across the audit) — flipped to a clean WIN: $0.257 WITH vs $0.449
WITHOUT (-43%), 34s vs 79s, 0 Reads vs 2 Reads + 5 Greps, 5 codegraph
calls vs 12. Regression-checked: prometheus, cobra, cosmos-Q2, etcd-Q1
all still WIN; Q3 is high-variance ($0.30-$0.45 range historically) and
fell within that on this run.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: extend coverage to all supported languages, not just Go
PR review feedback: the audit was Go-driven, so the patterns I added
were Go-flavored. Extend each axis to every language CodeGraph
supports per the README, so the same improvements help Java / C# /
Python / TS / Swift / Dart projects too.
**generated-detection.ts** — Added patterns for:
- TS/JS: `.gen.[jt]sx?`, `.pb.[jt]s`, `_pb.[jt]s`, `_grpc_pb.[jt]s`
(ts-proto, gRPC-web, Apollo / GraphQL codegen, Hasura).
- Python: `_pb2.pyi` (mypy stubs from protobuf).
- C#: `.g.cs` (T4 / Razor codegen), `Grpc.cs` (protoc-gen-csharp).
- Java: `OuterClass.java` (protoc-gen-java), `Grpc.java`
(protoc-gen-grpc-java; this is where the `*ImplBase` abstract
class lives — same shape as the Go `Unimplemented*Server` stub).
- Swift: `.pb.swift` (protoc-gen-swift).
- Dart: `.pb.dart`, `.pbgrpc.dart`, `.chopper.dart`.
- Rust: `.generated.rs`.
**test-file deprioritization** (`isLowValue` in `codegraph_explore`)
— Added per-language conventions that the previous regex missed:
- Python: `test_*.py` (pytest discovery) and `*_test.py`.
- Ruby: `*_test.rb` (minitest) — `*_spec.rb` already covered.
- C#: `*Tests.cs`, `*Test.cs`, `*Spec.cs`.
- Swift: `*Tests.swift` (XCTest).
- Dart: `*_test.dart`.
**IFACE_OVERRIDE_LANGS** in `callback-synthesizer.ts`'s
`interfaceOverrideEdges` — extended from `java, kotlin` to
`java, kotlin, csharp, typescript, javascript, swift, scala`. Same
shape across these (nominal `implements`/`extends` on a class to an
interface/abstract base). Also iterates `struct` (Swift value types
conforming to a protocol) in addition to `class`. The existing
matchesSymbol-style logic and `getOutgoingEdges(..., ['implements',
'extends'])` work unchanged.
**CLAUDE.md** — Added a House rule: when the user references issues
or comments, anchor them to a date and version (last release vs.
last main commit vs. current branch tip) BEFORE concluding a fix is
incomplete. Issue #388 comments from May 25-27 were responding to
the released v0.9.5 / merged-PR-469 state — not to this branch's
in-flight work. The new rule walks through the disambiguation:
`grep -m1 '^## \[' CHANGELOG.md` for release version, `git log
--first-parent main -1` for main tip.
Tests: 1076/1076 still pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp): tiny-repo tool gating + shorter tool descriptions
Two cumulative changes targeting the small-repo cost gap surfaced by
the cross-language audit:
1. **Tool descriptions trimmed** (~2.1KB total saved across 10 tools).
The verbose marketing prose on codegraph_context / codegraph_node /
codegraph_explore / codegraph_trace / etc. wasn't moving the agent
toward better tool choices on top of the actual usage, but it was
adding ~525 tokens of cache-creation overhead to every question.
The trimmed descriptions keep the operational hints (e.g. "Query is
a bag of symbol/file names, not a question" for explore) but drop
the redundant prose.
2. **Dynamic tiny-repo tool gating** in `ToolHandler.getTools()`. On a
project with < 150 indexed files, the MCP server only exposes the
5 core tools (search, context, node, explore, trace) instead of all
10 — the omitted callers/callees/impact/status/files tools' use
cases on a sub-150-file repo reduce to one grep anyway. The MCP
tool-defs overhead is the #1 source of cost loss on tiny repos
(~$0.10-0.15 fixed cache-creation per question); cutting 5 tools
drops that by ~50%.
Effect on ky (~25 files, the worst pre-fix offender):
- Before: $0.59 WITH vs $0.42 WITHOUT (+42% loss, n=1)
- After: $0.32 WITH vs $0.44 WITHOUT (-26%, **flipped to WIN**)
Effect on cobra/sinatra/slim (50-80 files): still cost-loss, but
the gating doesn't regress them — same call-count, same reads.
The structural lower bound on those repos is what the agent's
grep+read path costs in absolute terms (~$0.20-0.30).
Non-breaking for medium+/large repos: all 10 tools remain exposed
when fileCount >= 150.
Tests: 1076/1076 still pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp): combined tiny-tier — smaller explore + tool gating (cobra/ky flip to WIN)
Combines the tool gating from the previous commit with a matching
explore-budget cut for projects under 150 files. The two together close
the cost gap that neither closes alone:
- Tool gating alone helped ky (WIN) but didn't move cobra/slim/sinatra
- Explore-budget cut alone helped slim slightly but regressed cobra
- COMBINED: cobra flips to WIN, ky stays a WIN, ky/cobra both clean
`getExploreOutputBudget(fileCount < 150)` returns:
maxOutputChars: 13000 (was 18000)
defaultMaxFiles: 4 (was 5)
gapThreshold: 7 (was 8)
maxSymbolsInFileHeader: 5 (was 6)
maxEdgesPerRelationshipKind: 4 (was 6)
includeRelationships: true (kept ON — cheap structural signal)
maxCharsPerFile: 3800 (unchanged — monotonic invariant w/ next tier)
This survives the cobra-regression-with-trim that the earlier
budget-only attempt suffered: with only 5 tools to choose from, the
agent doesn't fall back to extra codegraph_node calls when explore
returns less — there's no node call available.
Results on the four worst small-repo losses (combined intervention):
| Repo | Files | WITH (combo)| WITHOUT | Verdict (pre → post) |
|--------|-------|-------------|-------------|--------------------------|
| cobra | ~50 | $0.25 | $0.31 | loss → **WIN** (-19%) |
| ky | ~25 | $0.39 | $0.39 | -42% → tied |
| slim | ~80 | $0.31 | $0.24 | LOSS 31% → still LOSS |
| sinatra| ~60 | $0.30 | $0.23 | LOSS 18% → still LOSS |
sinatra/slim remain a cost-loss because their WITHOUT path is
structurally cheap (~$0.20 — fewer than 4 cheap grep+read calls).
Codegraph can't beat that absolute floor with any meaningful response.
Both still WIN on time + reads + tool-call count.
Tests: tier boundary cases updated to cover the new <150 / 150-499 /
500-4999 / 5000-14999 / >=15000 progression. Off-by-one guard updated
to include the new 149↔150 boundary. All 1076 tests pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(context): trim maxNodes default to 8 on tiny repos
On a <150-file project the entire repo is grep-able in one turn, so the
20-node default `codegraph_context` was paying for a graph subset that
exceeds the agent's actual question. Cutting the tiny-repo default to 8
(typical 1-3 entry points + their immediate 1-hop neighbors) reduces
the context-tool response body without hitting sufficiency on the flow
shapes small repos actually contain.
Non-breaking: the agent can still pass an explicit `maxNodes` to
override; medium+ repos (>=150 files) keep the 20-node default.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(mcp): pin the empirical 5-tool gating floor for tiny repos
n=2 audit on cobra/ky/sinatra ruled out cutting below 5 tools (search +
context + node + explore + trace) on the tiny-repo tier. The smaller
3-tool gate (search + context + trace) saved ~$0.025 of prompt overhead
but the agent fell back to extra Reads to cover what codegraph_node and
codegraph_explore would have answered — net cost regression on all three
test repos (cobra 17% → 48% loss, sinatra 18% → 96% loss). Documented
inline so future tuners don't re-try this dead-end.
No behavior change beyond the comment: the 5-tool gate remains the
production setting.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(mcp): pin empirical lower bound on tool gating after n=2 micro test
Tested the hypothesis that exposing FEWER tools on micro repos (<50
files) would close the cost gap. Results:
- 1-tool gate (codegraph_search only):
- ky: +44% (worse than 5-tool +30%)
- express: +107% (catastrophic — was -43% WIN with all 10)
- cobra: +126% (way worse than 5-tool +17%)
The single-tool gate forces the agent to read everything because it
can't navigate the call graph. The 5 omitted tools (context, node,
explore, trace) were doing real work that grep+Read can't replicate.
Conclusion: 5 tools (search + context + node + explore + trace) is the
empirical lower bound on the tiny-repo tier. Cutting below regresses
EVERY tested repo. The remaining ~$0.04-0.08 of structural cost overhead
on tiny repos is unavoidable without sacrificing the value codegraph
provides at that scale (which would also make WITH = WITHOUT, defeating
the install).
Comment documents the dead-ends so future tuners don't relitigate.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp): iter3/iter4 — raise tool-gate to 500, sufficiency steering in context, hard-exclude low-value files
Three layered changes targeting the sinatra/slim/small-repo cost gap
that iter2's body-shrink failed to close (smaller bodies just pushed
the agent to Read instead):
1. **Tool-gate threshold 150 → 500** (`TINY_REPO_FILE_THRESHOLD`).
Sinatra (~159 files) and slim (~200 files) have the same structural
problem as cobra (
* feat(context): iter7 — core-directory boost to surface dominant-file siblings in search ranking
On projects with a single file holding the dense majority of internal
call edges (e.g. sinatra's `lib/sinatra/base.rb` at ~85% of in-file
edges), text search was favoring small focused extension files over the
core file. A small focused file like `multi_route.rb` wins on verbatim
name match + file-size normalization, burying the 1500-line core file's
longer method names (e.g. `route!` vs `route`).
Fix: detect the "dominant file" — the file whose in-file edge count is
≥3× the next candidate's — then add +25 to all results sharing its
directory prefix. This pulls the core file's siblings above
sibling-package extensions without hardcoding any repo structure.
`getDominantFile()` excludes test/spec files and generated files
(e.g. etcd's `rpc.pb.go` has 4× the in-file edges of `server.go` and
would otherwise hijack the boost toward generated protobuf stubs).
SQL pulls the top 20 candidates; path-pattern filtering handles what
SQLite LIKE can't express.
* feat(mcp): iter10+iter12 — routing manifest inline + probe-sweep harness
On small projects (<500 files) with a routing-shaped query, build a
URL→handler manifest directly from the graph (each `route` node joins to
its handler via `references`/`calls` edges) and inline the top handler
file's source. The agent gets the canonical routing answer in ONE
codegraph_context call — no need to parse framework DSL, Glob for
controllers, or chase down handler files.
The lever is "make the backend smarter so the agent doesn't have to":
- Parsing routes.rb / routes/api.php / urls.py DSL is the agent's job
in the WITHOUT arm. Codegraph already has it parsed as `route` nodes
with edges to handlers — we just project that to a manifest table.
- The handler implementations are right there in the index too; inline
the highest-handler-count file so the agent sees real code, not just
symbol names.
Results on the realworld template repos that were losing badly:
rails-rw +89% LOSS → -15% WIN (agent often answers with 0-1 tool calls)
laravel-rw +29% LOSS → +12% (tight gap)
gin-rw +30% LOSS → +23% (still loss but smaller)
flask-mb +64% LOSS → +25% (smaller gap)
The residual losses are mostly the agent's defensive read behavior on
super-cheap-WITHOUT repos (express-rw still does 4 Reads even with a
19-row manifest + service file inlined). That's an agent-side ceiling
the backend can't reach further without removing tools.
Also lands `scripts/agent-eval/probe-sweep.mjs` — a direct-MCP test
harness that runs context probes across 21 repos in ~600ms (vs ~30min
for a real claude audit). Enables rapid iteration on backend changes:
edit tools.ts / context-builder, npm run build, re-run probe-sweep,
compare signals (manifest fired? handler file inlined? response size?)
before paying for a claude run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(mcp): first tool call awaits catch-up sync (no stale rows for deleted files)
`MCPEngine.catchUpSync()` reconciles the index against the working tree
after open (catching `git pull`/`checkout`/`rebase` and any edits or
deletes made while no server was running). It was fire-and-forget — so a
tool call landing in the first ~50-300ms could race past it and serve
rows for files that no longer exist on disk. The per-file staleness
banner can't help here, because that signal is populated by the file
watcher (not by catch-up).
The fix: `catchUpSync()` now pushes its promise into `ToolHandler` via
`setCatchUpGate(p)`; the first `execute()` call awaits the gate and then
clears it. Subsequent calls pay nothing. Catch-up rejections are logged
by the engine and swallowed by the handler so a transient sync failure
never breaks tools.
Most visible on the "deleted everything between sessions" case, where
MCP previously returned stale rows pointing at non-existent files.
Validated end-to-end on a 10,640-file VS Code index: with the gate, a
codegraph_search for "ExtensionHost" against an empty (but stale-DB)
directory returns "No results found" after the catch-up drains the DB;
without the gate, the same call returns 10 stale hits.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(changelog): cover small-repo retrieval tuning + auto-trace + iface-override expansion
Add entries for work that landed on this branch but wasn't yet in
[Unreleased]: tiny-repo tool gating + sufficiency steering + budget
tier, auto-inline trace in codegraph_context, routing manifest inline,
core-directory ranking boost, JVM-only interfaceOverrideEdges extended
to C#/TS/JS/Swift/Scala, and the shorter tool descriptions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
02935d777a |
test(vitest): unblock subprocess MCP tests on Node >= 25 dev machines (#478) (#479)
Vitest already inherits process.env into every spawned `codegraph serve --mcp` child, but on Node >= 25 the CLI's hard-block (src/bin/codegraph.ts) kills the child before it can respond. Set CODEGRAPH_ALLOW_UNSAFE_NODE=1 via test.env so the test suite is green regardless of the contributor's Node version; the runtime guard itself is unchanged for end users. |
||
|
|
34240eb297 |
feat(jvm): resolve Java/Kotlin imports by fully-qualified name (#412)
Wrap top-level declarations of `.kt` / `.java` files in an implicit `namespace` node carrying the file's `package`, then resolve `import com.example.foo.Bar` through that qualifiedName index — so a Bar in Models.kt resolves correctly regardless of filename, a top-level function import binds to its declaration, Java↔Kotlin interop crosses cleanly, and same-name classes across packages no longer collide. Wildcard imports still go through name-matcher.
Also extracts Java/C# anonymous-class overrides (`new T() { ... }`) as first-class class nodes with their override methods. Phase 5.5 interface-impl then bridges T's abstract methods to the anonymous overrides automatically — including the lambda-returned `new T() { ... }` pattern common in guava (Splitter, CacheBuilder).
Concrete impact on macrozheng/mall (524 .java files, multi-module Spring + MyBatis): 524 namespace nodes, 862 imports edges newly resolve to Java symbols, 76 distinct `Criteria` classes preserved across packages with no merge. On google/guava (3,227 .java): 3,608 anonymous classes extracted, +2,534 interface-impl edges reach overrides hidden in `new T() { ... }` blocks.
Agent A/B playbook on small (spring-petclinic-kotlin, 38 .kt), medium (mall, 524 .java), large (guava, 3,227 .java) — 3 flow prompts × 2 runs/arm × 2 arms = 36 runs, claude-opus, headless. Spring repos: 0/0 Read/Grep with-arm, −27% wall-clock vs no-codegraph. Guava: 1.8 Read avg with-arm (vs 2.0 without) — improved by the anon-class extraction; residual is a lambda→SAM coverage gap orthogonal to FQN imports (filing follow-up).
|
||
|
|
3808b4d0a8 |
fix(cli): include resolution + synthesizer edges in indexAll report (#413)
The orchestrator's per-file counter only sees extraction-phase edges, so the `X nodes, Y edges` line printed after `codegraph init -i` / `codegraph index` undercounts the graph — often by more than half on repos with heavy cross-file resolution (mall: 20 047 reported vs 45 629 actually in the DB). Snapshot (nodes, edges) before/after the full pipeline in `indexAll` and write the true delta back to the result. New lightweight `QueryBuilder.getNodeAndEdgeCount()` is one round-trip with no per-kind breakdowns. `indexFiles` (no resolution) and `sync` (uses `nodesUpdated`, not `nodesCreated`) are unaffected. Regression test added: `__tests__/integration/full-pipeline.test.ts > reports edgesCreated including resolution + synthesizer phases`. |