Commit Graph
1483 Commits
Author SHA1 Message Date
Kamal Pandey 1331be175c fix(go): normalize a generic receiver type before building its id
(b *Box[T]) reads its receiver's type field as "Box[T]" verbatim, but
the type declaration's own id is keyed on the bare name alone: type
Box[T any] struct {...} captures [T any] as a separate
type_parameter_list, never part of the name. Without stripping the
instantiation before that text turns into an id, every method on a
generic type attached to a node disconnected from the type itself --
Box's own node carried no method edges at all, even though
go_type_table-based call resolution could still reach a method through
the receiver's own-function self_type binding.

Adds _go_strip_generic_args and applies it everywhere a receiver's raw
type text is turned into an id: the live walk() pass that attaches a
method to its owner, and the separate case-collision pre-scan
(_receiver_type_of) that has to agree with it or case-only-collision
salting silently breaks for generic types.

Found while verifying the already-merged `go_type_table` member-call
resolution (deca9fa3 / cherry-picked as 1695efae) against the edge
cases from #3368 -- this is the one gap left in that fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 1a8a04ac50)
2026-10-13 22:53:44 +01:00
JensD-git 931ca2269a fix(csharp): keep names that differ only by _ or case apart, in ids and in member-call keys (#4415)
make_id strips leading/trailing underscores and casefolds, while C# is
case-sensitive and spells private members `_Name`. In one file,
`Action()` / `_Action()`, `Size` / `_Size`, `Load()` / `load()` and the
types `Pair` / `_Pair` minted one id; add_node kept the first declaration
and dropped the second, and the dropped method's calls were credited to
the survivor.

Apply the rule of #3302 to C#: a pre-scan groups the declared names of a
file (types; per type its methods, properties and enum members) that
would share an id, and _underscore_salted_nid (renamed from the Python
helper, unchanged) keeps the plain id on the one name without a leading
underscore and salts the rest. In-file type references and base lists go
through the same rule. A name without a colliding sibling keeps its id.

The C# member-call resolver keyed names by lowercased alphanumerics, so
`this.Put()` bound to `_Put()` on a nearer base, and `Token` / `TOKEN`
were one type key. The key now keeps `_` and case.

Fixes #4415.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 914dda60cf)
2026-10-13 22:21:48 +01:00
nixvideojuego-ui b4f4a5b3bc feat(csharp): type foreach loop variables for receiver resolution
A C# `foreach` loop variable had no binding in the method-scoped receiver
table, so a call on it (or a chain starting at it) never resolved across
files: `foreach (var a in agents.Values) a.look.Animate();` produced no edge.

The loop variable is now bound inside its loop: to its explicit type
(`foreach (Agent a in xs)`), or for `var` to what a class field or property
yields when iterated: the element of a `T[]` or of a sequence type, and a
dictionary's value or key type for `f.Values` / `f.Keys`. Iterating a
dictionary itself (KeyValuePair), nested collections, type parameters, and a
loop over a name that a local or parameter shadows stay untyped.

Closes #4420.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f101ad4913)
2026-10-13 22:21:48 +01:00
nixvideojuego-ui 50436e7860 fix(csharp): new X() never binds to a method that shares X's name
A C# object creation was resolved against every definition named X, methods
included. Inside a helper like `static Bounds Bounds(..) => new Bounds(..)`
(Unity's Bounds, outside the corpus) it became an EXTRACTED self-loop; across
files the bare-name pass bound every `new List<T>()` in the corpus to an
unrelated `List()` method, minting an INFERRED hub with one false caller per
construction site.

The same-file path now defers a construction whose bare-name target is a
method, and the cross-file pass drops method candidates for `csharp_new`
calls, next to the enum-member/property exclusion (#3815, #4245). A real
type with the same name still wins.

Dropping those candidates removes ties that used to hide a separate guess: a
qualified construction (`new QuickSlotsBar.Slot()`) with several bare-name
`Slot` classes went to the path-proximity tie-break, which ignores the
qualifier and picks the Slot next to the caller. A qualified construction now
takes the one candidate nested in a type named by the qualifier's last
segment, or gets no edge; namespace qualifiers stay with
`_resolve_csharp_qualified_calls`.

Fixes #4419.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 68202f9766)
2026-10-13 22:21:48 +01:00
nixvideojuego-ui f41ec2ec26 fix(csharp): never bind a nameless member-call receiver to a same-named method in the file
A C# member call whose receiver has no capturable name — a member chain
(`run.Sequence.Apply()`, `s.Items.Count()`), an invocation (`Make().Apply()`)
or a `new X(...)` — reached the per-file bare-name map, because the C# defer
rule required `member_receiver`. It then bound to the caller's own same-named
method as an EXTRACTED edge: a false self-loop (`a.look.LateUpdate()` inside
`LateUpdate()`), LINQ `Count()` bound to a local `Count()`, or a typed chain
landing on the wrong class instead of reaching `receiver_chain` resolution.

Every C# member call now defers. A typed chain still resolves through
`receiver_chain`; `new X(...).M()` (#1770) is typed from the construction so
it keeps its edge; anything untypable gets no edge (fail-closed).

Fixes #4418.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d5544b125f)
2026-10-13 22:21:48 +01:00
JensD-git ab8d63bf4c fix(csharp): salt colliding ids before the partial merge, so a half keeps its members (#4411)
Two files can mint the same stem (`Spec.cs` / `_Spec.cs`, `Cfg/Helpers.cs` /
`Cfg_Helpers.cs`), so the type ids in them collide. The C# partial merge ran
before _disambiguate_colliding_node_ids: it rewired the edges of every half
onto the canonical half's still-colliding id, and the disambiguation, which
remaps by (id, source_file of the edge), then missed every edge that came from
another half and left it on a dead id. The members of that half had no owning
type. When both colliding files declared a half of the same partial type, the
second node kept the canonical id, was not removed by the merge, and was split
off again, so the type stayed two nodes. And since the merge removes
merged-away halves by id, an unrelated node in another file that shared such
an id was removed with them.

Run the disambiguation first. Every edge is still on its own half when ids are
salted, and the merge joins the halves by type path, whatever their ids.

Fixes #4411.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 78575576a8)
2026-10-13 22:21:43 +01:00
SrijanSriv 642e4b8462 fix(extract): keep edges between kept nodes when edge source_file is undispatched (#2276)
(cherry picked from commit cf78a6c6c9)
2026-10-13 22:21:43 +01:00
SrijanSriv edb93314b0 fix(python): walk enclosing scopes for module member calls (#2447)
(cherry picked from commit e34b753891)
2026-10-13 22:21:43 +01:00
nikhil2004 22124ba76d fix(detect): classify Jinja templates as documents (#3427)
The bug:
Jinja2 templates were completely skipped by Graphify's detection phase because their extensions were not listed in the recognized document extensions. This resulted in the source for generated files being hidden from the graph.

The fix:
Added `.j2`, `.jinja`, and `.jinja2` to the `DOC_EXTENSIONS` set in `graphify/detect.py`.

Tests:
`tests/test_detect_jinja.py` added to verify the newly added Jinja extensions are accurately recognized as documents. This ensures the bug does not regress in future updates.

(cherry picked from commit 61f6a40a56)
2026-10-13 22:21:43 +01:00
Deepjyoti-Sarmah 8d5b139da4 fix(dedup): keep a dropped-folder doc next to a top-level same-id file (#4281)
With protocol.md next to docs/protocol.md and docs/firmware/protocol.md, all
three minted `protocol` and the label tiebreak dropped a real document.
A file whose full stem is the id now defines it and keeps the bare id; the
other files are path-qualified when they have no other node spelled with
their own full stem. If they do, the bare id may be a reference, so the file
keeps today's collapse (known boundary). A genuine reference to the top-level
file still collapses onto it.

A bare path slice from a file that has its own page ranks as a reference, so the
real file survives whatever the labels are.

(cherry picked from commit a3fa3c47c4)
2026-10-13 22:21:43 +01:00
xiongjianxu 41b4e197b2 Finish Objective-C member calls across a repository boundary (#3152)
`_resolve_objc_member_calls` types a receiver from `self`/`super`, an explicit
class name, the file's local table or the class's @property/ivar table, then
requires exactly one declaration of that type. Zero declarations and more than
one shared a single bail, so a call into a class another repository declares was
dropped with the receiver type already in hand — the case #3152 parks for the
merge in Java, C++, C# and Swift.

Split the three type-known bails: zero declarations parks `{callee,
receiver_type, lang: "objc", line}` on the caller, more than one stays dropped as
before (a local ambiguity that merging only widens). A builtin receiver type is
filtered out at the parking sites, matching what the Swift and C++ resolvers
already skip before resolving.

On the merge side an ObjC method label keeps its `+`/`-` sigil where every other
extractor writes `.name()`, and a parked selector does not say which was meant,
so both spellings are tried and a class declaring both counts as ambiguous. The
sigil is what keeps ObjC and C++ members disjoint inside a shared `.h`, which is
in both languages' suffix sets.

Depends on the `_callable` / `_callable_class` markers (#3228) that
`_index_declarations` indexes declarations by; the commit before this one carries
them.

(cherry picked from commit 487fd5490d)
2026-10-13 11:18:12 +01:00
xiongjianxu 70f102270d Mark ObjC declarations with _callable / _callable_class (#3228)
Every other extractor stamps its definitions through the generic engine:
`_callable` for "a real callable, not a same-named data symbol" (#2438),
narrowed to `_callable_class` for a type, which is callable only through a
constructor (#2137). The ObjC extractor builds its nodes by hand and set
neither, so an ObjC class was invisible to every pass that indexes
declarations by those markers — the `same_type_as` pass from #3007, the
qualified-name indexes, and anything new that needs to tell a declaration
from a reference — and an ObjC method could not be told apart from a data
symbol of the same name.

`@interface`, `@implementation` and `@protocol` nodes now carry both
markers (a protocol is a type declaration, the same way the engine marks a
Java or C# interface), and method nodes carry `_callable`. The file node
and the stubs minted for dangling references stay unmarked: a stub has no
declaration behind it.

(cherry picked from commit 9a64af9be1)
2026-10-13 11:16:15 +01:00
xiongjianxu 83a393b695 Park Kotlin object member calls across a repo boundary (#3388)
The cross-file `_resolve_kotlin_member_calls` landed for #1698 binds
`Receiver.method()` only when the object/class the receiver names is declared
in the same build. When that declaration lives in another repository the pass
had the receiver name in hand and dropped the call anyway, so nothing about it
reached graph.json — the one artifact merge-graphs and global add read — and no
merge-time pass could recover what was never written down.

Park the call by name at that bail-out, the same mechanism Java/C++/C#/Swift
already use: only when the object is declared nowhere here (an ambiguous 2+ match
still fabricates nothing) and never for a builtin receiver. cross_repo_calls then
finishes the edge once the declaring repo joins the graph, gated on .kt/.kts so a
same-named declaration in another language cannot answer a Kotlin call.

Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit fa193c96c2)
2026-10-13 11:16:10 +01:00
xiongjianxu bf13b63ca4 fix(ts): index only the JS/TS interop family for receiver typing
A corpus-wide type index let a same-named class in another language stand in for
the receiver's type, which hid from the parking branch that nothing in the JS/TS
family declares it. SFC script blocks are in the family, so a class exported from
a .vue file still answers — here and at merge time.

(cherry picked from commit 078c106e62)
2026-10-13 11:15:14 +01:00
xiongjianxu 60c2f16ea3 Park TS/JS member calls whose receiver type is declared in another repo (#3152)
`_resolve_typescript_member_calls` binds `this.repo.save()` only when the
receiver's type is declared in the same build. With the type in another
repository the resolver held the receiver type and dropped the call, so
`graph.json` — the only artifact `merge-graphs` and `global add` read — recorded
nothing and no merge-time pass could recover it.

Those calls are now parked on the caller as `metadata.unresolved_calls`, and
`link_cross_repo_member_calls` finishes them after the merge. One
`_LANG_SUFFIXES` key covers all eight TS/JS suffixes: a TS class legitimately
answers a JS call site, and two keys would drop every TS<->JS cross-repo call.

Only a table-typed receiver parks. The uppercase-spelling arm also matches
namespace aliases and default imports (`React.createElement()`), and
`node_modules` is never scanned, so parking those would send npm names to the
merge to be matched by bare name. The park sits strictly upstream of the #2553
origin gate, which only ever vets a name that did match locally.

(cherry picked from commit 5b593c6d00)
2026-10-13 11:15:14 +01:00
xiongjianxu b4527cc8a6 Activate the TS/JS member-call resolver for .mjs and .cjs
`_DISPATCH` routes `.mjs`/`.cjs` to `extract_js` and the cross-file call gate
already lists them, but `typescript_member_calls` claimed only six suffixes, so a
repo written entirely in `.mjs` was the one shape where the resolver never ran and
`const s = new Svc(); s.doThing()` produced no edge.

Both the suffix set and the resolver now read one `_JS_TS_SUFFIXES` constant.

(cherry picked from commit 84fce94ce8)
2026-10-13 11:14:35 +01:00
xiongjianxu d045891235 feat(cross-repo): link parked PHP member calls across a repo boundary
A receiver typed to a class this build declares nowhere is parked on the
caller (#3152); the merge then binds it to the one declaration in another
repo. PHP was absent from `_LANG_SUFFIXES`, so parked PHP entries were never
language-checked and the edge was dropped. Adding PHP's suffixes lets the
merge finish the call, gated to PHP declarations like every other tier.

Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit e7fee26542)
2026-10-13 11:14:30 +01:00
xiongjianxu 0d635ff429 feat(php): resolve instance member calls through the receiver's declared type
v8's `_resolve_php_member_calls` (#3872) binds scoped `Class::method()` calls
but leaves instance `$g->method()` calls unresolved. This adds the instance
tier beside it: a per-file receiver type table (`_php_receiver_type_table`
over typed properties, promoted constructor params, typed params and `new`
bindings) names the receiver's class, and the shared resolver takes the single
PHP declaration of that class and emits the `calls` edge, INFERRED.

The declaration index is gated to the PHP interop family: the index is
corpus-wide, so a same-named class in another language would both answer the
receiver and hide that no PHP file declares it. A receiver the table cannot
type to one class — untyped, union, longer chain — stays unresolved.

Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit 199ba1af0d)
2026-10-13 11:14:30 +01:00
xiongjianxu ed2443f959 test(go): pin both directions of per-function receiver scoping
test_the_first_binding_of_a_name_wins asserted the flat table's cross-function
shadowing as intended behaviour, which the scoping fix removed; its fixture now
pins that each function's parameter answers only its own calls.

(cherry picked from commit 8a4bfef899)
2026-10-13 11:14:26 +01:00
xiongjianxu e159d57c8a fix(go): type a receiver from its own function's scope before the flat table
Parameter names repeat across a Go file, so the file-flat table answered one
function's receiver with a sibling function's parameter type: `Second(g *Shouter)`
calling `g.Greet()` bound to Greeter.Greet. Each function body now carries its own
parameter/local table, consulted before the flat one, which keeps serving fields.

(cherry picked from commit b74abf098a)
2026-10-13 11:14:26 +01:00
xiongjianxu 87f8b25d0c fix(go): gate the declaration index on Go files, type the method's own receiver
A corpus-wide `_callable_class` lookup let a same-named class in another language
answer a Go receiver, and it hid from the parking branch that no Go file declares
the type. The flat per-file table also mistyped a method's own receiver when an
earlier binding of the same name won, so `s.Save()` inside a `Store` method could
reach `Server.Save`.

(cherry picked from commit 86792724ff)
2026-10-13 11:14:26 +01:00
xiongjianxu 4874f071fa feat(go): park a member call whose receiver type is in another repo (#3152)
The Go resolver had the receiver's type in hand and dropped the call when nothing
in the corpus declared it, so `graph.json` — the only artifact `merge-graphs` and
`global add` read — recorded nothing and no merge-time pass could recover it. A
merged graph of a Go service and a Go library was missing exactly the edges that
make it a call graph.

Parks the pair on the caller node like the Java/C++/C#/Swift resolvers already do,
and adds `"go": {".go"}` to the merge pass's language guard. `.go` alone: Go and
Java both turn up in one backend merge, and a shared type name across them is a
coincidence, not a namespace.

The `_key` case preservation the merge pass already documents is what makes this
correct for Go — `greet` is a different method from `Greet` and is not reachable
from another package at all.

(cherry picked from commit f7d2a50603)
2026-10-13 11:14:26 +01:00
xiongjianxu 1695efae1a feat(go): resolve member calls through the receiver's declared type
`g.Greet()` where `Greeter` is declared in another file produced no edge. The
shared cross-file pass skips member calls, and the Go extractor read the
receiver's identifier only to discard it for anything that was not an imported
package — so the one shape Go dependency injection actually takes (a struct
field used from a method) contributed nothing to the call graph.

`extract_go` now exports a per-file `go_type_table` built from the four places a
Go receiver's type is written down: a struct field, a parameter (which covers the
method receiver `func (s *Server)`), `var x T`, and `x := T{}` / `x := &T{}`.
`_resolve_go_member_calls` looks the receiver up there, takes the single
declaration of that type and emits the `calls` edge to its method.

Also stamps `_callable` / `_callable_class` on Go declarations (#2438). Go is the
one extractor outside the tree-sitter engine, so its nodes carried neither, which
left them invisible to every consumer that reads the markers off the node.

Design notes:

- The receiver travels as `member_receiver`, not `receiver`: in this extractor
  `receiver` means "imported package", and the Swift/Python/Ruby member-call
  resolvers select on `is_member_call` plus `receiver` without checking the
  language, so a Go name there binds to their types in a mixed corpus.
- `s.logger.Log()` is read as a call on the `logger` field only when `s` is the
  enclosing method's own receiver. The table is flat and file-scoped, so any
  other head could be a package or a variable whose type it cannot confirm.
- Names are matched case-sensitively, unlike the sibling resolvers. Go exports by
  capitalisation, so `Run` and `run` on one type are two different methods.
- Go does not defer: a member call reaches `raw_calls` only once the bare callee
  name misses in the caller's file, so in-file behaviour is byte-identical.
- Two packages declaring the same name stay two type nodes and the
  single-definition guard bails — without import evidence neither is the answer.

(cherry picked from commit deca9fa366)
2026-10-13 11:14:26 +01:00
xiongjianxu 15d2312b03 fix(go): fold a package's type into one node when its methods span files (#3399)
extract_go keys a type on its package directory, so every file declaring a
method on it mints the type again under the same id. Disambiguation then splits
those apart by path, and the type ends up fragmented into several partial nodes,
each owning some of its methods: the declaring file's node owns none of them,
and every single-definition guard downstream reads the set as an ambiguity.

Fold the id-collision before disambiguation, the same point and the same shape
as _merge_decl_def_classes for C/C++/ObjC. Guards: all members must be .go
files in one directory (the id folds in only the directory's name), and a
_test.go member stops the fold, since its package clause may be a separate
external test package. The declaring file wins; no edge re-pointing is needed
because the group already shares one id.

(cherry picked from commit 7f9439ddd5)
2026-10-13 11:14:22 +01:00
Alex Tumanov 62094a7626 fix: preserve attributed Objective-C enum members
Allow clean enumerator attributes while keeping unsupported preprocessor bodies fail-closed.

AI-Assistance: OpenAI Codex
(cherry picked from commit 0f59982d5e)
2026-10-13 11:14:16 +01:00
Alex Tumanov 810a02ef28 fix: reject incomplete Objective-C enum macro parses
Validate both complete argument spans and reject unsupported body children instead of emitting partial enum graphs.

AI-Assistance: OpenAI Codex
(cherry picked from commit 7c09b4d360)
2026-10-13 11:14:16 +01:00
Alex Tumanov 61b43f5e91 feat: extract Objective-C enum macros
Extract NS_ENUM and NS_OPTIONS typedefs in Objective-C files using isolated enum body parsing and original member locations.

Fixes #4400

AI-Assistance: OpenAI Codex
(cherry picked from commit d6a74da475)
2026-10-13 11:14:16 +01:00
rajatnagda45 786e8868a0 fix(extract): drop Swift generic type parameters from references
A `struct Stack<Element>` / `func map<R>(...)` binds `Element`/`R` as local
names, not corpus symbols, but `_swift_collect_type_refs` appended the bare
identifiers used in a signature or stored-property type as type references and
ran them through the sourceless-stub path. Every generic type/function in the
corpus then collapsed its `T` onto one bare `t` node — a god node that pollutes
god-node rankings, since single-letter type parameters are not in the
analyzer's builtin-noise filter. C#, Java, TypeScript, Kotlin, Scala, C++, and
Go already exclude theirs.

Mirrors that pattern (and complements the `Self` keyword fix already in this
collector): a new `_swift_type_parameters_in_scope` helper walks up to every
enclosing declaration's `type_parameters` list and takes each `type_parameter`'s
first `type_identifier` as the bound name (a constraint like `T: Equatable` is a
separate `user_type`, left to reference), and `_swift_collect_type_refs`
auto-computes and threads that skip set. Genuine user types and the `Int`/
`String` builtin hubs are unaffected.

(cherry picked from commit 2e8a5c0f97)
2026-10-13 11:14:16 +01:00
rajatnagda45 5299fb1e81 fix(extract): drop C++ template type parameters from references
A `template<typename T>` binds `T` as a local name, not a corpus symbol, but
`_cpp_collect_type_refs` appended the bare identifiers used in a template
signature or member as type references and ran them through the sourceless-stub
path. Every template in the corpus then collapsed its `T` onto one bare `t`
node — a god node that pollutes god-node rankings, since single-letter type
parameters are not in the analyzer's builtin-noise filter. C#, Java,
TypeScript, Kotlin, Scala, and Go already exclude theirs.

Mirrors that pattern: a new `_cpp_type_parameters_in_scope` helper walks up to
every enclosing `template_declaration`'s `template_parameter_list` and collects
names from its `type_parameter_declaration`/`variadic_type_parameter_declaration`
children (a non-type `int N` parameter is a value and is left alone), and
`_cpp_collect_type_refs` auto-computes and threads that skip set. Genuine user
types and the generic container head still reference.

(cherry picked from commit e3bf52b318)
2026-10-13 11:14:16 +01:00
rajatnagda45 836fb8ff9c fix(extract): drop Scala generic type parameters from references
A `class Box[T]` / `def map[R](...)` binds `T`/`R` as local names, not
corpus symbols, but `_scala_collect_type_refs` appended the bare identifiers
used in a signature as type references and ran them through the sourceless-stub
path. Every generic class/method in the corpus then collapsed its `T` onto one
bare `t` node — a god node that pollutes god-node rankings, since single-letter
type parameters are not in the analyzer's builtin-noise filter. C#, Java,
TypeScript, Kotlin, and Go already exclude theirs.

Mirrors that pattern: a new `_scala_type_parameters_in_scope` helper walks up
to every enclosing declaration's `type_parameters` list (the use site is
`type_arguments`, a distinct node) and collects parameter names from direct
identifiers and variance-annotated params, skipping `context_bound`/
`upper_bound`/`lower_bound` nodes (those name real types). `_scala_collect_type_refs`
auto-computes and threads that skip set. Context bounds (#2046), genuine user
types, and the generic container head still reference.

(cherry picked from commit 585e909535)
2026-10-13 11:14:16 +01:00
rajatnagda45 6d64681bd9 fix(extract): drop Kotlin generic type parameters from references
A `class Box<T>` / `fun <R> map(...)` binds `T`/`R` as local names, not
corpus symbols, but `_kotlin_collect_type_refs` appended the bare identifiers
used in a signature or property type as type references and ran them through
the sourceless-stub path. Every generic class/function in the corpus then
collapsed its `T` onto one bare `t` node — a god node that pollutes god-node
rankings, since single-letter type parameters are not in the analyzer's
builtin-noise filter. C#, Java, TypeScript, and Go already exclude theirs.

Mirrors the C#/Java pattern: a new `_kotlin_type_parameters_in_scope` helper
walks up to every enclosing declaration's `type_parameters` list (the use site
is `type_arguments`, a distinct node, so an instantiation argument is never
collected), and `_kotlin_collect_type_refs` auto-computes and threads that skip
set. Genuine user types and the generic container head still reference.

(cherry picked from commit a9304a245c)
2026-10-13 11:14:16 +01:00
Kamal Pandey 9aa9d03aa5 fix(dispatch): don't count gate-failing implementers toward confidence
_dispatch_confidence was computed from the raw implementers set before
the per-implementer language gate ran, so a sourceless/external-reference
implementer, or a cross-language name collision, that never produces an
edge could still dilute the confidence score for the implementer(s) that
do. A single real implementer alongside one gate-failing entry scored
0.75 (the two-implementer tier) instead of 0.85.

Gate first, then count only the implementers that pass it, so the score
reflects how many implementers actually share the dispatch edge.

Addresses review feedback on PR #4396 (Graphify review bot, finding at
interface_dispatch.py:154).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit b01a6fcf10)
2026-10-13 11:14:16 +01:00
Kamal Pandey e7395e66d8 feat(dispatch): link interface members to every implementer, not just one
resolve_interface_dispatch (shared by the C# and Swift gates) bailed out
entirely whenever an interface/protocol had more than one implementer,
on the reasoning that guessing which one a runtime call reaches would be
wrong. For a static structural graph the question isn't which one is
reached at runtime, it's which ones *can* be reached, and the answer with
N implementers is all N, not none -- so every chain through an injected
dependency with more than one implementation was left permanently cut at
the interface.

Each implementer is still subject to the existing single-owner guard
independently: an implementer that itself declares more than one method
of the same name is skipped on its own, without suppressing its siblings.
Confidence drops as the candidate set grows, using the extraction
rubric's own discrete INFERRED tiers (0.85 / 0.75 / 0.65 floor) instead of
inventing continuous precision.

Fixes #3800 for the dispatches_to half (interface->implementation calls).
The property-access half of that issue (a method edge for `c.Count`
through an interface-typed receiver) is a separate mechanism and is not
addressed here, to keep this PR to one concern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit f340494e07)
2026-10-13 11:14:16 +01:00
Alex Tumanov 750b564285 fix: preserve earlier binary URL captures
Use a free numbered filename when the binary capture path is occupied.

Fixes #4393

AI-Assistance: OpenAI Codex
(cherry picked from commit 341d877c46)
2026-10-13 11:14:16 +01:00
Dev Chiniwala 953e6d489c fix(resolution): resolve js imports to tsx sources
(cherry picked from commit d22124709b)
2026-10-13 11:14:16 +01:00
Andre Brait 6ebe27a3c9 fix(pi): keep checking later glob targets after malformed hook output; document Pi 0.58.4+
(cherry picked from commit d449180d75)
2026-10-13 10:27:02 +01:00
Andre Brait 3b3ad322a6 feat(pi): add a native Pi guard extension that also loads in Oh My Pi
Pi has no PreToolUse hook, so the installed hook-guard policy never ran
there. graphify/pi/ ships a Pi package (Node built-ins only, no
node_modules) that runs `graphify hook-guard read|search` before Pi's
read, grep, find and search-style bash calls: strict-mode denials become
tool_call blocks, and guidance arrives as a graphify system reminder.
Only local targets inside the project are checked, the hook uses the first
`graphify` on PATH outside the project, and every call is bounded and
fails open.

`graphify pi guard install|uninstall` delegates registration to Pi's own
`pi install|remove <dir>`; `graphify pi guard path` prints the package
directory. Oh My Pi reads the same `pi` manifest key, so
`omp plugin install "$(graphify pi guard path)"` loads it there too,
where guidance uses OMP's passive tool-call context channel.

(cherry picked from commit a6281363bb)
2026-10-13 10:27:02 +01:00
Andre Brait ae334f99f3 fix(hook): leave piped greps out of the nudge hint
A grep fed by a pipe filters that command's output, not the code, so its
pattern is not a graph lookup the agent skipped.

(cherry picked from commit f9f3c9ce3a)
2026-10-13 10:26:57 +01:00
Andre Brait cdbaa16245 fix(hook): harden nudge hints after review
Sanitise graph text before it reaches the hint (one line, no control
characters or backticks, capped length), skip the graph parse when the call
has nothing to look up, isolate hint failures so they can never cost the
nudge or the strict deny, skip value-taking flags (rg -t/-g, -A/-B/-C...)
and file-name searches (find/fd), drop declaration keywords from terms,
match labels only (no source-path tier, short terms exact-only), resolve a
relative read target against the project root, and align the strict deny's
wording with the narrowed nudge.

(cherry picked from commit f8ba08fe93)
2026-10-13 10:26:57 +01:00
Andre Brait ae2035efa0 feat(hook): show the graph nodes behind a nudged call, and tighten its wording
Agents treated the soft nudge as noise: it was the same placeholder text on
every call, and "or to modify/debug specific lines" excused nearly any
targeted read. A firing nudge (and the strict-mode deny) now carries:

- a command built from the call: `graphify explain "<symbol>"` when the
  searched word is a node label, `graphify query "<terms>"` otherwise, or
  `graphify explain "<file>"` for a read;
- up to five matching graph nodes with file and line, so the nudge shows
  what the graph already knows instead of only asking for it.

The lookup scans graph.json labels and paths directly (0.1-0.5 s on 3k-15k
node graphs; the full query stack costs ~1.5 s per hook call), skips graphs
over 32 MiB, and falls back to the plain nudge on any failure or no match.

The escape clause now allows raw reads only once the exact file and lines
are known, and the always-on rule names the moment to query: before the
first grep, search or source-file read.

(cherry picked from commit b51314765c)
2026-10-13 10:26:57 +01:00
rohit-jsfreaky e90bd328d9 fix(js): anchor rescued external imports to the package, not the path leaf (#3084)
(cherry picked from commit 794e0c391f)
2026-10-12 23:02:50 +01:00
rohit-jsfreaky ba8b4c863c fix(js): keep import edges to declared npm packages (#3084)
(cherry picked from commit 70bdd8a6dc)
2026-10-12 23:02:50 +01:00
himanshupatro-334 a4028f3c8d fix: resolve cross-directory C/C++ quoted includes (#4354)
(cherry picked from commit 97b8c8dbf2)
2026-10-12 23:02:50 +01:00
rohit-jsfreaky 2531ab7dfc feat(java): resolve calls on chained, new and cast receivers through declared return types
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit 23f3604948)
2026-10-12 23:02:44 +01:00
rohit-jsfreaky 8297745215 fix(java): a superclass method wins over an interface default for a bare call
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit 85fab922d2)
2026-10-12 22:58:51 +01:00
rohit-jsfreaky 44a7d50aec fix(java): resolve bare calls and field receivers inherited from a supertype in another file
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit 724626659a)
2026-10-12 22:58:51 +01:00
rohit-jsfreaky 06b8d71245 fix(wiki): link index rows to their own article when labels collide (#3032, #3033)
(cherry picked from commit 2816f9365b)
2026-10-12 22:58:40 +01:00
AndresDiagoM 61ce767dab test(build): cover AST-twin shrink-guard fix end to end; sync alias docstrings
- Add to_json() and watch._check_shrink() regression tests that build a real
  graph where build_from_json folds a semantic ghost into its AST twin (#4389),
  plus a negative control showing a genuine semantic loss is still refused.
  Both positive tests fail on v8 and pass with this branch.
- Update _fold_node_aliases / _fold_edge_aliases docstrings and the build()
  dedup comment to list the new file/filepath and relationship aliases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 946333b2ab)
2026-10-12 22:58:40 +01:00
AndresDiagoM 3c5b1687fb fix(build): tighten AST-twin exemption to strict canonical AST origin and exclude collisions
(cherry picked from commit b2965c9dd5)
2026-10-12 22:58:40 +01:00
AndresDiagoM bbddf831ba fix(build): exempt deduplicated AST-twin semantic nodes from shrink guard and fold LLM node/edge field aliases
(cherry picked from commit 73a76fcd5d)
2026-10-12 22:58:40 +01:00