Commit Graph
2402 Commits
Author SHA1 Message Date
Mpasha17 c4589a0c1b fix(ts): don't guess between members the normalized key can't tell apart
Review on #4479: after the exact-name lookup, the `_key` fallback in
_resolve_typescript_member_calls still matched loosely. With #4475 splitting
close/#close, updateHash/_updateHash and set/$set into separate nodes, one
normalized key can now map to two members, and method_index kept whichever
was registered last. So c._close() bound to #close, c.UpdateHash() to
_updateHash, and c.a() to $a, none of which is the called name. Track keys
that more than one member normalizes to and skip the fallback for them (no
edge rather than a guess). Exact hits, and loose hits with a single
candidate, are unchanged.

Co-Authored-By: Grok Bot
(cherry picked from commit a1ce057757)
2026-10-16 17:11:41 +01:00
Mpasha17 70a600196f fix(js): keep class members that differ only by _, $ or # as separate nodes
make_id drops _, $ and #, so updateHash/_updateHash, close/#close and
set/$set in one JS/TS class body minted one id and the second member
disappeared, with its calls attributed to the survivor. Group each class
body's member names by id and reuse the Python/C# collision rule
(_underscore_salted_nid, now with configurable private prefixes): a unique
public name keeps its id, colliding private ones get a salted id.
Non-colliding members keep their ids. The TS cross-file member resolver now
matches the exact member name before falling back to the normalized key, so
c.set() still binds to set and not $set.

Fixes #4475

Co-Authored-By: Grok Bot
(cherry picked from commit 03beac9d09)
2026-10-16 17:11:41 +01:00
Hemanth 627c49a770 fix(python): salt module-level class ids that collide with a function or class (#2810)
The Python collision pre-scan (#3302) recorded module-level functions and
class methods but not module-level class names, so a class sharing an id
with a function or another class after normalization still lost one
declaration to first-wins add_node:

- `class Session` / `def session()` (requests/sessions.py): session() is
  absent from the graph, and which one survives depends on declaration order
- `class Foo` / `class FOO`, `class Pair` / `class _Pair`

Record module-level classes in the same scope as functions and salt the
class id with the existing _underscore_salted_nid rule. Methods of a salted
class are minted under the salted id, so the pre-scan now keys their groups
under the class's resolved id; keying them under the plain one would drop
the existing `_get`/`get` protection inside that class. Same-file base-class
references go through the same salt (as C# already does), so
`class Sub(Session)` binds to the real class instead of minting a sourceless
`Session` stub. The pre-scan also looks through decorated_definition, so
`@dataclass class Session` and the `_x` / `@property x` method idiom are
covered too.

Ids change only for files that contain such a collision; cached AST results
for them keep the collapsed ids until the AST cache schema is bumped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 03b420df97)
2026-10-16 17:11:41 +01:00
Deepjyoti-Sarmah 74ed051be0 fix(hooks): rebuild after cherry-pick, rebase and pull --rebase (#4490)
post-commit skipped on CHERRY_PICK_HEAD, which git leaves on disk while
the hook runs, so a clean pick never rebuilt. A rebase has no hook that
fires once at the end, because post-commit and post-checkout skip while
rebase-merge/ exists.

Drop the CHERRY_PICK_HEAD check. For a range pick or revert, skip every
step but the last and diff from sequencer/head, so one rebuild covers
the range; ignore a stale sequencer/ left by git reset --hard. Add a
post-rewrite hook that rebuilds on rebase.

(cherry picked from commit 47085dd742)
2026-10-16 17:10:31 +01:00
Deepjyoti-Sarmah b3af356532 fix(hooks): drop MERGE_HEAD guard from post-merge so real merges rebuild (#4463)
git leaves .git/MERGE_HEAD on disk while post-merge runs and deletes it
only after the hook returns, so the guard exited on every merge that
makes a merge commit. Only fast-forwards rebuilt. The post-commit and
post-checkout guards are unchanged.

(cherry picked from commit c37d35bd50)
2026-10-16 17:10:31 +01:00
Mpasha17 6c81b838b5 fix(detect): only treat a valid .git as a VCS root (#4444)
_find_vcs_root() accepted any .git, so an empty or partial .git folder in a
home directory became the ignore-walk ceiling and its .gitignore leaked into
unrelated scans. A .git directory now needs HEAD, objects/ and refs/ (what
git checks), and a .git file must start with gitdir: (worktrees,
submodules). No git subprocess; OSError means not a root. Other VCS
markers are unchanged.

The three VCS-boundary tests that faked a repo with a bare mkdir .git now
use a minimal valid .git.

Co-Authored-By: Grok Bot
(cherry picked from commit f0b6493163)
2026-10-16 17:10:31 +01:00
berce 7b76ce3a97 fix(js): drop the barrel-named import edge once the import resolves past it (#4486)
A named import through a barrel gets an `imports` edge from the structural
extractor to `<barrel>_<symbol>`, and the symbol facts add a second one to
the defining symbol. The barrel defines nothing, so the first edge dangles
and build mints it as an external stub. Through a named re-export barrel
the first edge was repointed instead, so the same import came out as two
identical edges.

After the facts loop, drop the structural edge when its import resolves
to exactly one owned symbol behind the barrel. An ambiguous barrel (one
name from two modules) and a chain that ends in a third-party package
keep the edge, as before.

(cherry picked from commit 0aaac55a86)
2026-10-16 17:10:31 +01:00
berce f97677d5d1 fix(js): fall back to src/ when a workspace package's exports name only dist/ (#4485)
#3834 let a workspace import whose `main`/`types` point into dist/ fall
through to `src/index`, but an `exports` map returned its own candidates
and never reached that fallback. A tsc-built package with
`"exports": {".": {"import": "./dist/index.js"}}` therefore resolved to
dist/ (outside the corpus, so an external stub) when it was built, and to
nothing on a clean clone.

Append each build-output exports target's source twin (dist/sub.js ->
src/sub) and, for the root entry, src/index and index. They come after the
exports targets, and _resolve_workspace_import already prefers a file
outside build output, so source wins when it exists and dist/ stays the
fallback.

(cherry picked from commit 705abe6c4c)
2026-10-16 17:10:31 +01:00
N. Hariharan 13294c0cf0 feat(llm): opt-in backend selection, disable switch, and honest auto-detect (#4361)
detect_backend() picks a cloud provider purely from whichever API key is set
in the environment, and community labeling additionally falls back to claude -p
when the CLI is on PATH. Keys in a developer environment are often there for
other tools, so a bare `graphify extract .` could send uncached documents to a
provider the user did not intend.

Add explicit control without breaking existing auto-detect flows:

- GRAPHIFY_BACKEND=<name> selects a backend explicitly and wins over ambient
  keys; GRAPHIFY_BACKEND=none disables every LLM backend, including the label
  claude -p fallback. `graphify extract` then keeps the local AST pass and
  reports what it held back instead of failing or auto-selecting a provider.
- Auto-detection now reports the exact environment variable that selected the
  backend, so sending a corpus to a cloud provider is never a surprise.
- The claude -p labeling fallback is announced, and GRAPHIFY_BACKEND=none opts
  out of it (it is otherwise kept: silently downgrading good labels to
  "Community N" was the reason it exists).
- extract_files_direct raises a clear error naming GRAPHIFY_BACKEND when the
  library caller would otherwise auto-detect a disabled backend.

README data-residency note updated. detect_backend_with_source() now carries the
detection logic and names its source; detect_backend() delegates to it.

Note: this deliberately keeps auto-detection ON by default (the issue's
alternative #2) rather than flipping cloud use to opt-in, which would break
every existing key-less `graphify extract` flow. GRAPHIFY_BACKEND makes the
opt-in switch a one-liner for a follow-up if maintainers prefer it.

(cherry picked from commit 7e2467ea43)
2026-10-16 17:10:31 +01:00
Dennis Cornwell 08f5304ac7 fix(llm): pin source_file to the dispatched file in the extraction prompt
The native-backend extraction prompt never says what source_file means; its
only guidance is the schema placeholder "relative/path". When a document
cites, links, or describes another file (a "Source:" line, a "Related:" list,
a wiki link, a class a design doc specifies), the model often attributes the
document's own entities to that file. On an incremental run the out-of-scope
filter (#1895) then drops them, so the document silently loses nodes (#2297,
#3377), or a doc-described API is attributed to the code file (#2270).

The skill path already states a source_file RULE in extraction-spec.md. Add
the native-backend equivalent: source_file is the `path` of the enclosing
<untrusted_source> block (or the listed source_file for an image), a file
named inside a source is a reference rather than the source, and the
reference is modelled as a node attributed to the enclosing file plus a
`references`/`cites` edge. The prompt change re-keys the semantic cache via
the existing prompt fingerprint (#1939), so affected files re-extract on
their next change.

Closes #3377
Refs #2270, #2297, #2217, #3982, #2276

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ff0b4e0dcd)
2026-10-16 17:10:31 +01:00
safishamsiandClaude Opus 4.8 10130e79ed chore(release): 0.9.91
Eight community PRs: Windows MCP-stdio hang (#4441), Rust use->declaration
(#4443), JS/TS file-scope callback param shadowing (#4457), sibling-file-id
symbol retention (#4452), TS class-field receiver scoping (#4297), Ollama
/api/chat num_ctx (#4414), cluster label retention on majority overlap (#4451),
and an ingest symlink test fix (#4442). AST cache schema bumped to 16.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.9.91
2026-10-14 23:09:54 +01:00
SrijanSriv a0587559ca fix(cluster): keep a community label when membership overlap stays a majority (#2298)
A one-node membership change failed the exact signature check and replaced the saved name with the hub label. Keep the name when the shared nodes are a strict majority of both the saved set and the current set.

Related to #2298

(cherry picked from commit 62e83eabff)
2026-10-14 22:53:26 +01:00
smngvlkz f48425e6e0 send ollama requests to /api/chat so num_ctx actually applies
(cherry picked from commit 5f7b2b4dc8)
2026-10-14 22:53:26 +01:00
rohit-jsfreaky 990ab1b75c fix(ts): read this.x from the caller's class and its bases only, with constructor, accessor and static fields
Two review findings on the per-class field table:

- A `this.x` the class table did not type fell back to the file-wide name table,
  where constructor parameter properties of every class in the file (and locals
  named like a field) still lived, so the cross-class leak came back through them.
  `this.x` in a method of a known class now reads only that class and its bases.
- Fields of an unnamed class (`export default class { .. }`) were dropped; they go
  to the file table again, which their class-less methods still read.

To make the class-only lookup complete, a class's fields now come from declared
fields, parameter properties, `this.x = param` / `this.x = new T()` in the
constructor, and `get x(): T` / `set x(v: T)`; a field declared on a base class is
found through `inherits` and its type is judged by the declaring file's imports;
static fields are kept apart and read only from static methods. Fields live on the
class node (`_ts_fields`, `_ts_static_fields`), forwarded on changed-files rebuilds
together with the unchanged files' import edges, so an inherited field resolves the
same as in a full build.

vs 0.9.84: TypeORM `this.x.m()` 79.6% -> 96.1% (+95 edges confirmed by the compiler,
0 wrong), NestJS 78.5% -> 81.6% (+16, 0 lost), excalidraw +10 / -1 wrong; hono, mermaid,
typescript-eslint unchanged. Changed-files rebuild == full build on NestJS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit b1c79fc726)
2026-10-14 22:53:26 +01:00
rohit-jsfreaky 5fd413a057 fix(ts): scope class-field receiver types to the field's own class
Class fields went into the per-file receiver table, so two classes in one file
with a same-named field of different types typed each other's `this.repo`
(`A.run` bound to `RepoB.save`), and a local named like a field took the field's
type. Only the first commit of #4231 shipped in 0.9.81; this is its second one.

Fields now go to a per-class table (`ts_type_table["fields"][Class][field]`), and
only a `this.x.m()` call, marked `ts_this_field` on its raw call, reads it through
the caller's own class. `const { x } = this` / `{ x: y } = this` in the enclosing
function mark `x.m()` / `y.m()` the same way, and classes are matched casefolded
like node ids (`class UsecaseVisitor` beside `const usecaseVisitor`). Every other
receiver uses the file table as before.

vs 0.9.84: excalidraw -1 wrong / +2 correct edges, TypeORM -2 (a local sharing a
field's name), NestJS, hono, mermaid, typescript-eslint unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit 1d2e2bc47e)
2026-10-14 22:53:26 +01:00
Deepjyoti-Sarmah c29950f822 fix(extract): keep a symbol that spells a sibling's old file id on its own node (#4449)
extract() renames each file's old file-level id (its path with the extension, esquema_x_sql for x.sql) to the canonical one, on every node, edge endpoint and raw call that spells it. A symbol of another file can spell it too: const sql in x.mjs, so it took the file node's id and was dropped by dedup.
The rename now follows only the file that minted the key, wherever another file's own symbol carries the same id; a file that only refers to the owner renames as before, so every key nobody contests behaves exactly as today.
Swift extension nids and Objective-C field-table keys follow the same rule; the Objective-C path has no test that reaches it.

(cherry picked from commit 2876a1ebbf)
2026-10-14 22:53:26 +01:00
berce 5faa05c852 fix(rust): only land a use rooted in this crate
The review bot is right that `use serde::Serialize;` should not land on a
local `Serialize`. Leaving out std/core/alloc was not enough: any other
crate has the same problem. A `use` is now resolved only when its path
starts at `crate`, `self` or `super`.

(cherry picked from commit ee27336a4f)
2026-10-14 22:34:17 +01:00
berce ef1d256ec8 fix(rust): land a use on the declaration it imports (#4439)
A Rust `use` targets the bare, case-folded name it imports. That reached the
declaration only when a type annotation in the corpus had left a sourceless
node under the same id for the stub rewire to remap. Otherwise the edge
dangled, and since 0.9.63 build mints the target as an external stub that
ties with the real declaration in `affected` and `explain`.

The `use` edge now carries the name as written, and the rewire is lent a
sourceless node for each imported name that has none. The lent nodes are not
kept, so an import with no unique declaration dangles exactly as before.
Standard-library paths are left out.

An incremental build re-extracts only the changed files, so the rewire also
takes the unchanged ones as candidates; without that it resolved less than a
full build.

Bumps the AST cache schema: cached edges lack the name.

(cherry picked from commit 672a2b69b1)
2026-10-14 22:34:17 +01:00
berce 1af5104b68 fix(js): shadow a file-scope callback's own params (#4448)
A closure passed in a module-level call statement is walked under the
file node (#3124), but its params and locals were never registered for
that walk. A parameter passed on as a call argument then resolved against
the corpus-wide label index and produced an indirect_call to any function
with the same name, including module-private ones in other packages.

Record the closure's bindings in closure_locals_by_body, the same way the
#2568 branch already does for const-assigned closures.

(cherry picked from commit 60cfd51b7d)
2026-10-14 22:31:57 +01:00
berce e7772b2d18 test(ingest): compare the symlink target against what readlink returned before (#4435)
On Windows readlink() returns the target as \\?\C:\..., so comparing it to
the plain sentinel path failed three parameters of
test_binary_captures_skip_existing_entries even though the capture left the
link alone. Read the target right after creating the link and compare against
that, which is what "untouched" means on every platform.

(cherry picked from commit eb027be627)
2026-10-14 22:31:57 +01:00
berce 9dfe13254f fix(mcp): don't hand git the server's stdin in _git_head (#4440)
graph_stats asks git for HEAD to report staleness. The child inherited the
MCP stdio pipe, and on Windows git blocks on it: the call sat on the 3s
timeout, or hung under Claude Code. prs.py already isolates stdin (#3318);
these two _git_head copies were missed.

(cherry picked from commit 7092f980ed)
2026-10-14 22:31:57 +01:00
safishamsiandClaude Opus 4.8 fe085ceddd docs(translations): regenerate all 31 READMEs from the current English
Re-translate every localized README to match the reorganized English
(demo and benchmarks above the docs hub, number-bearing headings,
tightened install, trimmed per-platform/project-config sections, the
one-install line). Headers and the language switcher are preserved
verbatim; code blocks, URLs, badges, brand names, and figures are kept
byte-identical; repo-relative links use the docs/translations/ relative
convention; no emojis, no em dashes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-14 13:06:45 +01:00
safishamsiandClaude Opus 4.8 041771be72 docs(translations): drop em dashes and flag emojis from all 31 READMEs
Apply the same house-style cleanup as the English README: collapse
every em-dash run to a colon and replace the flag-emoji language
switcher with plain text language names, across all translated READMEs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 23:45:50 +01:00
safishamsiandClaude Opus 4.8 3d65480648 docs(readme): surface demo and benchmarks above the docs hub, number the headings
- move the Documentation link hub below See-it-in-action, What-it-does,
  and Benchmarks, so a skimmer sees real output and numbers before a
  wall of external links
- let the outcome-bearing headings carry a true number already in the
  body: 'Benchmarks: 0.497 recall@10, 0 LLM credits' and
  'What files it handles: ~40 languages'

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 23:37:37 +01:00
safishamsiandClaude Opus 4.8 dc4457a2a8 docs(readme): tighten install and trim over-inflated sections
- one 'one install, any assistant, any OS' line (fixes the 15+/20+/22
  assistant-count mismatch to a consistent 20+)
- de-dupe the install steps: the quickstart lives in the hero; the
  Install section now leads with install methods and the auto-detect
- state the Python/uv prerequisite inline at the hero
- sell 'graphify install' auto-detect (no per-assistant setup)
- trim the four per-platform deep-dives and the Project-configuration
  cache/import prose out to docs.graphify.com

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 23:19:52 +01:00
safishamsiandClaude Opus 4.8 be10de610e docs(readme): drop em dashes and flag emojis
Replace every ' — ' descriptor dash with a colon and swap the
flag-emoji language switcher for plain text language names, to match
the house style (no emojis, no em dashes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 23:16:40 +01:00
safishamsiandClaude Opus 4.8 6996bcd338 chore(release): 0.9.90
Fourteen community pull requests: C# call-resolution and id-collision
fixes, Python/Go/dedup correctness, a hub-label sidecar, a post-merge
heal hook, and query/build fixes. The AST cache schema is bumped (now
15), so the first build after upgrading re-extracts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.9.90
2026-10-13 23:01:41 +01:00
safishamsiandClaude Opus 4.8 b4cd4e07e0 chore(cache): bump AST schema to 15 for the Go generic-receiver fix
Go generic receiver types are normalized before building the method's
parent id (#4438), so methods attach to the type node; this changes AST
extraction output and warm caches must re-extract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 22:58:20 +01:00
ayushcodes10andClaude Opus 4.8 31cc79219c feat(hooks): install a post-merge hook to heal merge-driver-resurrected symbols (#2418)
The graph.json union merge driver can resurrect a symbol deleted on one
side of a merge; nothing re-extracts that file unless it is touched
again, so the phantom node persisted. graphify hook install now also
registers a post-merge hook that runs an unscoped background rebuild,
re-deriving the graph from the merged tree and dropping the stale node.
Install/uninstall/status go through the same hook machinery as the
post-commit/post-checkout hooks.

Squashed from #3556 with authorship preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 22:57:14 +01:00
ayushcodes10andClaude Opus 4.8 97db005309 feat(watch): route hub fallback labels to a pending sidecar (#3334)
Hub-fallback community names were written straight into the tracked
.graphify_labels.json. They now go to a .graphify_labels.pending.json
sidecar (written atomically alongside the curated labels), so the
tracked file holds only curated (manual/LLM) names; promotion drops a
cid from pending and unlinks an emptied sidecar.

Squashed from #3551 with authorship preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 22:55:20 +01:00
Vibhanshu Mishra 07588ee1e7 fix(query): retain visited-node relationships in fallback traversal
Preserve relationships between already visited nodes in BFS and DFS fallback traversal.

Collect edges from the induced subgraph of selected nodes while preserving directed edges, parallel relationships, and traversal depth limits.

Add regression tests for cycles, cross-links, multiple seeds, and output boundaries.

Fixes #4314

Co-authored-by: OpenAI Codex <codex@openai.com>
(cherry picked from commit fde445e263)
2026-10-13 22:53:44 +01:00
Mpasha17 aa18a71c30 fix(build): spare hyperedge members and pre-prune semantic isolates in orphan sweep (#4399)
Review follow-up on #4429:
- The merge-time sweep now skips nodes referenced by a hyperedge in
  G.graph; degree 0 does not mean unreferenced for them, and sweeping one
  left a dangling member that to_json writes verbatim.
- On the prune path a source-less non-code, non-stub node is removed only
  when the prune itself isolated it (v8's post-rebind _isolated_before), so
  semantic nodes stranded by re-extraction survive as before.

Co-Authored-By: Grok Bot <grokbot@users.noreply.github.com>
(cherry picked from commit 685f7f47bb)
2026-10-13 22:53:44 +01:00
Mpasha17 9cbb70a41c fix(build): sweep source-less placeholders orphaned by re-extraction (#4399)
build_merge drops re-extracted sources' nodes and edges by source_file, so a
source-less placeholder (external type, namespace stub) whose last edge went
with them survived at degree 0. The #2807 sweep only ran under prune_sources,
and its 'already isolated' set was taken after the old edges were gone.

Snapshot the source-less nodes isolated in the stored graph (plus ones the
fresh chunks emit isolated) before the rebind, and run the sweep after every
merge, sparing only that snapshot. Off the prune path only code placeholders
and #2873 external stubs are collected, so semantic/curated nodes are safe.
Swept disk nodes are counted in the #3774 shrink accounting.

Fixes #4399

Co-Authored-By: Grok Bot <grokbot@users.noreply.github.com>
(cherry picked from commit bd1f0acf9b)
2026-10-13 22:53:44 +01:00
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
safishamsiandClaude Opus 4.8 7f04857f01 chore(cache): bump AST schema to 14 for the C# resolution/id batch
C# nameless / `new X()` / `foreach` receiver resolution (#4422, #4428,
#4423) and `_`/case-distinct node ids (#4416) change AST extraction
output, so warm caches must re-extract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 22:22:01 +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
safishamsiandClaude Opus 4.8 e8eb76e797 chore(release): 0.9.89
Sixteen community pull requests: cross-repo member-call resolution
(Go/PHP/TS/Kotlin/ObjC), generic/template type-param cleanup
(Kotlin/Scala/C++/Swift), an Objective-C enum extractor, interface
dispatch fan-out, a native Pi guard extension, and resolution/ingest
fixes. The AST cache schema is bumped (now 13), so the first build
after upgrading re-extracts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.9.89
2026-10-13 11:27:05 +01:00
safishamsiandClaude Opus 4.8 de08bed0fe chore(cache): bump AST schema to 13 for the generics/cross-repo batch
Kotlin/Scala/C++/Swift generic & template type-param pruning (#4406,
#4407, #4408, #4409), Objective-C NS_ENUM/NS_OPTIONS extraction and
callable markers (#4401, #3385), and Go/PHP receiver-typed member-call
parking (#3395, #3391) all change AST extraction output, so warm caches
must re-extract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 11:18:28 +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