Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion graphify/cross_repo_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ def link_cross_repo_member_calls(merged: "nx.Graph") -> int:
relation="calls",
context="cross_repo",
confidence="INFERRED",
confidence_score=0.8,
confidence_score=0.85,
source_file=str(caller_data.get("source_file") or ""),
source_location=entry.get("line"),
weight=1.0,
Expand Down
2 changes: 1 addition & 1 deletion graphify/cross_repo_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def link_shared_type_declarations(merged: "nx.Graph") -> int:
relation=SHARED_TYPE_RELATION,
context="cross_repo",
confidence="INFERRED",
confidence_score=0.9,
confidence_score=0.85,
source_file=str(merged.nodes[left].get("source_file") or ""),
weight=1.0,
_src=left,
Expand Down
12 changes: 6 additions & 6 deletions graphify/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -3923,7 +3923,7 @@ def _key(label: str) -> str:
"relation": relation,
"context": "call",
"confidence": "EXTRACTED" if type_qualified else "INFERRED",
"confidence_score": 1.0 if type_qualified else 0.8,
"confidence_score": 1.0 if type_qualified else 0.85,
"source_file": rc.get("source_file", ""),
"source_location": rc.get("source_location"),
"weight": 1.0,
Expand Down Expand Up @@ -4215,7 +4215,7 @@ def _key(label: str) -> str:
"relation": "calls",
"context": "call",
"confidence": "EXTRACTED" if type_qualified else "INFERRED",
"confidence_score": 1.0 if type_qualified else 0.8,
"confidence_score": 1.0 if type_qualified else 0.85,
"source_file": rc.get("source_file", ""),
"source_location": rc.get("source_location"),
"weight": 1.0,
Expand Down Expand Up @@ -4381,7 +4381,7 @@ def _key(label: str) -> str:
"relation": relation,
"context": "call",
"confidence": "EXTRACTED" if type_qualified else "INFERRED",
"confidence_score": 1.0 if type_qualified else 0.8,
"confidence_score": 1.0 if type_qualified else 0.85,
"source_file": src_file,
"source_location": rc.get("source_location"),
"weight": 1.0,
Expand Down Expand Up @@ -4603,7 +4603,7 @@ def _park_if_absent(type_name: str | None, caller_node: dict | None, rc: dict) -
"relation": "calls",
"context": "call",
"confidence": "EXTRACTED" if type_qualified else "INFERRED",
"confidence_score": 1.0 if type_qualified else 0.8,
"confidence_score": 1.0 if type_qualified else 0.85,
"source_file": src_file,
"source_location": rc.get("source_location"),
"weight": 1.0,
Expand Down Expand Up @@ -4813,7 +4813,7 @@ def _method_on_type_or_bases(type_nid: str, callee_key: str) -> str | None:
"relation": "calls",
"context": "call",
"confidence": "EXTRACTED" if exact else "INFERRED",
"confidence_score": 1.0 if exact else 0.8,
"confidence_score": 1.0 if exact else 0.85,
"source_file": raw_call.get("source_file", ""),
"source_location": raw_call.get("source_location"),
"weight": 1.0,
Expand Down Expand Up @@ -5004,7 +5004,7 @@ def _field_type_up_chain(cls, receiver):
"relation": relation,
"context": "call",
"confidence": "EXTRACTED" if type_qualified else "INFERRED",
"confidence_score": 1.0 if type_qualified else 0.8,
"confidence_score": 1.0 if type_qualified else 0.85,
"source_file": src_file,
"source_location": rc.get("source_location"),
"weight": 1.0,
Expand Down
2 changes: 1 addition & 1 deletion graphify/interface_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def resolve_interface_dispatch(
"relation": DISPATCH_RELATION,
"context": "call",
"confidence": "INFERRED",
"confidence_score": 0.9,
"confidence_score": 0.85,
"source_file": str(impl_node.get("source_file", "")),
"source_location": impl_node.get("source_location"),
"weight": 1.0,
Expand Down
1 change: 1 addition & 0 deletions tests/test_cross_repo_member_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def test_a_parked_call_binds_to_the_one_declaration_in_another_repo():
data = G.edges["a::app_run", "b::greeter_greet"]
assert data["relation"] == "calls"
assert data["confidence"] == "INFERRED"
assert data["confidence_score"] == 0.85
assert data["context"] == "cross_repo"
assert data["source_location"] == "L10"

Expand Down
1 change: 1 addition & 0 deletions tests/test_cross_repo_shared_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def test_same_namespace_and_name_across_repos_are_linked(tmp_path):
endpoints = {links[0]["source"], links[0]["target"]}
assert endpoints == {"svc_a::evt", "svc_b::evt"}
assert links[0]["confidence"] == "INFERRED"
assert links[0]["confidence_score"] == 0.85


def test_same_name_in_different_namespaces_is_not_linked(tmp_path):
Expand Down
4 changes: 4 additions & 0 deletions tests/test_csharp_interface_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ def _reachable(r, start: str) -> set[str]:
def test_single_implementer_links_the_interface_method(tmp_path):
dispatch, r = _extract(tmp_path, _INJECTED)
assert (_find(r, ".Build()", "ireport"), _find(r, ".Build()", "report_report")) in dispatch
for edge in r["edges"]:
if edge["relation"] == "dispatches_to":
assert edge["confidence"] == "INFERRED"
assert edge["confidence_score"] == 0.85


def test_chain_through_an_injected_dependency_becomes_reachable(tmp_path):
Expand Down
84 changes: 72 additions & 12 deletions tests/test_inferred_confidence_rubric.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
what the extractor knows; snapping the scores onto the documented scale fixes
the stated violation without making that call.
"""
import ast
from pathlib import Path

import pytest
Expand Down Expand Up @@ -63,19 +64,78 @@ def test_extracted_and_ambiguous_defaults_are_unchanged():
# No emission site ships an off-rubric literal
# ---------------------------------------------------------------------------

@pytest.mark.parametrize("rel_path", [
"extract.py",
"symbol_resolution.py",
"extractors/engine.py",
"extractors/resolution.py",
def _literal_scores(expr):
"""Read only emitted literals, including both branches of a ternary.

Calls and lookups are consumers of scores, not literal emission sites.
Parsing Python also avoids matching examples in strings or comments.
"""
if isinstance(expr, ast.Constant) and type(expr.value) in (int, float):
yield expr.value
elif isinstance(expr, ast.IfExp):
yield from _literal_scores(expr.body)
yield from _literal_scores(expr.orelse)


def _emitted_score_exprs(tree):
for node in ast.walk(tree):
if isinstance(node, ast.Dict):
for key, value in zip(node.keys, node.values):
if isinstance(key, ast.Constant) and key.value == "confidence_score":
yield value
elif isinstance(node, ast.keyword) and node.arg == "confidence_score":
yield node.value
elif isinstance(node, ast.Assign) and any(
isinstance(target, ast.Name) and target.id == "confidence_score"
for target in node.targets
):
yield node.value
elif isinstance(node, ast.AnnAssign) and (
isinstance(node.target, ast.Name) and node.target.id == "confidence_score"
) and node.value is not None:
yield node.value


def test_no_module_hardcodes_an_off_rubric_score():
"""Cover every Python module and spelling, not a fixed list of resolvers.

EXTRACTED/1.0 and AMBIGUOUS/0.2 are valid too. Keep the separate
runtime checks below to verify that INFERRED edges use the INFERRED set.
"""
allowed = RUBRIC | {1.0, 0.2}
violations = []
for path in sorted(SRC.rglob("*.py")):
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
for expr in _emitted_score_exprs(tree):
for score in _literal_scores(expr):
if score not in allowed:
violations.append(f"{path.relative_to(SRC)}:{expr.lineno}: {score}")
assert not violations, "Off-rubric emitted scores:\n" + "\n".join(violations)


@pytest.mark.parametrize("source", [
'{"confidence_score": 0.8}',
'emit(confidence_score=0.8)',
'confidence_score = 0.8',
'confidence_score: float = 0.8',
'{"confidence_score": 1.0 if exact else 0.8}',
])
def test_no_module_hardcodes_an_off_rubric_inferred_score(rel_path):
"""0.8 was the value in the tree and is not on the scale. Catch it and the
forbidden 0.5 as literals, so a future edit cannot reintroduce either."""
text = (SRC / rel_path).read_text(encoding="utf-8")
for forbidden in ('"confidence_score": 0.8,', '"confidence_score": 0.5,',
"confidence_score = 0.8\n", "confidence_score = 0.5\n"):
assert forbidden not in text, f"{rel_path} still emits {forbidden.strip()}"
def test_emission_guard_recognizes_all_literal_spellings(source):
scores = [
score
for expr in _emitted_score_exprs(ast.parse(source))
for score in _literal_scores(expr)
]
assert 0.8 in scores


def test_emission_guard_ignores_comments_strings_and_score_consumers():
tree = ast.parse(
'# "confidence_score": 0.8\n'
'example = \'{"confidence_score": 0.8}\'\n'
'score = edge.get("confidence_score", 0.5)\n'
)
assert list(_emitted_score_exprs(tree)) == []


# ---------------------------------------------------------------------------
Expand Down
6 changes: 3 additions & 3 deletions tests/test_swift_cross_file_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def test_swift_cross_file_member_calls_have_correct_confidence_and_resolve(tmp_p
if e.get("relation") != "calls":
continue
if tgt_label in inferred_targets:
assert e["confidence"] == "INFERRED" and e["confidence_score"] == 0.8
assert e["confidence"] == "INFERRED" and e["confidence_score"] == 0.85
assert e["target"] in node_ids and src_by_id.get(e["target"])
seen_inferred.add(tgt_label)
elif tgt_label in extracted_targets:
Expand Down Expand Up @@ -295,7 +295,7 @@ def test_environment_attribute_typed_receiver_resolves(tmp_path: Path):
and _label(result, e["target"]) == ".reset()"), None)
assert edge is not None, "store.reset() must resolve to Store.reset"
assert _label(result, edge["source"]) == ".go()"
assert edge["confidence"] == "INFERRED" and edge["confidence_score"] == 0.8
assert edge["confidence"] == "INFERRED" and edge["confidence_score"] == 0.85


def test_environment_keypath_and_dotted_forms_are_skipped(tmp_path: Path):
Expand Down Expand Up @@ -368,7 +368,7 @@ def test_factory_returned_receiver_resolves(tmp_path: Path):
assert (".local()", "calls", ".go()") in calls # method-local receiver
for e in result["edges"]:
if e.get("relation") == "calls" and _label(result, e["target"]) == ".go()":
assert e["confidence"] == "INFERRED" and e["confidence_score"] == 0.8
assert e["confidence"] == "INFERRED" and e["confidence_score"] == 0.85


def test_factory_receiver_resolves_through_cross_file_extension(tmp_path: Path):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_swift_protocol_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def test_dispatch_edge_shape(tmp_path):
_, r = _extract(tmp_path, _INJECTED)
edge = next(e for e in r["edges"] if e["relation"] == "dispatches_to")
assert edge["confidence"] == "INFERRED"
assert edge["confidence_score"] == 0.9
assert edge["confidence_score"] == 0.85
assert edge["context"] == "call"
assert edge["source_file"].endswith("RemoteStore.swift")

Expand Down
Loading