Skip to content

fix: affected silently returns nothing for equivalent path forms - #2707

Closed
phudayyy wants to merge 1 commit into
Graphify-Labs:v8from
phudayyy:fix/affected-path-forms
Closed

fix: affected silently returns nothing for equivalent path forms#2707
phudayyy wants to merge 1 commit into
Graphify-Labs:v8from
phudayyy:fix/affected-path-forms

Conversation

@phudayyy

Copy link
Copy Markdown
Contributor

Fixes #2706.

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 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, 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_file comparison only:

  • the label branches above keep the query verbatim, so label resolution is untouched
  • non-path queries pass through unchanged — Path("myFunc()").as_posix() is "myFunc()"
  • an absolute path rooted outside the repo is left alone rather than reduced to its basename, which would match an unrelated file of the same name

30 lines, most of them the comment explaining why.

Verification

test_affected_resolves_equivalent_path_forms covers the three spellings. Red without the change, green with it — checked by reverting only graphify/affected.py and re-running.

Full suite, both ways:

failed passed
without patch 5 4252
with patch 5 4253

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 affected against an independently built import graph (Python ast, 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.

`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.

@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. 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 rootgraphify/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 rootgraphify/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).

safishamsi added a commit that referenced this pull request Aug 13, 2026
Correctness (#2685 loop-binding shadow, #2699 provenance, #2707 affected
seed, #2688 py subpackage imports, #2602 sql grammar, #2683 cache
integrity, #2682 report basename) + Windows portability/docs batch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@safishamsi

Copy link
Copy Markdown
Collaborator

Shipped in v0.9.42 (graphifyy==0.9.42 on PyPI). Resolves a ./-relative seed. Note: an absolute-path seed still requires the working directory to be the analysed repo root — a follow-up could derive the root from the graph location. Credited in the release notes. Thanks @phudayyy!

@safishamsi safishamsi closed this Aug 13, 2026
filipechagas added a commit to lawnstarter/graphify that referenced this pull request Aug 14, 2026
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>
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.

affected silently returns nothing when the path is spelled differently (./ or absolute)

2 participants