Skip to content

fix(python): resolve absolute package import targets - #3729

Closed
Ha1baraA11 wants to merge 4 commits into
Graphify-Labs:v8from
Ha1baraA11:codex/fix-python-package-imports
Closed

Ha1baraA11 wants to merge 4 commits into
Graphify-Labs:v8from
Ha1baraA11:codex/fix-python-package-imports

Conversation

@Ha1baraA11

@Ha1baraA11 Ha1baraA11 commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #3723 by resolving absolute Python package imports to the package or module file that exists on disk. The extractor now probes non-package ancestors of the importing file, preserves the existing dotted-name fallback for external imports, and avoids turning same-basename external imports into self-loops.

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Tests or CI
  • Refactor
  • Security fix

How was this tested?

This was tested with the following commands:

uv run --extra openai pytest tests/ -q --tb=short
5736 passed, 96 skipped, 3 warnings

uv run --extra openai ruff check graphify/extract.py tests/test_python_import_resolution.py
uv run --extra openai python -m tools.skillgen --check
uv run --extra openai python -m tools.skillgen --audit-coverage
graphify update .
git diff --check

The regression test reproduces both package targets and confirms that pkg and pkg_sub dangling ids are replaced by pkg_init and pkg_sub_init.

Graphify-specific checklist

  • I added or updated tests for behavior changes.
  • I updated documentation or confirmed that no documentation is needed.
  • I updated generated skill artifacts when changing their source fragments.
  • I considered compatibility across supported Python versions.
  • I confirmed that no API keys, generated graph data, or local-only files are included.

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


Graphify review — findings

Resolves absolute Python imports at extraction time by probing the filesystem the same way relative imports already do, so from pkg.sub import thing produces a file-level edge to the real pkg_sub_init node instead of a dangling pkg_sub dotted-name slug (#3723). Searches upward from the importer through non-package ancestors and guards against turning a same-basename external import into a self-loop, falling back to the dotted-name id only when no module file resolves.

Worth a look

  • target_path may reference an unbound/carried-over value from prior loop iterations — graphify/extract.py:515 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Absolute import probing may bind to unrelated files sharing the module basename — graphify/extract.py:494 · 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 — 2068 functions depend on the 259 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 656 callers, 45 callees
  • new: _rebuild_code() — 142 callers, 54 callees
  • new: extract_js() — 87 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 125 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • new: collect_files() — 17 callers, 6 callees
  • …and 30 more — each is listed as a finding

Verification — 2068 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: 1893 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

121 of 290 test file(s) selected (42%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_astro_import_ids.py — impact
  • tests/test_build.py — impact
  • tests/test_builtin_global_type_refs.py — impact
  • tests/test_case_sensitive_resolution.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_cpp_objc_cross_file_calls.py — impact
  • tests/test_cross_extension_reexport_self_cycle.py — impact
  • tests/test_cross_language_call_resolution.py — impact
  • tests/test_cross_repo_external_call_guards.py — impact
  • tests/test_cross_repo_member_calls.py — impact
  • tests/test_csharp_call_site_generic_args.py — impact
  • tests/test_csharp_enum_members.py — impact
  • tests/test_csharp_field_generic_args.py — impact
  • tests/test_csharp_generic_callsites.py — impact
  • tests/test_csharp_interface_dispatch.py — impact
  • tests/test_csharp_member_calls.py — impact
  • tests/test_csharp_member_nodes.py — impact
  • tests/test_csharp_object_creation.py — impact
  • tests/test_csharp_partial_classes.py — impact
  • tests/test_csharp_type_resolution.py — impact
  • tests/test_definition_file_portability.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_duplicate_annotation_edges.py — impact
  • tests/test_elixir_import_resolution.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cache_location.py — impact
  • tests/test_file_label_disambiguation.py — impact
  • tests/test_file_node_id_spec.py — impact
  • tests/test_forwarding_review_findings.py — impact
  • tests/test_go_builtin_call_targets.py — impact
  • tests/test_go_interface_methods.py — impact
  • tests/test_go_qualified_resolution.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_import_self_loops.py — impact
  • tests/test_imported_export_forwarding.py — impact
  • tests/test_incremental.py — impact
  • tests/test_indirect_call_arrow_single_param_shadow.py — impact
  • tests/test_indirect_call_block_scoped_shadow.py — impact
  • tests/test_indirect_call_catch_binding_shadow.py — impact
  • tests/test_indirect_call_external_import_shadow.py — impact
  • tests/test_indirect_call_for_of_binding_shadow.py — impact
  • tests/test_indirect_call_function_expression_shadow.py — impact
  • tests/test_indirect_call_nested_closure_shadow.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_inferred_confidence_rubric.py — impact
  • … and 71 more

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

· 38 more finding(s) on lines outside this diff (see the check run).

@safishamsi

Copy link
Copy Markdown
Collaborator

Thanks @Ha1baraA11 — the direction is right (resolve absolute package imports at the scan root; the target_file stamp riding the existing canonicalization is the correct pattern, and it composes cleanly with the #2873 external-stub minting). A few things to sort before this can land:

  1. Confine the upward walk to the scan root. Right now it stat-probes ancestors all the way up to / for every absolute from a.b import c, unlike _resolve_python_module_path, which stops at the scan root. That's needless I/O per file plus a latent out-of-corpus false-match (currently only defanged by the ext_ remap). This is the blocking item.
  2. Plain import pkg.sub has the same bug — only from … import was fixed. Either give it the same treatment or note the deferral.
  3. Ideally reuse _resolve_python_module_path / _resolve_python_namespace_dir rather than re-implementing a subset — the existing helpers already encode the root guard and PEP 420 namespace handling this copy is missing.

Not far off — mostly consolidating onto the existing resolver. Happy to re-review once the walk is bounded.

@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 4 advisory finding(s) below merit a look before merge.

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


Graphify review — findings

Resolves Python absolute imports through the shared module resolver at extraction time so import edges stamp the real target file, including files importable from a nested src/-style root; _import_python now takes a scan_root and _safe_extract threads it into extract_python. Adds a corpus-wide ambiguity guard (_python_absolute_import_alias_files + _suppress_ambiguous_python_imports) that, when one absolute module name maps to multiple scanned files, repoints the import edge to a stable node-less id so the graph builder drops it rather than binding to an arbitrary same-named package, and flags dependent calls (including *-import call sites) so symbol resolution leaves them dangling. A unique hit under the scan root stays authoritative even when a nested independently-importable tree shares the name, keeping legitimate imports resolved while only genuinely ambiguous ones fall closed.

Worth a look

  • Early return in continuation-processing aborts remaining imports on the node — graphify/extractors/resolution.py:2563 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • _python_import_bindings dotted_name local binding uses last segment instead of first — graphify/extract.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Parenthesized Python from-import bindings are ignored — graphify/extract.py:599 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Global XAML extract root races across concurrent extractions — graphify/extract.py:6810 · 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 — 2602 functions depend on the 679 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 665 callers, 47 callees
  • new: _rebuild_code() — 142 callers, 54 callees
  • new: _extract_generic() — 18 callers, 29 callees
  • new: extract_js() — 87 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: _resolve_js_module_path() — 34 callers, 9 callees
  • new: dispatch_command() — 2 callers, 125 callees
  • new: extract_objc() — 27 callers, 9 callees
  • …and 51 more — each is listed as a finding

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

Health delta baseline: last indexed commit a5957aa (diverged from this PR's base — delta is approximate).

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: 2427 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

125 of 290 test file(s) selected (43%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_astro_import_ids.py — impact
  • tests/test_build.py — impact
  • tests/test_builtin_global_type_refs.py — impact
  • tests/test_case_sensitive_resolution.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_cpp_objc_cross_file_calls.py — impact
  • tests/test_cross_extension_reexport_self_cycle.py — impact
  • tests/test_cross_language_call_resolution.py — impact
  • tests/test_cross_repo_external_call_guards.py — impact
  • tests/test_cross_repo_member_calls.py — impact
  • tests/test_csharp_call_site_generic_args.py — impact
  • tests/test_csharp_enum_members.py — impact
  • tests/test_csharp_field_generic_args.py — impact
  • tests/test_csharp_generic_callsites.py — impact
  • tests/test_csharp_interface_dispatch.py — impact
  • tests/test_csharp_member_calls.py — impact
  • tests/test_csharp_member_nodes.py — impact
  • tests/test_csharp_object_creation.py — impact
  • tests/test_csharp_partial_classes.py — impact
  • tests/test_csharp_type_resolution.py — impact
  • tests/test_definition_file_portability.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_duplicate_annotation_edges.py — impact
  • tests/test_elixir_import_resolution.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cache_location.py — impact
  • tests/test_file_label_disambiguation.py — impact
  • tests/test_file_node_id_spec.py — impact
  • tests/test_forwarding_review_findings.py — impact
  • tests/test_go_builtin_call_targets.py — impact
  • tests/test_go_interface_methods.py — impact
  • tests/test_go_qualified_resolution.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_import_self_loops.py — impact
  • tests/test_imported_export_forwarding.py — impact
  • tests/test_incremental.py — impact
  • tests/test_indirect_call_arrow_single_param_shadow.py — impact
  • tests/test_indirect_call_block_scoped_shadow.py — impact
  • tests/test_indirect_call_catch_binding_shadow.py — impact
  • tests/test_indirect_call_external_import_shadow.py — impact
  • tests/test_indirect_call_for_of_binding_shadow.py — impact
  • tests/test_indirect_call_function_expression_shadow.py — impact
  • tests/test_indirect_call_nested_closure_shadow.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_inferred_confidence_rubric.py — impact
  • … and 75 more

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify extract.

The verifier did not have enough to check extract, 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: no capturable inputs from the test suite; property tier: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_python.

The verifier did not have enough to check extract\_python, 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: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_import\_python.

The verifier did not have enough to check \_import\_python, 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: no capturable inputs from the test suite; property tier: parameter `scan_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

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

The verifier did not have enough to check \_resolve\_python\_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: no capturable inputs from the test suite; property tier: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

Could not verify: Could not verify \_safe\_extract.

The verifier did not have enough to check \_safe\_extract, 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: no capturable inputs from the test suite; property tier: parameter `extractor` is annotated `Callable` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_safe\_extract\_with\_xaml\_root.

The verifier did not have enough to check \_safe\_extract\_with\_xaml\_root, 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: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, 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: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_augment\_symbol\_resolution\_edges.

The verifier did not have enough to check \_augment\_symbol\_resolution\_edges, 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: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_collect\_python\_symbol\_resolution\_facts.

The verifier did not have enough to check \_collect\_python\_symbol\_resolution\_facts, 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: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_resolve\_cross\_file\_imports.

The verifier did not have enough to check \_resolve\_cross\_file\_imports, 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: no capturable inputs from the test suite; property tier: non-vacuity: domain too small (only 2 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

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

Comment thread graphify/extract.py
return bindings


def _import_python(

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 — _import_python()

fans out to 6 callees (efferent coupling).

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

@@ -3459,7 +3459,8 @@ def _lua_is_require_call(node, source: bytes) -> bool:


def _extract_generic(

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 — _extract_generic()

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

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

@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 4 advisory finding(s) below merit a look before merge.

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


Graphify review — findings

Adds fail-closed handling for ambiguous Python absolute imports: when the same module name maps to more than one scanned file, _suppress_ambiguous_python_imports leaves the import edge dangling on a stable node-less id and flags dependent call edges rather than binding to an arbitrary same-named package. Carves out an exception for a bare import helper resolved to a concrete same-directory sibling in the importer's own non-package directory, treating that directory-local target as unambiguous via _is_resolved_loose_sibling. Wires extract_python to receive the scan_root so imports are probed against every in-root, non-package ancestor the shared resolver would use (_python_absolute_import_alias_files, _python_import_bindings).

Worth a look

  • Python import_handler hook now receives an extra positional argument — graphify/extractors/engine.py:3711 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Context source_file can escape scan root — graphify/extract.py:7194 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Dotted Python imports record the wrong local binding — graphify/extract.py:650 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • XAML extraction root stored in process-global state races across concurrent extractions — graphify/extract.py:6852 · 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 — 2613 functions depend on the 696 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 665 callers, 47 callees
  • new: _rebuild_code() — 142 callers, 54 callees
  • new: _extract_generic() — 18 callers, 29 callees
  • new: extract_js() — 87 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: _resolve_js_module_path() — 34 callers, 9 callees
  • new: dispatch_command() — 2 callers, 125 callees
  • new: extract_objc() — 27 callers, 9 callees
  • …and 51 more — each is listed as a finding

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

Health delta baseline: last indexed commit a5957aa (diverged from this PR's base — delta is approximate).

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: 2438 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

125 of 290 test file(s) selected (43%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_astro_import_ids.py — impact
  • tests/test_build.py — impact
  • tests/test_builtin_global_type_refs.py — impact
  • tests/test_case_sensitive_resolution.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_cpp_objc_cross_file_calls.py — impact
  • tests/test_cross_extension_reexport_self_cycle.py — impact
  • tests/test_cross_language_call_resolution.py — impact
  • tests/test_cross_repo_external_call_guards.py — impact
  • tests/test_cross_repo_member_calls.py — impact
  • tests/test_csharp_call_site_generic_args.py — impact
  • tests/test_csharp_enum_members.py — impact
  • tests/test_csharp_field_generic_args.py — impact
  • tests/test_csharp_generic_callsites.py — impact
  • tests/test_csharp_interface_dispatch.py — impact
  • tests/test_csharp_member_calls.py — impact
  • tests/test_csharp_member_nodes.py — impact
  • tests/test_csharp_object_creation.py — impact
  • tests/test_csharp_partial_classes.py — impact
  • tests/test_csharp_type_resolution.py — impact
  • tests/test_definition_file_portability.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_duplicate_annotation_edges.py — impact
  • tests/test_elixir_import_resolution.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cache_location.py — impact
  • tests/test_file_label_disambiguation.py — impact
  • tests/test_file_node_id_spec.py — impact
  • tests/test_forwarding_review_findings.py — impact
  • tests/test_go_builtin_call_targets.py — impact
  • tests/test_go_interface_methods.py — impact
  • tests/test_go_qualified_resolution.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_import_self_loops.py — impact
  • tests/test_imported_export_forwarding.py — impact
  • tests/test_incremental.py — impact
  • tests/test_indirect_call_arrow_single_param_shadow.py — impact
  • tests/test_indirect_call_block_scoped_shadow.py — impact
  • tests/test_indirect_call_catch_binding_shadow.py — impact
  • tests/test_indirect_call_external_import_shadow.py — impact
  • tests/test_indirect_call_for_of_binding_shadow.py — impact
  • tests/test_indirect_call_function_expression_shadow.py — impact
  • tests/test_indirect_call_nested_closure_shadow.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_inferred_confidence_rubric.py — impact
  • … and 75 more

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify extract.

The verifier did not have enough to check extract, 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: no capturable inputs from the test suite; property tier: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_python.

The verifier did not have enough to check extract\_python, 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: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_import\_python.

The verifier did not have enough to check \_import\_python, 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: no capturable inputs from the test suite; property tier: parameter `scan_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

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

The verifier did not have enough to check \_resolve\_python\_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: no capturable inputs from the test suite; property tier: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

Could not verify: Could not verify \_safe\_extract.

The verifier did not have enough to check \_safe\_extract, 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: no capturable inputs from the test suite; property tier: parameter `extractor` is annotated `Callable` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_safe\_extract\_with\_xaml\_root.

The verifier did not have enough to check \_safe\_extract\_with\_xaml\_root, 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: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, 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: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_augment\_symbol\_resolution\_edges.

The verifier did not have enough to check \_augment\_symbol\_resolution\_edges, 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: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_collect\_python\_symbol\_resolution\_facts.

The verifier did not have enough to check \_collect\_python\_symbol\_resolution\_facts, 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: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_resolve\_cross\_file\_imports.

The verifier did not have enough to check \_resolve\_cross\_file\_imports, 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: no capturable inputs from the test suite; property tier: non-vacuity: domain too small (only 2 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

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

Comment thread graphify/extract.py
return bindings


def _import_python(

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 — _import_python()

fans out to 6 callees (efferent coupling).

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

@@ -3459,7 +3459,8 @@ def _lua_is_require_call(node, source: bytes) -> bool:


def _extract_generic(

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 — _extract_generic()

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

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

safishamsi added a commit that referenced this pull request Sep 23, 2026
…ge imports

#3729 (absolute package import resolution) and #3784 (retract phantom
package/module-collision import edges) landed in the same cut and interacted: a
legitimate `from pkg import sub` (pure package, no colliding pkg.py) now resolves
its provisional edge to the real `pkg/__init__.py` node, but #3784 retracted it —
via the __init__.py candidate id and the len==1 blind fallback — even though there
is no name collision (no phantom cycle to fix). This dropped the correct
package-dependency edge (caught by test_absolute_package_import_targets_package_init).

Tighten #3784 to retract ONLY genuine module-collision phantoms: drop the
__init__.py candidate id forms (keep the module-file `pkg`/`pkg.py` collision ids
that the #3777 fix actually targets) and remove the len==1 blind fallback (a real
phantom always matches by its module-file id, so no fallback is needed). Both the
#3729 package-import tests and all 26 #3784 phantom-cycle tests now pass together.

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

Copy link
Copy Markdown
Collaborator

Shipped in v0.9.67 (on PyPI). Cherry-picked with authorship preserved. Thanks @Ha1baraA11! Great rework in response to the review — scan-root confinement via the canonical resolver, both import forms, and the fail-closed ambiguity guard all landed. On merge I reconciled a small interaction with #3784 (it was over-retracting the legitimate package-init edge this PR now produces).

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.

Python: _import_python's absolute import arm skips the path resolution the relative arm does, so package __init__.py nodes lose their in-edges

2 participants