Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .agents/skills/polylith/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@

> **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.

## Available Skills
## Skill loading model

Two kinds of skill live under this directory; the distinction matters when picking an entry point:

- **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.
- **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.

## Available Skills (daily Polylith workflows)

| Skill | Command | Purpose |
|---------------------------|--------------------|----------------------------------------------------------------------------------------------------------|
Expand All @@ -15,8 +22,13 @@
| [Sync](./polylith-sync/SKILL.md) | `poly sync` | Update each project's brick list to match actual imports. |
| [Workspace Inspection](./polylith-workspace-inspection/SKILL.md) | `poly info` | Show brick × project usage (which projects use which bricks). |
| [Dependency Visualization](./polylith-dependency-visualization/SKILL.md) | `poly deps` | Show brick × brick dependencies and interface compliance. |
| [Dependency Management](./polylith-dependency-management/SKILL.md) | — | Add or manage third-party libraries for a brick or project. |
| [Testing](./polylith-testing/SKILL.md) | `poly test diff` | List bricks/projects affected by **test-code** changes since a tag. |
| [Diff](./polylith-diff/SKILL.md) | `poly diff` | List bricks whose **implementation** changed since a tag. |
| [Check](./polylith-check/SKILL.md) | `poly check` | Validate the workspace (CI gate; exits 1 on failure). |
| [Libs](./polylith-libs/SKILL.md) | `poly libs` | Inspect third-party libraries per project. |
| [Concepts](./polylith-concepts/SKILL.md) | — | Provides foundational knowledge about Polylith architecture and terminology. |
| [Concepts](./polylith-concepts/SKILL.md) | — | Foundational knowledge about Polylith architecture and terminology. |

## Advanced workflow

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.
111 changes: 111 additions & 0 deletions .agents/skills/polylith/migrate-project/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Project Migration Skills

> **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.

This directory contains skills for migrating **non-Polylith Python projects** into a Polylith workspace. They cooperate via two artifacts under `migration/<PROJECT>/`:

- `state.md` — a flat `KEY=value` file. Canonical schema lives in [`migrate-discover/SKILL.md`](./migrate-discover/SKILL.md).
- `manifest.md` — a human-readable structural inventory of the source project.

---

## ⚠️ When to use these skills

**Only** when:
1. A human has **explicitly instructed** to migrate a specific project (e.g., "migrate `projects/my-app` to Polylith").
2. The target project lives under `projects/<PROJECT>/` of this Polylith workspace.
3. The goal is to refactor the project into Polylith bricks (bases and components).

**Do not use** for:
- Automated or unattended migrations.
- Projects that are already structured as Polylith bricks.
- Daily Polylith development tasks — for those, see the sibling `polylith-*` skills.

---

## How to invoke

Load the orchestrator and let it drive the rest:

```
Load the `migrate-orchestrator` skill and migrate `projects/<project-name>`.
```

The orchestrator will:
1. Ask the user for explicit confirmation.
2. Establish a git safety net (dedicated branch + per-phase commits).
3. Load and execute each phase skill in order, verifying after each.

> **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.

---

## Downstream installation

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.

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.

```markdown
## Polylith migration instructions

When the user asks to migrate a non-Polylith Python project to Polylith
(e.g. "migrate `projects/<name>` to Polylith"), load the
`migrate-orchestrator` skill first and let it drive the workflow.

Never load `migrate-discover`, `migrate-extract-to-base`, or any other
`migrate-*` sub-skill directly — they are phases the orchestrator
invokes in order, with per-phase verification and git checkpoints
between them.
```

---

## Workflow at a glance

The **authoritative** phase order, numbering, and dependencies live in
[`migrate-orchestrator/SKILL.md`](./migrate-orchestrator/SKILL.md) — it is the
**single source of truth**. This README intentionally does **not** repeat the
table, to avoid drift (sub-skills must not hardcode phase numbers either; they
reference `<N>` from the orchestrator table).

At a glance, the flow is:

> **discover → analyze-imports** (choose `shim`/`shimless`) **→ extract-to-base →
> update-imports →** *[optional shim sub-track, only when `SHIM_STRATEGY=shim`]* **→
> prepare-project → verify-stability → isolate-base-and-big-component →
> split-big-component → extract-standalone-modules →
> isolate-shared-and-project-logic → distribute-wiring → split-component-internals →
> refactor-tests → definition-of-done**

### Optional skills (triggered during `migrate-discover`)

| Skill | Purpose | Trigger / dependency |
|-------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|
| [`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`. |
| [`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`. |
| [`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`. |
| [`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`. |

---

## Scope of the four "splitting" skills

These skills overlap in vocabulary but address different scopes. Use this matrix to decide which one applies:

| Skill | Scope | Trigger |
|---------------------------------------------|------------------------------------------------|---------------------------------------------------------------------------|
| `migrate-split-big-component` | Within one project; component → multiple components. | The temporary big component (from `migrate-isolate-base-and-big-component`) is too large. |
| `migrate-extract-standalone-modules` | Within one project; pulls foundational modules out of the residual. | Residual still contains `consts.py`/`exceptions.py`/`models.py`. |
| `migrate-split-component-internals` | Within one already-extracted shared component; `core.py` → multiple files. | A component's `core.py` mixes multiple domains internally. |
| `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. |

> 💡 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.

---

## Files in this directory

- `README.md` — this file (human reference + index).
- `migrate-orchestrator/SKILL.md` — entry point; defines the phase order and the git safety net.
- `migrate-<phase>/SKILL.md` — one per phase referenced in the orchestrator table.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
name: migrate-analyze-imports
description: 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: migrate-analyze-imports

## Goal
Analyze the project's import graph to:
1. Find **every** reference to the original namespace (in all three forms — see below).
2. Choose the namespace-rewrite strategy: `SHIM_STRATEGY=shim|shimless`.
3. List symbols exported by the original namespace's `__init__.py`.
4. Detect potential circular imports.

This drives the namespace rewrite (phase 4) and, when `SHIM_STRATEGY=shim`, the
shim sub-track (phase 4b). See the `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:

1. **Dotted import** — `from ${ORIG_TOP_NS}.<sub> import …`, `import ${ORIG_TOP_NS}.<sub>`.
2. **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.
3. **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
1. Search for all three forms above across **all** `.py` files in the project (and
`pyproject.toml` / config files for string paths).
2. 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
1. 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 `_`).
2. 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
1. Inspect the import graph for cycles — especially base ↔ shim once a shim is in
place (`shim` strategy only).
2. 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
1. `migration/<project-name>/import_analysis.md` exists and is not empty.
2. It records all three reference forms, the exported symbols, and any cycles.
3. `SHIM_STRATEGY` is set in `state.md` (`shim` or `shimless`) with a recorded rationale.

## Commit
```bash
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 `migrate-orchestrator` table (the single
> source of truth) — do not hardcode it.
Loading