Skip to content

Commit 8ccaf06

Browse files
authored
feat(skills): add migrate-project-to-polylith skills (#447)
* feat: add migrate project to polylith skills * bump CLI to 1.48.0
1 parent 0ac7301 commit 8ccaf06

26 files changed

Lines changed: 2458 additions & 3 deletions

File tree

.agents/skills/polylith/README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33

44
> **Note for contributors:** this README is a **human reference**. The agent loads each `*/SKILL.md` independently via the skill loader; this file is **not** auto-loaded with any skill. Anything an agent must know to act has to live in the relevant `SKILL.md` itself, not here.
55
6-
## Available Skills
6+
## Skill loading model
7+
8+
Two kinds of skill live under this directory; the distinction matters when picking an entry point:
9+
10+
- **Atomic skills (`polylith-*`).** Each maps to one `poly` CLI command (or one focused concept). Safe to load in isolation; individually composable. These cover everyday Polylith workflows — creating bricks, syncing, checking, inspecting, and so on.
11+
- **Orchestrated skill set (`migrate-project/migrate-*`).** A multi-phase workflow with shared state (`migration/<PROJECT>/state.md`) and a git safety net. **Never load an individual `migrate-*` skill directly** — always load `migrate-orchestrator` and let it drive the phases in order. See [`migrate-project/README.md`](./migrate-project/README.md). This is an advanced, explicit-opt-in workflow used for migrating a **non-Polylith** project into a Polylith workspace, **not** part of daily Polylith use.
12+
13+
## Available Skills (daily Polylith workflows)
714

815
| Skill | Command | Purpose |
916
|---------------------------|--------------------|----------------------------------------------------------------------------------------------------------|
@@ -15,8 +22,13 @@
1522
| [Sync](./polylith-sync/SKILL.md) | `poly sync` | Update each project's brick list to match actual imports. |
1623
| [Workspace Inspection](./polylith-workspace-inspection/SKILL.md) | `poly info` | Show brick × project usage (which projects use which bricks). |
1724
| [Dependency Visualization](./polylith-dependency-visualization/SKILL.md) | `poly deps` | Show brick × brick dependencies and interface compliance. |
25+
| [Dependency Management](./polylith-dependency-management/SKILL.md) || Add or manage third-party libraries for a brick or project. |
1826
| [Testing](./polylith-testing/SKILL.md) | `poly test diff` | List bricks/projects affected by **test-code** changes since a tag. |
1927
| [Diff](./polylith-diff/SKILL.md) | `poly diff` | List bricks whose **implementation** changed since a tag. |
2028
| [Check](./polylith-check/SKILL.md) | `poly check` | Validate the workspace (CI gate; exits 1 on failure). |
2129
| [Libs](./polylith-libs/SKILL.md) | `poly libs` | Inspect third-party libraries per project. |
22-
| [Concepts](./polylith-concepts/SKILL.md) || Provides foundational knowledge about Polylith architecture and terminology. |
30+
| [Concepts](./polylith-concepts/SKILL.md) || Foundational knowledge about Polylith architecture and terminology. |
31+
32+
## Advanced workflow
33+
34+
For migrating an existing **non-Polylith** Python project into a Polylith workspace, see [`migrate-project/README.md`](./migrate-project/README.md). This is a destructive, multi-phase, explicit-opt-in workflow — start with the `migrate-orchestrator` skill, not any individual `migrate-*` sub-skill.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Project Migration Skills
2+
3+
> **Note for contributors:** this README is a **human reference** and a skill **index**. Agents load each `migrate-*/SKILL.md` independently via the skill loader; this README is **not** auto-loaded with any skill. Anything an agent must know to act has to live in the relevant `SKILL.md` itself, not here.
4+
5+
This directory contains skills for migrating **non-Polylith Python projects** into a Polylith workspace. They cooperate via two artifacts under `migration/<PROJECT>/`:
6+
7+
- `state.md` — a flat `KEY=value` file. Canonical schema lives in [`migrate-discover/SKILL.md`](./migrate-discover/SKILL.md).
8+
- `manifest.md` — a human-readable structural inventory of the source project.
9+
10+
---
11+
12+
## ⚠️ When to use these skills
13+
14+
**Only** when:
15+
1. A human has **explicitly instructed** to migrate a specific project (e.g., "migrate `projects/my-app` to Polylith").
16+
2. The target project lives under `projects/<PROJECT>/` of this Polylith workspace.
17+
3. The goal is to refactor the project into Polylith bricks (bases and components).
18+
19+
**Do not use** for:
20+
- Automated or unattended migrations.
21+
- Projects that are already structured as Polylith bricks.
22+
- Daily Polylith development tasks — for those, see the sibling `polylith-*` skills.
23+
24+
---
25+
26+
## How to invoke
27+
28+
Load the orchestrator and let it drive the rest:
29+
30+
```
31+
Load the `migrate-orchestrator` skill and migrate `projects/<project-name>`.
32+
```
33+
34+
The orchestrator will:
35+
1. Ask the user for explicit confirmation.
36+
2. Establish a git safety net (dedicated branch + per-phase commits).
37+
3. Load and execute each phase skill in order, verifying after each.
38+
39+
> **Why every sub-skill says "do not load directly".** Each `migrate-<phase>` skill's `description:` starts with a redirect to `migrate-orchestrator`. This is intentional: it makes the orchestrator the only valid entry point regardless of which sub-skill the agent's fuzzy-match initially favours. The sub-skills depend on state (`migration/<PROJECT>/state.md`) and a git safety net that only the orchestrator sets up.
40+
41+
---
42+
43+
## Downstream installation
44+
45+
When this skill set is installed into another Polylith workspace (e.g. via a skills package), the in-skill `[ENTRY POINT]` / `[Internal sub-skill of migrate-orchestrator …]` markers in each `description:` are the primary routing signal — they ship with the package.
46+
47+
For an extra-strong signal, downstream consumers should add the following snippet to their own repo-level `AGENTS.md` (or equivalent agent-routing file). It is **not** required — the in-skill markers are usually sufficient — but it removes any ambiguity for agents that read `AGENTS.md` before scanning skill descriptions.
48+
49+
```markdown
50+
## Polylith migration instructions
51+
52+
When the user asks to migrate a non-Polylith Python project to Polylith
53+
(e.g. "migrate `projects/<name>` to Polylith"), load the
54+
`migrate-orchestrator` skill first and let it drive the workflow.
55+
56+
Never load `migrate-discover`, `migrate-extract-to-base`, or any other
57+
`migrate-*` sub-skill directly — they are phases the orchestrator
58+
invokes in order, with per-phase verification and git checkpoints
59+
between them.
60+
```
61+
62+
---
63+
64+
## Workflow at a glance
65+
66+
The **authoritative** phase order, numbering, and dependencies live in
67+
[`migrate-orchestrator/SKILL.md`](./migrate-orchestrator/SKILL.md) — it is the
68+
**single source of truth**. This README intentionally does **not** repeat the
69+
table, to avoid drift (sub-skills must not hardcode phase numbers either; they
70+
reference `<N>` from the orchestrator table).
71+
72+
At a glance, the flow is:
73+
74+
> **discover → analyze-imports** (choose `shim`/`shimless`) **→ extract-to-base →
75+
> update-imports →** *[optional shim sub-track, only when `SHIM_STRATEGY=shim`]* **
76+
> prepare-project → verify-stability → isolate-base-and-big-component →
77+
> split-big-component → extract-standalone-modules →
78+
> isolate-shared-and-project-logic → distribute-wiring → split-component-internals →
79+
> refactor-tests → definition-of-done**
80+
81+
### Optional skills (triggered during `migrate-discover`)
82+
83+
| Skill | Purpose | Trigger / dependency |
84+
|-------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|
85+
| [`migrate-convert-linter`](./migrate-convert-linter/SKILL.md) | Align the project's linter/formatter with the **workspace's** configured tool. | `CONVERT_LINTER=yes` in `state.md`. Runs between `migrate-discover` and `migrate-analyze-imports`. |
86+
| [`migrate-convert-type-checker`](./migrate-convert-type-checker/SKILL.md) | Align the project's type checker with the **workspace's** configured tool. | `CONVERT_TYPE_CHECKER=yes` in `state.md`. Runs between `migrate-discover` and `migrate-analyze-imports`. |
87+
| [`migrate-convert-package-manager`](./migrate-convert-package-manager/SKILL.md) | Convert the project's `pyproject.toml` to uv workspaces. **Opinionated about uv** — only run when the workspace itself uses uv. | `CONVERT_PACKAGE_MANAGER=yes` in `state.md`. Runs between `migrate-discover` and `migrate-analyze-imports`. |
88+
| [`migrate-dedupe`](./migrate-dedupe/SKILL.md) | Identify and apply controlled deduplication discovered during refactoring. | User approval. Runs after `migrate-split-big-component` or `migrate-extract-standalone-modules`. |
89+
90+
---
91+
92+
## Scope of the four "splitting" skills
93+
94+
These skills overlap in vocabulary but address different scopes. Use this matrix to decide which one applies:
95+
96+
| Skill | Scope | Trigger |
97+
|---------------------------------------------|------------------------------------------------|---------------------------------------------------------------------------|
98+
| `migrate-split-big-component` | Within one project; component → multiple components. | The temporary big component (from `migrate-isolate-base-and-big-component`) is too large. |
99+
| `migrate-extract-standalone-modules` | Within one project; pulls foundational modules out of the residual. | Residual still contains `consts.py`/`exceptions.py`/`models.py`. |
100+
| `migrate-split-component-internals` | Within one already-extracted shared component; `core.py` → multiple files. | A component's `core.py` mixes multiple domains internally. |
101+
| `migrate-isolate-shared-and-project-logic` | Cross-project; separate shared vs project-specific in components used by ≥ 2 projects. | Migrating a 2nd+ project that overlaps with an already-extracted one. |
102+
103+
> 💡 In a fresh migration of a single project, you usually run `migrate-split-big-component``migrate-extract-standalone-modules``migrate-split-component-internals`, and skip `migrate-isolate-shared-and-project-logic` until a second project is migrated.
104+
105+
---
106+
107+
## Files in this directory
108+
109+
- `README.md` — this file (human reference + index).
110+
- `migrate-orchestrator/SKILL.md` — entry point; defines the phase order and the git safety net.
111+
- `migrate-<phase>/SKILL.md` — one per phase referenced in the orchestrator table.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
---
2+
name: migrate-analyze-imports
3+
description: "[Internal sub-skill of `migrate-orchestrator`. Do not load directly — load `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+
---
5+
6+
# Skill: migrate-analyze-imports
7+
8+
## Goal
9+
Analyze the project's import graph to:
10+
1. Find **every** reference to the original namespace (in all three forms — see below).
11+
2. Choose the namespace-rewrite strategy: `SHIM_STRATEGY=shim|shimless`.
12+
3. List symbols exported by the original namespace's `__init__.py`.
13+
4. Detect potential circular imports.
14+
15+
This drives the namespace rewrite (phase 4) and, when `SHIM_STRATEGY=shim`, the
16+
shim sub-track (phase 4b). See the `migrate-orchestrator` table for phase numbers.
17+
18+
## Inputs
19+
- Project name (from `migration/<project-name>/state.md`)
20+
- Original namespace `ORIG_TOP_NS` (from `migration/<project-name>/state.md`)
21+
22+
## The three reference forms (cover all of them)
23+
A namespace rewrite is **incomplete** unless it covers every form below. A naive
24+
"replace `from <ns>.` " misses forms 2 and 3:
25+
26+
1. **Dotted import**`from ${ORIG_TOP_NS}.<sub> import …`, `import ${ORIG_TOP_NS}.<sub>`.
27+
2. **Bare submodule import**`from ${ORIG_TOP_NS} import <sub>` — single, multi-name
28+
(`a, b, c`), and **mixed** lines where only some names move. Easy to miss: there is
29+
no dot after the namespace.
30+
3. **Quoted string module paths**`mock.patch("${ORIG_TOP_NS}.x.Y")`, logging
31+
dict-config `"${ORIG_TOP_NS}.logging.HealthFilter"`, `importlib` / `getattr`
32+
targets. Not import statements, but they must be rewritten too.
33+
34+
> ⚠ Do **not** rewrite unquoted local variables that merely share a name with the
35+
> namespace (e.g. a FastAPI `app = FastAPI()` instance's `app.include_router(...)`).
36+
> Target import statements and quoted module paths only.
37+
38+
## Steps
39+
40+
### 1. Identify references to the original namespace
41+
1. Search for all three forms above across **all** `.py` files in the project (and
42+
`pyproject.toml` / config files for string paths).
43+
2. 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 are
46+
what the strategy decision below hinges on.
47+
48+
### 2. List symbols exported by the original namespace
49+
1. 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 symbols
51+
(not starting with `_`).
52+
2. Record them, and **note whether `__init__.py` is effectively empty** (docstring only).
53+
54+
### 3. Decide the rewrite strategy (`SHIM_STRATEGY`)
55+
Choose based on the findings and record it in `state.md`:
56+
57+
- **`shimless`** — when imports are predominantly **submodule-qualified**
58+
(`from ${ORIG_TOP_NS}.<sub> import …` / `from ${ORIG_TOP_NS} import <sub>`) and
59+
`${ORIG_TOP_NS}/__init__.py` exports little or nothing. A single top-level
60+
re-export shim would resolve **none** of those submodule paths, and a full package
61+
shim mirroring every module is high-effort/high-risk. Phase 4 rewrites **all**
62+
references (internal + external + tests) directly to the new namespace, and the
63+
**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 to
66+
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 remove
68+
later (the definition-of-done forbids undocumented shims), at the cost of a wider
69+
but mechanical rewrite. Confirm with the user if the consumer surface is large.
70+
71+
Record:
72+
```
73+
SHIM_STRATEGY=<shim|shimless>
74+
```
75+
76+
### 4. Detect potential circular imports
77+
1. Inspect the import graph for cycles — especially base ↔ shim once a shim is in
78+
place (`shim` strategy only).
79+
2. 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 had
81+
them.
82+
83+
## Output
84+
- `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`.
87+
88+
## Verify
89+
1. `migration/<project-name>/import_analysis.md` exists and is not empty.
90+
2. It records all three reference forms, the exported symbols, and any cycles.
91+
3. `SHIM_STRATEGY` is set in `state.md` (`shim` or `shimless`) with a recorded rationale.
92+
93+
## Commit
94+
```bash
95+
git add migration/${PROJECT}/import_analysis.md migration/${PROJECT}/state.md
96+
git commit -m "migrate(${PROJECT}): phase <N> — analyze-imports"
97+
```
98+
> `<N>` is this phase's number from the `migrate-orchestrator` table (the single
99+
> source of truth) — do not hardcode it.

0 commit comments

Comments
 (0)