Skip to content

fix: v1.6.1 node identity, traversal depth, method values and handler recall - #278

Merged
sunerpy merged 6 commits into
mainfrom
sync/v1.6.1-alignment-2
Oct 1, 2026
Merged

sunerpy merged 6 commits into
mainfrom
sync/v1.6.1-alignment-2

Conversation

@sunerpy

@sunerpy sunerpy commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fourth PR of the v1.6.1 alignment, following #277. It lands the owner-approved node-identity change, the traversal and method-value fixes, and the remaining extraction recall rows. Dispositions and landing state are tracked in docs/upstream-sync/V1_6_1_AUDIT_2026-09-30.md; tracked parity in UPSTREAM.md stays v1.6.0 until the release gate.

Same-line node identity (#1349) — extraction version 14 → 15

A same-kind, same-name declaration on one line (a getter/setter pair, two {% render "x" %} tags) hashed to the first one's node id and silently replaced it. Within one file's extraction the first keeps its id and every later one at a different column appends :{column} in UTF-16 code units, as upstream's NodeIdAllocator does; a declaration that collides with nothing keeps exactly the id it had. The owner approved this change to the node-id invariant on 2026-10-01; AGENTS.md and docs/equivalence.md record the suffix. codegraph node <suffixed-id> resolves the later declaration.

Traversal (#1974)

callers, callees and impact re-expand a node reached nearer than before, so dependents within the depth limit are no longer lost to edge order (impact from depth 2, callers/callees from depth 3). Each (node, edge kind) is reported once per walk.

Method values (#1820)

Python and Go member values keep their whole receiver path (self.store.fetch, c.store.Fetch, Actual.fetch) and resolve through the receiver's scope — the enclosing class and its bases, a field's annotated/constructed/parameter type, a local's type, an import, a Go struct field's in-module type, a method expression — before a project-unique name. Data attributes, @property members, test doubles, abstract bases and unknowable receivers stay unlinked; the capitalization heuristic no longer guesses method values.

Recall

  • Default-exported namespace objects (F13): const Api = { upload }; export default Api — a default import finds the binding the export default statement names, and Api.upload() resolves through the literal's own bindings.
  • Handler values (F13): JSX attribute expressions (onPress={handleSubmit}) and object shorthand members (a hook's return { handleApprove }) are function values.
  • Relationships (#1839 CLI, #774/#804): callers/callees follow instantiates edges as upstream has since June; each node in the JSON carries relationships, and text output marks [instantiates] / [references].

Golden impact

  • Extraction goldens: all 19 re-indexable corpora regenerated with this build are byte-identical (no colliding same-line declarations exist in them).
  • MCP structural fixtures: three move with the instantiates edge and nothing else — runDemo's callees gain Counter, Counter's trail and blast radius gain runDemo, and explore's symbol count and file-header order follow.

Verification

  • Pre-push gate (fmt, clippy -D warnings, full test suite, guardrail) passed on the pushed head; docs-check, oxfmt --check and scripts/guardrail.sh clean.
  • The last commit is documentation only (landing references in the v1.6.1 audit); checked with oxfmt --check and docs-check.
  • Every new suite was run against the previous sources first and fails there (negative controls excepted): node_identity, the same-line accessor persistence case, the #1974 traversal cases, method_values, the F13 extraction/resolution cases, and the CLI relationships case.
  • Three existing tests that pinned the replaced behavior were updated deliberately: the Python method-value representation (this.x → self.x, v1.6.1 #1820) in two unit tests and one coverage test, and callees(runDemo) now including the instantiated Counter (upstream #774/#804).

BEGIN_COMMIT_OVERRIDE
fix(extract): keep same-line declarations that share a node id
fix(graph): re-expand a node reached nearer than before in depth-limited walks
fix(resolve): scope Python and Go method values by their receiver
feat(resolve): resolve default-exported namespace objects and JSX handler values
feat(cli): report how each caller or callee relates to the symbol
END_COMMIT_OVERRIDE

🤖 Generated with Claude Code

CodeGraph Test added 6 commits October 1, 2026 08:15
A same-kind, same-name declaration on one line — a getter/setter pair,
two {% render "x" %} tags — hashed to the node id of the first and
silently replaced it in the store. Within one file's extraction the first
declaration now keeps its id and every later one at a different column
appends :{column}, its zero-based UTF-16 column, as upstream v1.6.1 does
(#1349); revisiting a declaration yields the same id, so a declaration
that collides with nothing keeps exactly the id it had. Applied to the
tree-sitter walker (CFML included) and the Liquid extractor; the formula
in AGENTS.md and docs/equivalence.md records the suffix. Extraction
version 15.
…ted walks

callers, callees and impact are depth-limited DFS walks. A node first
expanded through a longer path was never expanded again when a nearer
path reached it, so its own dependents within the limit were lost to edge
order — impact at depth 2 already, callers and callees from depth 3. The
walks now record the shallowest depth each node was expanded at and expand
it again from a nearer depth (upstream #1974). Each (node, edge kind) is
reported once per walk, keeping multi-kind rows while no longer repeating
a node once per parent, and impact records a node's edges on its first
expansion only.
pool.submit(self.store.fetch) and Submit(c.store.Fetch) pass a method as a
value. The extractor now keeps the whole receiver path (self.store.fetch,
c.store.Fetch, Actual.fetch) instead of a last segment or a this. marker,
and resolution follows upstream v1.6.1 #1820: self/cls resolve on the
enclosing class and its bases, a field on its annotated, constructed or
parameter-assigned type, a local on its annotation or constructor, an
imported class through the import, a Go receiver on its declared type or a
struct field's in-module type, and a method expression on its type; a
base-typed field reaches only that base's descendants. Otherwise a method
name binds only when it is unique across the project — test doubles and
abstract bases count. Data attributes, @Property members, ambiguity and
unknowable receivers stay unlinked, and the capitalization heuristic is no
longer used for method values.
…dler values

const UploadApi = { uploadARCapture, makeFolder: createFolder };
export default UploadApi made every UploadApi.member() call from another
file land on a guessed first exported function, because the constant is
not exported at its declaration. A default import now finds the binding the
export default statement names — a namespace object included — after a
component file and before the first-exported-function guess, and the
member resolves through the literal's own bindings.

TS/JS function values also cover JSX attribute expressions
(onPress={handleSubmit}) and object shorthand members (a hook's
return { handleApprove }), so the handler a component or hook hands out has
an edge (upstream F13, 873f133).
callers and callees now follow instantiates edges, as upstream has since
#774/#804: a function that constructs a class is that class's caller, and
the class is its callee. Each node in the callers/callees JSON — per
definition and in the union list — carries relationships, the distinct
edge kinds that link it to the queried symbol, and text output marks a
non-call link as [instantiates] or [references] (upstream #1839).

Three structural MCP fixtures move with the instantiation edge and nothing
else: runDemo's callees gain Counter, Counter's trail and blast radius gain
runDemo, and explore's symbol count and file-header order follow.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 78c4e02a06

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +178 to +184
// An unknown receiver keeps the unique-or-drop discipline across ALL
// files; test doubles and abstract bases are candidates too.
unique_callable(
context.get_nodes_by_name_shared(member),
reference,
context,
0.8,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Refuse unique-name fallback for unknown receivers

When a Python or Go receiver has no inferred type, this fallback resolves obj.fetch to the sole project method named fetch, even though there is no evidence that obj has that type. For example, def register(obj, pool): pool.submit(obj.fetch) creates a false reference to an unrelated Store.fetch whenever it is the only such method; the previous receiver-overlap path rejected this shape. Return None when receiver inference fails rather than treating global name uniqueness as receiver evidence.

AGENTS.md reference: AGENTS.md:L59-L61

Useful? React with 👍 / 👎.

Comment on lines +32 to +35
#[derive(Debug, Default)]
pub struct NodeIdAllocator {
first_columns: HashMap<String, u32>,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply the collision allocator to custom extractors

The allocator is wired into the generic tree-sitter walker and Liquid only, while custom extractors such as Vue and MyBatis still call generate_node_id/default_node directly. Thus a valid Vue script containing function x(){} function x(){} on one line still emits two identical IDs and one declaration is overwritten, despite extraction version 15 advertising the new collision behavior. Route every per-file declaration path through a shared allocator.

AGENTS.md reference: AGENTS.md:L46-L52

Useful? React with 👍 / 👎.

Comment on lines +445 to +449
let shadow = Regex::new(&format!(
r"(?m)^\s*(?:(?:self|cls)\.)?{}\s*(?:=|:)",
regex::escape(member)
));
if shadow.is_ok_and(|pattern| pattern.is_match(&body)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore bare locals when detecting member shadowing

Because the self|cls prefix is optional and the regex scans the entire class body, an unrelated local such as fetch = build_value() inside any method is treated as a class/instance assignment. Consequently, otherwise valid callbacks such as pool.submit(self.fetch) are left unresolved even when the class defines def fetch(...). Bare assignments should count only at class scope; assignments inside methods should require an explicit self. or cls. receiver.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Oct 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.50000% with 66 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/codegraph-resolve/src/member_value.rs 89.32% 54 Missing ⚠️
crates/codegraph-graph/src/graph/mod.rs 93.00% 7 Missing ⚠️
crates/codegraph-extract/src/function_ref.rs 93.18% 3 Missing ⚠️
crates/codegraph-resolve/src/import_resolver.rs 89.47% 2 Missing ⚠️

❌ Your patch check has failed because the patch coverage (92.50%) is below the target coverage (95.00%). You can increase the patch coverage or adjust the target coverage.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #278      +/-   ##
==========================================
- Coverage   95.20%   95.17%   -0.03%     
==========================================
  Files         153      154       +1     
  Lines       92926    93662     +736     
==========================================
+ Hits        88472    89147     +675     
- Misses       4454     4515      +61     
Files with missing lines Coverage Δ
crates/codegraph-cli/src/main.rs 90.99% <100.00%> (+0.05%) ⬆️
crates/codegraph-core/src/node_id.rs 100.00% <100.00%> (ø)
crates/codegraph-extract/src/embedded/liquid.rs 99.09% <100.00%> (+0.11%) ⬆️
crates/codegraph-extract/src/walker.rs 93.09% <100.00%> (+<0.01%) ⬆️
crates/codegraph-resolve/src/name_matcher.rs 96.77% <100.00%> (+<0.01%) ⬆️
crates/codegraph-resolve/src/resolver.rs 96.53% <100.00%> (+<0.01%) ⬆️
crates/codegraph-resolve/src/source_facts.rs 100.00% <100.00%> (+0.86%) ⬆️
crates/codegraph-store/src/index_state.rs 83.33% <ø> (ø)
crates/codegraph-resolve/src/import_resolver.rs 96.72% <89.47%> (-0.06%) ⬇️
crates/codegraph-extract/src/function_ref.rs 91.58% <93.18%> (+<0.01%) ⬆️
... and 2 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sunerpy
sunerpy merged commit 227f1d2 into main Oct 1, 2026
9 checks passed
@sunerpy
sunerpy deleted the sync/v1.6.1-alignment-2 branch October 1, 2026 07:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant