Skip to content

Finish member calls across a repo boundary on merge (#3152) - #3222

Open
xiongjianxu wants to merge 3 commits into
Graphify-Labs:v8from
xiongjianxu:fix/3152-cross-repo-member-calls
Open

Finish member calls across a repo boundary on merge (#3152)#3222
xiongjianxu wants to merge 3 commits into
Graphify-Labs:v8from
xiongjianxu:fix/3152-cross-repo-member-calls

Conversation

@xiongjianxu

@xiongjianxu xiongjianxu commented Aug 31, 2026

Copy link
Copy Markdown

Fixes #3152.

What was wrong

A single-repo build binds obj.method() only when the receiver's type is declared in that same build. When the type lives in another repository, the resolver already has the receiver type in hand and drops the call anyway — so nothing about it reaches graph.json, the only artifact merge-graphs and global add consume. No merge-time pass can recover what was never written down.

Two repos, App.run() calling Greeter.greet() where Greeter is declared in the other repo:

one corpus  : 8 nodes, 8 edges  (.run() -> .greet() resolved)
two repos   : 8 nodes, 7 edges  (the call edge is simply gone)

What this changes

  1. Park the call at extraction (extract.py): when the receiver type resolves to zero declarations in this corpus, the resolver records {callee, receiver_type, lang, line} on the caller node under metadata.unresolved_calls instead of dropping it silently. The payload carries names only, never node ids — ids are rewritten by the id-disambiguation remap and again by repo prefixing, and a stale id inside metadata fails silently (ObjC: #2591 self.<field> receiver typing emits zero edges via the CLI — objc_field_types keys are skipped by the #1529 id remap #3150 was that bug); names survive every rewrite.

    > 1 declarations stays dropped: that is a local ambiguity, and merging only widens it.

  2. Finish the edge after the merge (cross_repo_calls.py): link_cross_repo_member_calls(merged) reads the parked entries back and emits relation="calls", context="cross_repo", confidence="INFERRED" when the receiver type resolves to exactly one declaration in another repo that owns exactly one member of that name.

  3. Hooks: merge-graphs (next to the merge-graphs: a contract type both repos declare stays two unconnected nodes #3007 same_type_as pass) and global_add.

The pass adds edges only — no node merging, no renaming — so it composes with the prefixing and pruning already in place. Every edge it adds is tagged, and it drops its own previous output before recomputing, which is what makes an incremental global add land exactly where a single merge-graphs of the same inputs does, and how a repo whose types moved stops answering for calls it no longer owns.

Languages

Parking is wired into Java, C++, C# and Swift — every resolver that already has a receiver type and the same "declared nowhere in this corpus" bail-out. Three details the non-Java ones force:

  • Swift shares all_raw_calls with every other language and its raw_calls carry no lang tag, so the parked entry's language comes from the declaring file's suffix rather than from a tag that isn't there.
  • C# collapses "absent", "ambiguous" and "scoping was decisive" into one None from _resolve_type_name_nid, and only the first is a cross-repo candidate, so the park re-checks the bare-name index instead of trusting the None.
  • C++ models an in-class declaration (void bar(); in a header) as a field carrying defines, not method, so the merge-side member index is kept per relation: a C++ entry may fall back to defines when no method of that name exists, and for every other language a defines target is a field and cannot answer a call. When a class has both — header declaration plus out-of-line definition — the definition wins.

Foo::bar() in C++ is also the shape of a namespace-qualified free function, and a Pascal-cased C# receiver may be a local rather than a type; in both cases the entry is parked by name and the merge's guards do the deciding, so the worst case is an entry nothing answers.

Guards

Each has a test, because each is what keeps a name collision from fabricating an edge:

guard why
exactly one candidate declaration the same single-definition rule the single-repo resolvers apply
candidate repo ≠ caller repo a same-repo declaration means the local resolver already refused; this pass never re-decides inside one repo
candidate's source_file suffix matches the parked lang without it a Java Greeter binds just as happily to a Python class of the same name — or to a C# one
declaration must have a source_file a sourceless stub minted for a dangling reference owns nothing
exactly one member of that name on the owner no guessing between overloads' owners
defines answers for C++ only elsewhere a defines target is a field, and a field cannot be called
known lang only an entry from an extractor not in _LANG_SUFFIXES cannot be language-checked, so it is not acted on

Verified

residue in repo-a/graph.json:
  src_appa_app_app_run -> [{"callee":"greet","receiver_type":"Greeter","lang":"java","line":"L10"}]

merge-graphs: "resolved 1 member call(s) across repos"
              Merged 2 graphs -> 8 nodes, 8 edges   (matches the one-corpus control)
              repo-a::…app_run -> repo-b::…greeter_greet  [INFERRED cross_repo]

idempotency : 2nd and 3rd pass each report 1, edge count stays 8
global add  : repo-a -> 0 ; then repo-b -> 1   (incremental agrees with the single merge)

tests/test_cross_repo_member_calls.py — 19 tests: the happy path, the guards above, run-twice-no-duplicate, a repo that stops declaring the type losing the edge, merge-graphs through the real CLI, and an end-to-end per language that extracts two repos, parks in one and merges (Java field receiver, both C++ receiver shapes, C# field receiver, Swift property receiver). The language cases skip when their tree-sitter grammar is absent.

Full suite before and after this branch: identical failure sets — 408 failed / 4563 passed on the base commit, 408 failed / 4582 passed here (+19, the new file). The 408 are pre-existing in my environment, mostly tree-sitter grammars I don't have installed (Python, Ruby, …); test_serve_http.py is excluded from both runs for an x86_64/arm64 rpds mismatch.

Not included

A single-repo build binds `obj.method()` only when the receiver's type is
declared in the same build. When the type lives in another repository the
Java resolver already holds the receiver type but drops the call, so
nothing about it reaches graph.json — the only artifact `merge-graphs` and
`global add` read — and no merge-time pass can recover what was never
written down. Merging two repos produced 7 edges where the same code in
one corpus produced 8: exactly the edges that make it a call graph.

The resolver now parks such a call on the caller node as a
`metadata.unresolved_calls` entry (callee, receiver type, language, line —
names only, never node ids, which the id remaps and the repo prefixing
rewrite), and a new pass finishes the edge after the graphs are composed.

The pass keeps the single-definition guard the single-repo resolvers use,
only crosses a repo boundary, and requires the answering declaration to be
in the same language, so a name collision fabricates nothing. It adds
edges only — no node merging or renaming — and clears its own previous
output before recomputing, which is what makes an incremental
`global add` land where a single `merge-graphs` of the same inputs does.

Parking is only wired into the Java resolver here; the pass is
language-keyed so other extractors can opt in the same way.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds cross-repo member call resolution so obj.method() edges survive a repository boundary. The Java resolver now parks unresolved calls (receiver type, callee, and language, names only) as metadata.unresolved_calls on the caller node via _park_unresolved_member_call when the receiver's type is declared nowhere in the current corpus, and the new link_cross_repo_member_calls reads them back after graphs are composed, emitting a tagged calls edge only when the type resolves to exactly one declaration in a different repo with a matching language suffix. merge-graphs and global add invoke it and report how many calls they resolved; the pass drops its own prior edges before recomputing, so incremental adds and full merges of the same inputs agree and re-runs never duplicate.

No blocking issues surfaced. 10 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1899 functions depend on the 329 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 522 callers, 43 callees
  • new: _rebuild_code() — 113 callers, 50 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • new: collect_files() — 17 callers, 6 callees
  • …and 33 more — each is listed as a finding

Verification — 1899 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1845 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify dispatch\_command.

The verifier did not have enough to check dispatch\_command, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_resolve\_java\_member\_calls.

The verifier did not have enough to check \_resolve\_java\_member\_calls, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

Could not verify: Could not verify global\_add.

The verifier did not have enough to check global\_add, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 7 grounded finding(s) anchored inline below; 34 more finding(s) on lines outside this diff (see the check run).

return by_owner


def link_cross_repo_member_calls(merged: "nx.Graph") -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionlink_cross_repo_member_calls()

fans out to 6 callees (efferent coupling); 14 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/extract.py
parked.append(entry)


def _resolve_java_member_calls(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression_resolve_java_member_calls()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
@@ -79,7 +79,8 @@ def _file_hash(path: Path) -> str:
def global_add(source_path: Path, repo_tag: str) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionglobal_add()

fans out to 9 callees (efferent coupling); 10 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
"skipped": False, "cross_repo_calls": cross_repo_calls}


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionglobal_remove()

high coupling complexity (Ca·Ce = 25).

Grounded coupling-delta finding (deterministic), not an LLM guess.

PARKED_GREET = [{"callee": "greet", "receiver_type": "Greeter", "lang": "java", "line": "L10"}]


def test_a_parked_call_binds_to_the_one_declaration_in_another_repo():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiontest_a_parked_call_binds_to_the_one_declaration_in_another_repo()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert data["source_location"] == "L10"


def test_two_repos_declaring_the_same_name_bind_nothing():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiontest_two_repos_declaring_the_same_name_bind_nothing()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert G.number_of_edges() == edges_after_first


def test_a_repo_that_stops_declaring_the_type_loses_the_edge():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiontest_a_repo_that_stops_declaring_the_type_loses_the_edge()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

pyproject sets ruff line-length = 100; the added print was 101.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds cross-repo member-call resolution so obj.method() calls whose receiver type is declared in another repository get their calls edge, which single-repo extraction can only drop. The Java resolver now parks such calls (names only, no node ids) as metadata.unresolved_calls entries via _park_unresolved_member_call, and link_cross_repo_member_calls reads them back after graphs are composed, emitting a tagged INFERRED edge only when the receiver type and callee each resolve to exactly one same-language declaration in a different repo. The pass runs during both merge-graphs and global add, clears its own prior output first so it stays idempotent across incremental adds, and reports the edge count.

No blocking issues surfaced. 9 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1899 functions depend on the 329 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 522 callers, 43 callees
  • new: _rebuild_code() — 113 callers, 50 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • new: collect_files() — 17 callers, 6 callees
  • …and 33 more — each is listed as a finding

Verification — 1899 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1845 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify dispatch\_command.

The verifier did not have enough to check dispatch\_command, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_resolve\_java\_member\_calls.

The verifier did not have enough to check \_resolve\_java\_member\_calls, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

Could not verify: Could not verify global\_add.

The verifier did not have enough to check global\_add, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 7 grounded finding(s) anchored inline below; 34 more finding(s) on lines outside this diff (see the check run).

return by_owner


def link_cross_repo_member_calls(merged: "nx.Graph") -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionlink_cross_repo_member_calls()

fans out to 6 callees (efferent coupling); 14 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/extract.py
parked.append(entry)


def _resolve_java_member_calls(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression_resolve_java_member_calls()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
@@ -79,7 +79,8 @@ def _file_hash(path: Path) -> str:
def global_add(source_path: Path, repo_tag: str) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionglobal_add()

fans out to 9 callees (efferent coupling); 10 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
"skipped": False, "cross_repo_calls": cross_repo_calls}


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionglobal_remove()

high coupling complexity (Ca·Ce = 25).

Grounded coupling-delta finding (deterministic), not an LLM guess.

PARKED_GREET = [{"callee": "greet", "receiver_type": "Greeter", "lang": "java", "line": "L10"}]


def test_a_parked_call_binds_to_the_one_declaration_in_another_repo():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiontest_a_parked_call_binds_to_the_one_declaration_in_another_repo()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert data["source_location"] == "L10"


def test_two_repos_declaring_the_same_name_bind_nothing():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiontest_two_repos_declaring_the_same_name_bind_nothing()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert G.number_of_edges() == edges_after_first


def test_a_repo_that_stops_declaring_the_type_loses_the_edge():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiontest_a_repo_that_stops_declaring_the_type_loses_the_edge()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

…3152)

The merge-time pass only had Java residue to work with, so a C++, C# or
Swift call whose receiver type lives in another repo was still dropped
with the type already in hand. Each of those three resolvers has the same
bail-out Java had — receiver typed, type declared nowhere in this corpus —
so each now parks the call by name for the merge to finish.

Two details the added languages force:

* Swift shares `all_raw_calls` with every other language and its raw_calls
  carry no `lang` tag, so the parked entry's language comes from the
  declaring file's suffix rather than from a tag that isn't there.
* C# collapses "absent", "ambiguous" and "scoping was decisive" into one
  `None` from `_resolve_type_name_nid`, and only the first is a cross-repo
  candidate, so the park re-checks the bare-name index instead of
  trusting the `None`.

On the merge side, a C++ class that only declares `void bar();` in a
header owns it through `defines`, not `method`, so the member index is
kept per relation: a C++ entry may fall back to `defines` when no
`method` of that name exists, and for every other language a `defines`
target is a field and cannot answer a call.

Tests cover a real two-repo build per language (both C++ receiver shapes),
the `defines` owner and its language restriction, the definition winning
over a same-named header declaration, and C++ not binding to a C# class
of the same name.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds a merge-time pass that finishes member calls crossing a repository boundary: single-repo resolvers now park unresolved obj.method() calls (receiver type, callee, and language, capped at 64 per node) on the caller node's metadata.unresolved_calls, and link_cross_repo_member_calls reads them back after graphs are composed to emit calls edges when the receiver type resolves to exactly one same-language declaration in another repo. The pass keeps the single-definition guard the in-repo resolvers use, adds edges only (no node merging), and clears its own prior output before recomputing so an incremental global add produces the same result as one merge-graphs of the same inputs. Wires it into dispatch_command for both merge-graphs and global add, printing a count of resolved cross-repo calls.

Worth a look

  • Concurrent global_add can lose a repo updategraphify/global_graph.py:153 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Existing non-call edge suppresses cross-repo call resolutiongraphify/cross_repo_calls.py:197 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1915 functions depend on the 345 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 522 callers, 43 callees
  • new: _rebuild_code() — 113 callers, 50 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • new: link_cross_repo_member_calls() — 18 callers, 7 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • …and 36 more — each is listed as a finding

Verification — 1915 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1861 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify dispatch\_command.

The verifier did not have enough to check dispatch\_command, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_resolve\_cpp\_member\_calls.

The verifier did not have enough to check \_resolve\_cpp\_member\_calls, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

Could not verify: Could not verify \_resolve\_csharp\_member\_calls.

The verifier did not have enough to check \_resolve\_csharp\_member\_calls, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

Could not verify: Could not verify \_resolve\_java\_member\_calls.

The verifier did not have enough to check \_resolve\_java\_member\_calls, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

Could not verify: Could not verify \_resolve\_swift\_member\_calls.

The verifier did not have enough to check \_resolve\_swift\_member\_calls, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

Could not verify: Could not verify global\_add.

The verifier did not have enough to check global\_add, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 8 grounded finding(s) anchored inline below; 36 more finding(s) on lines outside this diff (see the check run).

return ("method", "defines") if lang == "cpp" else ("method",)


def link_cross_repo_member_calls(merged: "nx.Graph") -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionlink_cross_repo_member_calls()

fans out to 7 callees (efferent coupling); 18 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
@@ -79,7 +79,8 @@ def _file_hash(path: Path) -> str:
def global_add(source_path: Path, repo_tag: str) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionglobal_add()

fans out to 9 callees (efferent coupling); 10 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
"skipped": False, "cross_repo_calls": cross_repo_calls}


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionglobal_remove()

high coupling complexity (Ca·Ce = 25).

Grounded coupling-delta finding (deterministic), not an LLM guess.

PARKED_GREET = [{"callee": "greet", "receiver_type": "Greeter", "lang": "java", "line": "L10"}]


def test_a_parked_call_binds_to_the_one_declaration_in_another_repo():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiontest_a_parked_call_binds_to_the_one_declaration_in_another_repo()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert data["source_location"] == "L10"


def test_two_repos_declaring_the_same_name_bind_nothing():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiontest_two_repos_declaring_the_same_name_bind_nothing()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert link_cross_repo_member_calls(G) == 0


def test_a_cpp_header_declaration_answers_through_defines():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiontest_a_cpp_header_declaration_answers_through_defines()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert link_cross_repo_member_calls(G) == 0


def test_the_definition_answers_before_a_same_named_declaration():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiontest_the_definition_answers_before_a_same_named_declaration()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert G.number_of_edges() == edges_after_first


def test_a_repo_that_stops_declaring_the_type_loses_the_edge():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiontest_a_repo_that_stops_declaring_the_type_loses_the_edge()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

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.

merge-graphs / global add can never produce cross-repo member-call edges: the resolved receiver type is discarded at bail-out instead of persisted

1 participant