Local import ownership
Status: implementation authorized and completed; verification results below. Base: origin/main at b0f1b01 (SARIF output merged).
Next technical-debt task
Deliver one bounded part of roadmap P0 item 3: attribute internal import edges to the scope containing the import. Keep the full module-aware resolver pending. Baseline and cycle detection still depend on that resolver.
Verified defect
Both DependencyVisitor.visit_Import and visit_ImportFrom emit an edge for every collected element in the file, including when the import is local. CodeAnalyzer supplies the visitor to the runner, which evaluates each emitted edge against layer rules; unrelated functions can therefore gain violations.
On the base commit, this source produces both first -> Target and second -> Target import edges at line 2:
def first():
from target import Target
def second():
pass
The correct import owner is first only. This probe isolates import_from dependencies; no name or call inference is involved.
Contract
- For
importandfrom ... import ...inside a function, async function, method, or class body, emit edges only from the nearest enclosing definition when that definition is collected. - An uncollected nested definition must not transfer its imports to an outer element or unrelated elements. This matches existing nested-scope handling.
- Imports under
if,try, or loops retain their enclosing definition owner. - Module-level imports retain the existing file-wide propagation policy, including imports inside module-level control-flow blocks.
- Preserve target lookup, dependency types, and import source locations. Alias resolution, relative-module resolution, and shadowing remain incomplete.
Implementation steps
- Add focused regression coverage to
tests/test_dependency_visitor.pyfor both import forms: sibling isolation, async functions, class/method ownership, collected and uncollected nested definitions, restored ownership after a nested definition, and imports under control-flow blocks. Retain the existing module-import propagation test and cover a module-level conditional import. - In
deply/utils/dependency_visitor.py, share import-source selection between the two import handlers. Reuse AST parent links installed byCodeAnalyzerto locate the nearest definition and its existing qualified-name lookup. Distinguish an uncollected definition from module scope explicitly:current_code_element is Nonealone cannot distinguish them. Do not add a second scope stack or changeCodeElement. - Add a runner regression in
tests/test_deply_runner.py: functions collected into different layers, a target forbidden only to the non-importing sibling, and a local import that must not cause a sibling violation. Also verify a forbidden importing owner still produces a violation. Exercise analysis through existing collector/rule setup and preserve reported source location. - Update
doc/technical-roadmap.mdto record this completed slice without marking P0 item 3 resolved; document local-import ownership and remaining inference limitations indoc/features.md.
Scope boundaries
No module identity redesign, new module nodes, alias/shadowing resolver, external-import rule changes, baseline, cycle detection, CLI/config changes, report-schema changes, dependencies, or collector refactoring.
The remaining P0 work needs its own plan: module identities and import targets, lexical bindings/shadowing, then explicit treatment of unresolved inference. This change fixes source ownership only and does not claim graph completeness.
Acceptance and verification
- Focused tests fail on the base commit for the incorrect extra source edges.
- They pass after the shared owner-selection fix; real owner violations remain.
- Run
./.venv/bin/python -m unittest tests.test_dependency_visitor tests.test_deply_runner, thenmake checkandgit diff --check. - Review both import handlers and their runner flow; ensure no fallback from an uncollected local scope to file-wide sources. This is a small isolated change for main-agent review, unless implementation expands beyond the stated scope.
- Regression tests reproduced extra local-import source edges and false sibling violations before the fix (24 failing subtests).
- Focused visitor/runner suite: 39 tests passed.
make check: 183 tests passed, Ruff and mypy passed, dependency audit found no known vulnerabilities. - Scoped diff and affected runner flow reviewed;
git diff --checkpassed.