* fix(python): walk ancestors for multi-segment dotted imports (#1240)
Single-segment Python imports (`from middleware import X`) already get an
ancestor-directory walk in `resolvePythonImportInternal`, so they resolve
correctly when the importer and the imported module share a parent
directory (e.g. both under `backend/`).
Multi-segment dotted imports (`from services.sync import X`) were only
resolved against the workspace root. In a `backend/`-prefixed repo,
`from services.sync import X` from `backend/routers/cron.py` would not
resolve because `services/sync.py` does not exist at the workspace root —
only `backend/services/sync.py` does. The IMPORTS edge was dropped, the
imported names were never bound, and downstream CALLS edges to those
names were silently lost.
The fix mirrors the single-segment ancestor walk for multi-segment paths
in `resolveAbsoluteFromFiles`, and widens `hasRepoCandidate` to accept
nested `/segment/` matches so it does not bail before the walk runs.
Includes a new fixture and 5 integration tests covering:
- IMPORTS resolution for `from services.sync`, `from services.alerts`,
`from routers.alerts` from `backend/routers/cron.py`.
- CALLS edge counts for every multi-segment-imported callee.
- Regression check: single-segment ancestor walk
(`from auth_utils import …`) still resolves correctly.
The django-app-imports regression suite (which prevents `accounts.apps`
from spuriously matching a local `apps.py`) continues to pass — the new
nested-namespace check in `hasRepoCandidate` is bounded by an explicit
`/segment/` substring, and the workspace-root candidate check still
runs first.
* fix(python): scope hasRepoCandidate widening to importer ancestors + tighten ancestor-walk loop
Address review findings from PR #1241:
1. hasRepoCandidate's nested check now requires the matching directory to
sit on an ancestor of the importer. Previously any nested /SEGMENT/
path satisfied the gate, which would let a vendored copy of an
external package (e.g. vendor/django/urls.py) gate-pass an external
import like 'from django.urls import path' issued from app/main.py.
2. Loop bound in resolveAbsoluteFromFiles tightened from 'i >= 0' to
'i > 0' to skip a redundant root-candidate recheck (the workspace-root
direct check above already covers that case).
3. Doc-comment in resolveAbsoluteFromFiles now states the precedence
order explicitly: workspace root > closest ancestor > suffix fallback.
Tests added:
- Vendored-external false-positive guard (vendor/django/urls.py must not
resolve from app/main.py).
- Workspace-root vs ancestor precedence (root services/sync.py wins over
backend/services/sync.py for a backend/routers/cron.py importer).
215/215 python integration tests pass (+4 from this change). tsc --noEmit
green.