| name | polylith-migrate-analyze-imports |
| description | [Internal sub-skill of `polylith-migrate-orchestrator`. Do not load directly — load `polylith-migrate-orchestrator` first, which drives all phases.] Analyze the project's import graph to find how the original namespace is referenced, choose the namespace-rewrite strategy (shim vs shimless), detect circular imports, and list symbols exported by the original namespace. |
Skill: polylith-migrate-analyze-imports
Goal
Analyze the project's import graph to:
- Find every reference to the original namespace (in all three forms — see below).
- Choose the namespace-rewrite strategy:
SHIM_STRATEGY=shim|shimless.
- List symbols exported by the original namespace's
__init__.py.
- Detect potential circular imports.
This drives the namespace rewrite (phase 4) and, when SHIM_STRATEGY=shim, the
shim sub-track (phase 4b). See the polylith-migrate-orchestrator table for phase numbers.
Inputs
- Project name (from
migration/<project-name>/state.md)
- Original namespace
ORIG_TOP_NS (from migration/<project-name>/state.md)
The three reference forms (cover all of them)
A namespace rewrite is incomplete unless it covers every form below. A naive
"replace from <ns>. " misses forms 2 and 3:
- Dotted import —
from ${ORIG_TOP_NS}.<sub> import …, import ${ORIG_TOP_NS}.<sub>.
- Bare submodule import —
from ${ORIG_TOP_NS} import <sub> — single, multi-name
(a, b, c), and mixed lines where only some names move. Easy to miss: there is
no dot after the namespace.
- Quoted string module paths —
mock.patch("${ORIG_TOP_NS}.x.Y"), logging
dict-config "${ORIG_TOP_NS}.logging.HealthFilter", importlib / getattr
targets. Not import statements, but they must be rewritten too.
⚠ Do not rewrite unquoted local variables that merely share a name with the
namespace (e.g. a FastAPI app = FastAPI() instance's app.include_router(...)).
Target import statements and quoted module paths only.
Steps
1. Identify references to the original namespace
- Search for all three forms above across all
.py files in the project (and
pyproject.toml / config files for string paths).
- Record the paths and statements in
migration/${PROJECT}/import_analysis.md,
grouped as: internal (inside ${ORIG_TOP_NS}/), external consumers
(entrypoints, alembic, scripts), and tests. The external + test groups are
what the strategy decision below hinges on.
2. List symbols exported by the original namespace
- Inspect
${ORIG_TOP_NS}/__init__.py (typically projects/${PROJECT}/src/${ORIG_TOP_NS}/__init__.py
or projects/${PROJECT}/${ORIG_TOP_NS}/__init__.py) and list public symbols
(not starting with _).
- Record them, and note whether
__init__.py is effectively empty (docstring only).
3. Decide the rewrite strategy (SHIM_STRATEGY)
Choose based on the findings and record it in state.md:
shimless — when imports are predominantly submodule-qualified
(from ${ORIG_TOP_NS}.<sub> import … / from ${ORIG_TOP_NS} import <sub>) and
${ORIG_TOP_NS}/__init__.py exports little or nothing. A single top-level
re-export shim would resolve none of those submodule paths, and a full package
shim mirroring every module is high-effort/high-risk. Phase 4 rewrites all
references (internal + external + tests) directly to the new namespace, and the
phase 4b sub-track is skipped.
shim — when consumers import top-level symbols (from ${ORIG_TOP_NS} import X)
that a single ${ORIG_TOP_NS}/__init__.py re-export can satisfy, and you want to
defer rewriting external consumers. Run the phase 4b sub-track after phase 4.
- When in doubt, prefer
shimless — it leaves no transitional shim to remove
later (the definition-of-done forbids undocumented shims), at the cost of a wider
but mechanical rewrite. Confirm with the user if the consumer surface is large.
Record:
SHIM_STRATEGY=<shim|shimless>
4. Detect potential circular imports
- Inspect the import graph for cycles — especially base ↔ shim once a shim is in
place (
shim strategy only).
- Record any chains in
import_analysis.md. A pure namespace rename (shimless)
preserves the original graph, so cycles there usually mean the project already had
them.
Output
migration/<project-name>/import_analysis.md with: references by form & group,
exported symbols, the chosen strategy + rationale, and circular-import chains (if any).
SHIM_STRATEGY set in migration/<project-name>/state.md.
Verify
migration/<project-name>/import_analysis.md exists and is not empty.
- It records all three reference forms, the exported symbols, and any cycles.
SHIM_STRATEGY is set in state.md (shim or shimless) with a recorded rationale.
Commit
git add migration/${PROJECT}/import_analysis.md migration/${PROJECT}/state.md
git commit -m "migrate(${PROJECT}): phase <N> — analyze-imports"
<N> is this phase's number from the polylith-migrate-orchestrator table (the single
source of truth) — do not hardcode it.