feat(affected): surface per-edge confidence in affected output (#2352) - #2431
feat(affected): surface per-edge confidence in affected output (#2352)#2431Mustaqeem66 wants to merge 1 commit into
Conversation
…Labs#2352) `affected` walks edges in reverse and renders each hit as `- label [relation] file:line`, dropping the `confidence` field that every edge is required to carry. An EXTRACTED call and an INFERRED guess rendered identically, so a blast radius could not be triaged without re-reading graph.json by hand. Carry `confidence`/`confidence_score` off the same edge dict that already supplies `relation` and the call-site location, so all three always describe the edge actually traversed. The text view now renders `- run() [calls, EXTRACTED] cli/run.go:L165`; edges with no recorded confidence render exactly as before rather than gaining a placeholder. Also adds `affected_records()`, the structured per-node rows behind a future `affected --json`, built from the same helper as the text view so the two cannot drift.
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR threads edge confidence data through the affected module's reverse-walk output. It adds two optional via_confidence/via_confidence_score fields to AffectedHit (populated from the traversed edge), a _coerce_score helper, and a new affected_records function that returns structured, JSON-serializable rows mirroring the text view; the text format_affected output now appends the edge's confidence next to its relation tag. The change also introduces a new test file (tests/test_affected_confidence.py) covering text rendering, confidence-source selection across parallel edges, record serialization, filter behavior, and score coercion edge cases.
No blocking issues surfaced. 1 lower-confidence candidate did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 186 functions depend on the 35 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
resolve_seed()— 11 callers, 3 callees - worse:
format_affected()— 6 callers, 4 callees - new:
affected_records()— 5 callers, 4 callees
Verification — 186 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: 66 function(s) in the blast radius were not formally verified this run
· 2 grounded finding(s) anchored inline below; 1 more finding(s) on lines outside this diff (see the check run).
| return (data.get("source_file") or None, data.get("source_location") or None) | ||
|
|
||
|
|
||
| def affected_records( |
There was a problem hiding this comment.
affected_records()
high coupling complexity (Ca·Ce = 20).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return records | ||
|
|
||
|
|
||
| def format_affected( |
There was a problem hiding this comment.
format_affected()
6 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Closes #2352 (text output). See "Scope" below for the
--jsonhalf.Problem
affectedwalks edges in reverse and renders each hit as- label [relation] file:line. Every edge is required to carry aconfidence(validate.REQUIRED_EDGE_FIELDS, one ofEXTRACTED/INFERRED/AMBIGUOUS), but the reverse walk dropped it on the floor.The result: a high-confidence extracted call and a speculative inferred edge render identically. On a wide blast radius you cannot tell which hits are facts and which are guesses without opening
graph.jsonand matching edges up by hand — which defeats the point of the command.Change
affected_nodes()now readsconfidenceandconfidence_scorefrom the same edge dict that already suppliesrelationand the call-site location. That matters: a node can be reached by more than one edge, so the confidence reported has to belong to the edge whose relation actually passed the filter — not whichever edge happened to be stored first. Piggy-backing on the existing lookup makes that invariant structural rather than something a future edit can quietly break.Two deliberate calls:
- X() [calls] app.py:L4) rather than gaining anUNKNOWNplaceholder. Hand-built and older graphs stay clean, and the tag always means something when it appears.confidence_scoreis coerced defensively.boolis rejected explicitly, because bools are ints in Python and a naivefloat()would turn a mis-typed flag into a1.0score. Unparseable values degrade toNoneinstead of raising mid-walk; numeric strings are still read.I also added
affected_records()— the structured per-node rows (id,label,depth,relation,confidence,confidence_score,source_file,source_location). It shares the_hit_source()helper with the text renderer, so the two views resolve the call-site-vs-def-line fallback identically and cannot drift.Scope
Issue #2352 asks for two things. This PR fully delivers the first (inline confidence in text output) and provides the complete data layer for the second.
I stopped short of adding the
affected --jsonflag itself because the argument parsing lives incli.py, and I did not want to fold an unrelated CLI-surface change into a change that is otherwise contained inaffected.pyand reviewable on its own. Withaffected_records()in place the flag is a small, mechanical follow-up. Happy to add it to this PR if you'd rather land the feature in one go — just say the word and I'll push it.Compatibility
AffectedHit's new fields default toNoneand are appended after the existing ones, so positional construction in existing callers and tests is unaffected.[...]tag (tests/test_affected_cli.pyuses substring assertions likeassert "calls" in out), so those continue to pass.#BUG1call-site behaviour is untouched: hits still report the relation SITE when the edge stored one, and honestly fall back to the node's own definition line when it did not.Tests
New
tests/test_affected_confidence.py(9 cases) covering: inline tagging ofEXTRACTED/INFERRED; unchanged rendering for confidence-less edges; confidence resolving from the correct edge when parallel edges join the same pair (viaMultiDiGraph);AffectedHitdefaults; record shape and JSON round-trip; relation/depth filter behaviour; empty result for an unresolvable seed; and score coercion including theTrue-is-an-int trap.