fix: affected silently returns nothing for equivalent path forms - #2707
fix: affected silently returns nothing for equivalent path forms#2707phudayyy wants to merge 1 commit into
affected silently returns nothing for equivalent path forms#2707Conversation
`resolve_seed` compares the query to the stored `source_file` as a plain string,
so a file can be named three ways and only one of them resolves:
graphify affected src/x.py -> 16 results, exit 0
graphify affected ./src/x.py -> 0 results, exit 0
graphify affected /abs/src/x.py -> 0 results, exit 0
graphify affected typo.py -> 0 results, exit 0
The last two lines are the problem. A blast-radius tool answering "nothing
depends on this" is an answer people act on, and here it is indistinguishable
both from a genuine zero and from a typo — same empty list, same exit 0, no
warning. `./` is what shell completion produces and an absolute path is what any
script passes.
Found while measuring `affected` against an independently built import graph:
the probe passed absolute paths and measured recall 0.000 for every module,
which is the same failure one layer up.
Fix: normalise the query to repo-relative form for the `source_file` comparison
only. The label branches above keep the query verbatim, and non-path queries are
unaffected -- `Path("myFunc()").as_posix()` is `"myFunc()"`. An absolute path
rooted outside the repo is left alone rather than guessed at by basename, which
would match an unrelated file of the same name.
Test: `test_affected_resolves_equivalent_path_forms` is red without the change
and green with it.
Suite: 5 failed / 4252 passed without the patch, 5 failed / 4253 passed with it
-- identical failures, all in `tests/test_ollama_retry_cap.py` (no ollama in this
environment), plus the one new test.
There was a problem hiding this comment.
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. 1 change(s) tested, no difference found (not proven).
Graphify review — findings
This PR modifies resolve_seed in graphify/affected.py to normalize path-shaped seed queries into a repo-relative form via a new _as_repo_relative helper, so that queries like ./pkg/foo.py, absolute paths, and pkg/foo.py are matched against the graph's stored repo-relative source_file values. Non-path (label) queries are intended to pass through unchanged. A new test in tests/test_affected_cli.py asserts that these three equivalent path forms all resolve to the same node.
Worth a look
- Absolute path query fails when cwd is not the repo root —
graphify/affected.py:82· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- _as_repo_relative uses cwd instead of repo root, so absolute paths only resolve when cwd is the repo root —
graphify/affected.py:84· 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 — 195 functions depend on the 41 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
resolve_seed()— 11 callers, 4 callees
Verification — 195 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: 137 function(s) in the blast radius were not formally verified this run
Formal verification
No difference found (not proven): No behavior difference found in resolve\_seed (not a proof).
The verifier ran both versions of resolve\_seed on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
· 1 more finding(s) on lines outside this diff (see the check run).
|
Shipped in v0.9.42 ( |
Resolves the PR conflicts against upstream v0.9.42 (upstream 7fe58b0), refreshing the branch from the v0.9.40 state the last merge left it in. CHANGELOG.md: upstream released 0.9.41 on 2026-08-12 while this PR was open, and this PR's consolidated section claimed that same number - the same collision the last refresh resolved one version down. The section is renumbered to 0.9.43 (one patch above the higher of the two sides, per the sync convention), its AST-cache-roll bullet follows it, and upstream's 0.9.42 (unreleased), 0.9.41 (2026-08-12) and re-dated 0.9.40 (2026-08-11) sections are kept verbatim below it. pyproject.toml: 0.9.41 (ours) vs 0.9.42 (upstream) -> 0.9.43. uv.lock: regenerated for the bump. The committed lock had been stale since before the last refresh - byte-identical to the 0.9.40 base while pyproject.toml read 0.9.41 - so it did not previously appear in this PR's delta at all. `uv lock --check` passes. graphify/affected.py: two insertions at the same point in resolve_seed. The fork's sourced-stub preference (#49/#54) is kept first and upstream's repo-relative query_path (Graphify-Labs#2707) second; neither guards the other. graphify/extractors/resolution.py: upstream's prefixed-use-import alias fallback (Graphify-Labs#2661) is folded into the else-branch of the fork's target_fqn-metadata restructure (#48), firing only when that path declines and no stub label exists. Upstream Graphify-Labs#2661 also moved the `uses = uses_by_file.get(ref_file, {})` binding into the region the fork rewrote; the deletion at the moved-from site auto-merges silently, so the binding is kept explicitly above the repoint block - without it the fork's else-branch raises NameError on the first PHP repoint edge. Delta audit: `git diff --name-only upstream/v8` covers the same 31 files as the previous refresh plus uv.lock, with none dropped. Tests on the merged tree: 4672 passed / 45 skipped / 0 failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fixes #2706.
resolve_seedcompares the query to the storedsource_fileas a plain string, so a file can be named three ways and only one resolves:The last two lines are the problem: a blast-radius tool answering "nothing depends on this" is an answer people act on, and here it is indistinguishable both from a genuine zero and from a typo — same empty list, same exit 0, nothing on stderr.
./is what shell completion produces; an absolute path is what any script passes.The change
Normalise the query to repo-relative form for the
source_filecomparison only:Path("myFunc()").as_posix()is"myFunc()"30 lines, most of them the comment explaining why.
Verification
test_affected_resolves_equivalent_path_formscovers the three spellings. Red without the change, green with it — checked by reverting onlygraphify/affected.pyand re-running.Full suite, both ways:
Identical failures, all in
tests/test_ollama_retry_cap.py— no ollama in this environment — plus the one new test.How it was found
Measuring
affectedagainst an independently built import graph (Pythonast, no tree-sitter) on a ~1,400-node Python corpus. The probe passed absolute paths and measured recall 0.000 for every module, which is the same failure one layer up.