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.
1---2name: polylith-migrate-analyze-imports3description: [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.4---56# Skill: polylith-migrate-analyze-imports78## Goal9Analyze the project's import graph to:101. Find **every** reference to the original namespace (in all three forms — see below).112. Choose the namespace-rewrite strategy: `SHIM_STRATEGY=shim|shimless`.123. List symbols exported by the original namespace's `__init__.py`.134. Detect potential circular imports.1415This drives the namespace rewrite (phase 4) and, when `SHIM_STRATEGY=shim`, the16shim sub-track (phase 4b). See the `polylith-migrate-orchestrator` table for phase numbers.1718## Inputs19- Project name (from `migration/<project-name>/state.md`)20- Original namespace `ORIG_TOP_NS` (from `migration/<project-name>/state.md`)2122## The three reference forms (cover all of them)23A namespace rewrite is **incomplete** unless it covers every form below. A naive24"replace `from <ns>.` " misses forms 2 and 3:25261. **Dotted import** — `from ${ORIG_TOP_NS}.<sub> import …`, `import ${ORIG_TOP_NS}.<sub>`.272. **Bare submodule import** — `from ${ORIG_TOP_NS} import <sub>` — single, multi-name28 (`a, b, c`), and **mixed** lines where only some names move. Easy to miss: there is29 no dot after the namespace.303. **Quoted string module paths** — `mock.patch("${ORIG_TOP_NS}.x.Y")`, logging31 dict-config `"${ORIG_TOP_NS}.logging.HealthFilter"`, `importlib` / `getattr`32 targets. Not import statements, but they must be rewritten too.3334> ⚠ Do **not** rewrite unquoted local variables that merely share a name with the35> namespace (e.g. a FastAPI `app = FastAPI()` instance's `app.include_router(...)`).36> Target import statements and quoted module paths only.3738## Steps3940### 1. Identify references to the original namespace411. Search for all three forms above across **all** `.py` files in the project (and42 `pyproject.toml` / config files for string paths).432. Record the paths and statements in `migration/${PROJECT}/import_analysis.md`,44 grouped as: **internal** (inside `${ORIG_TOP_NS}/`), **external consumers**45 (entrypoints, `alembic`, scripts), and **tests**. The external + test groups are46 what the strategy decision below hinges on.4748### 2. List symbols exported by the original namespace491. Inspect `${ORIG_TOP_NS}/__init__.py` (typically `projects/${PROJECT}/src/${ORIG_TOP_NS}/__init__.py`50 or `projects/${PROJECT}/${ORIG_TOP_NS}/__init__.py`) and list public symbols51 (not starting with `_`).522. Record them, and **note whether `__init__.py` is effectively empty** (docstring only).5354### 3. Decide the rewrite strategy (`SHIM_STRATEGY`)55Choose based on the findings and record it in `state.md`:5657- **`shimless`** — when imports are predominantly **submodule-qualified**58 (`from ${ORIG_TOP_NS}.<sub> import …` / `from ${ORIG_TOP_NS} import <sub>`) and59 `${ORIG_TOP_NS}/__init__.py` exports little or nothing. A single top-level60 re-export shim would resolve **none** of those submodule paths, and a full package61 shim mirroring every module is high-effort/high-risk. Phase 4 rewrites **all**62 references (internal + external + tests) directly to the new namespace, and the63 **phase 4b sub-track is skipped**.64- **`shim`** — when consumers import top-level symbols (`from ${ORIG_TOP_NS} import X`)65 that a single `${ORIG_TOP_NS}/__init__.py` re-export can satisfy, and you want to66 defer rewriting external consumers. Run the phase 4b sub-track after phase 4.67- **When in doubt, prefer `shimless`** — it leaves no transitional shim to remove68 later (the definition-of-done forbids undocumented shims), at the cost of a wider69 but mechanical rewrite. Confirm with the user if the consumer surface is large.7071Record:72```73SHIM_STRATEGY=<shim|shimless>74```7576### 4. Detect potential circular imports771. Inspect the import graph for cycles — especially base ↔ shim once a shim is in78 place (`shim` strategy only).792. Record any chains in `import_analysis.md`. A pure namespace rename (shimless)80 preserves the original graph, so cycles there usually mean the project already had81 them.8283## Output84- `migration/<project-name>/import_analysis.md` with: references by form & group,85 exported symbols, the chosen strategy + rationale, and circular-import chains (if any).86- `SHIM_STRATEGY` set in `migration/<project-name>/state.md`.8788## Verify891. `migration/<project-name>/import_analysis.md` exists and is not empty.902. It records all three reference forms, the exported symbols, and any cycles.913. `SHIM_STRATEGY` is set in `state.md` (`shim` or `shimless`) with a recorded rationale.9293## Commit94```bash95git add migration/${PROJECT}/import_analysis.md migration/${PROJECT}/state.md96git commit -m "migrate(${PROJECT}): phase <N> — analyze-imports"97```98> `<N>` is this phase's number from the `polylith-migrate-orchestrator` table (the single99> source of truth) — do not hardcode it.