---
name: efcore
description: >
  EF Core migration workflow — create, squash, rebase-snapshot, apply, list,
  status. Applies the best practices of the global /efcore agents without
  MCP or external dependencies. Works against a single-assembly generated
  app (CoreDbContext + ExtensionsDbContext) or the multi-assembly Studio
  (StudioDbContext with Sqlite / Postgres / SqlServer migration projects).
argument-hint: "[create|squash|rebase-snapshot|apply|list|status|recreate-db] [-BruteForce]"
phase: infrastructure
cli: cli/
allowed-tools: [Read, Glob, Grep, Bash]  # Bash: CLI invocation
---

# efcore — EF Core migration workflow

This skill manages EF Core migrations for any .NET project in a worktree:
SmartStack-generated apps (dual context: Core + Extensions) or the Studio
itself (single context, three provider assemblies).

All intelligence lives in this skill + the agent prompts under `agents/`.
The colocated CLIs in `cli/` are pure executors — they never decide what to
do, they only do what the spec tells them.

## Routing table

| User intent | Agent prompt | CLI |
|-------------|--------------|-----|
| "Show me the migrations" | `agents/list.md` | `cli/list/` |
| "Is anything pending?" | `agents/status.md` | `cli/status/` |
| "Create a new migration" | `agents/create.md` | `cli/create/` |
| "Squash branch migrations before merge" | `agents/squash.md` | `cli/squash/` |
| "Full re-baseline — delete ALL migrations incl. prod" (`/efcore -BruteForce`) | `agents/squash.md` §Step 4 | `cli/squash/` (`bruteForce`+`confirmBruteForce`) |
| "Fix snapshot after a rebase" | `agents/rebase-snapshot.md` | `cli/rebase-snapshot/` |
| "Apply migrations to the local DB" | `agents/db-update.md` | `cli/apply/` (3-tier policy: 🟢 local, 🟡 revert, 🔴 remote/unknown) |
| "Drop + recreate the local DB (destructive)" | `agents/recreate-db.md` | drop = 🔴 user-run command; re-apply via `cli/apply/` |

**Always read `_shared.md` before executing any operation.** It documents
WSL git preflight, the `dotnet-ef` PATH trick on Git Bash, the naming
convention, and the dual-DbContext detection heuristic.

## Invariants

- Never create migrations manually named — always let `cli/create/` compute
  the name. The convention is `{contextPrefix}_v{version}_{seq3}_{PascalCaseDescription}`.
- Never edit `ModelSnapshot.cs` by hand — it is regenerated by EF.
- Never run `squash` or `rebase-snapshot` on `main` / `master` — the CLI
  refuses (and `bruteForce` does NOT override this).
- **Always dry-run (plan) first.** Show the user the three lists before any
  squash: 🔒 `parentMigrations` (reference branch — INTOUCHABLE), ✂️
  `deletedFiles` (branch-only — the only set a normal squash removes), ☠️
  `missingFromWorkingTree` (the prod-break signal).
- **A normal squash NEVER deletes a migration that lives on the reference
  branch.** If a reference migration is MISSING from the working tree (a manual
  re-baseline removed it), the CLI **REFUSES** — restore it, or use
  `/efcore -BruteForce`. The skill must never offer to "keep" such a tree.
- **`/efcore -BruteForce` is the ONLY governed way to delete reference/prod
  migrations** (a full re-baseline). It requires `confirmBruteForce: true`
  (the CLI first reports the exact casualties), stays blocked on `main`/`master`,
  and is recorded immutably under `.claude/Efcore-history/`.
- Squash always creates a backup under `.efcore-squash-backup/<timestamp>/`
  before deleting anything. If something goes wrong, the backup is the
  source of truth.
- For multi-provider contexts (the Studio itself), `create` adds the same
  migration to every provider assembly by default. Pass `assembly` to
  target only one.

## Invocation contract for agents

When Claude decides which agent to use, it invokes the corresponding CLI
with a single `--spec '<JSON>'` argument. Every CLI returns a single JSON
object on stdout. Exit code 0 = success, 1 = failure (inspect `error` or
`errors[]`).

Example — create a migration on the current worktree:

```
npx --prefer-offline tsx skills/efcore/cli/create/index.ts --spec '{
  "cwd": "D:/projets/app/features/ba-002",
  "contextName": "CoreDbContext",
  "description": "AddEmployeeAvatar"
}'
```

## Consumer: DevRunner Migrations tab

The Studio's DevRunner page (see `src/frontend/components/dev-runner/MigrationsTab.tsx`)
exposes these operations through buttons. Deterministic reads (list,
status, detect-contexts) go through the IPC router `/migrations/*`; any
operation that mutates the worktree (create, squash, rebase-snapshot,
db-update) is invoked via `claude -p @skills/efcore/SKILL.md ...` so that
the agent prompts drive Claude. This keeps all business logic in Markdown
and away from TypeScript.

## When something goes wrong

- **dotnet-ef not on PATH** — follow `_shared.md` "dotnet-ef PATH setup".
- **Snapshot conflicts after rebase** — use `rebase-snapshot`.
- **Squash produced a build error** — copy files back from the backup
  under `.efcore-squash-backup/<timestamp>/` and investigate.
- **Multiple contexts detected but operation only matches one** — pass the
  `contextName` and optionally `assembly` to disambiguate.
