# Agent — Squash branch migrations

Read `_shared.md` first.

## Goal

Collapse the migrations that are **unique to the current branch** into a single
consolidated migration right before merging. The CLI does the mechanical work
(resolve reference branch / classify / backup / delete / restore / regenerate)
and **enforces the safety gates** — you follow the closed decision tree below
and never improvise.

## The one rule that matters

> **You can NEVER delete a migration that already lives on the reference
> (parent) branch.** It is already applied downstream — in production for
> `develop`/`release`/`hotfix`. Deleting it and merging removes it from prod and
> breaks everything.

The reference branch is chosen by branch TYPE (the GOLDEN RULE):

```
feature/* → develop      (feature → develop)
develop   → main          (develop → prod)
release/* → main          (release → prod)
hotfix/*  → main          (hotfix  → prod)
main/master → BLOCKED      (never squash on production)
```

A normal squash only ever consolidates the **branch-only** set
(`local − reference`); it restores every reference migration + the snapshot
verbatim. The single, governed exception that may delete reference migrations is
`/efcore -BruteForce` (see below) — never a manual edit.

## Step 1 — Always run the dry-run plan FIRST (fast, git-only)

```bash
npx --prefer-offline tsx skills/efcore/cli/squash/index.ts --spec '{
  "cwd": "<WORKTREE>",
  "contextName": "CoreDbContext",
  "description": "FeatureXYZConsolidated",
  "dryRun": true
}'
```

It runs on git + disk only (no build) and returns, per assembly:

- 🔒 `parentMigrations` — the reference-branch migrations (**INTOUCHABLE**).
- ✂️ `deletedFiles` — the **only** set a normal squash removes (branch-only).
- ☠️ `missingFromWorkingTree` — reference migrations **absent from your tree**
  (deleted/renamed locally). **Any entry here is a prod-break.**

Plus `branchType`, `baseBranch`, `behindBase`, and `warnings`.

**Render the three lists to the user verbatim** — what's on the parent branch
(🔒), what can be squashed (✂️), and any danger (☠️). This is the answer to
"montre ce qu'on a sur la branche parente et ce que tu peux supprimer".

## Step 2 — The decision tree (closed set — pick exactly one, invent nothing)

In priority order:

1. **`missingFromWorkingTree` is non-empty (☠️)** → **STOP. Refuse.**
   A reference/prod migration has been removed from the working tree (a manual
   re-baseline). Show the exact names and offer **only** the two safe paths:
   - *Restore it*: `git checkout <baseBranch> -- <Migrations dir>` (then re-run), or
   - *Deliberate release re-baseline*: `/efcore -BruteForce` (Step 4).

   **Do NOT** offer to "keep" the re-baseline, to commit it, or to "analyse the
   impact and decide later". There is nothing to weigh: merging it breaks prod.

2. **`behindBase > 0`** → **STOP.** Tell the user to sync first
   (`/gitflow sync`, or merge/rebase the reference). Do **not** suggest
   `allowBehindBase` — it is not a casual menu item.

3. **Uncommitted, unrelated migration edits in the tree** → **STOP.** Ask the
   user to commit or stash them first. Do not "annuler les modifs" for them.

4. **Every assembly reports nothing to consolidate** (all `deletedFiles` empty /
   "nothing to squash") → say **"Rien à consolider."** in one line and STOP.
   This is the correct, complete answer — not a menu.

5. **Otherwise** → present the plan (🔒 preserved count + ✂️ to-consolidate +
   the new migration name) and ask a **single binary** confirmation:
   *proceed* / *abandon*. On *proceed*, run Step 3.

   **Empty base (a brand-new context).** When the reference branch carries **no**
   migrations *and* no snapshot for this context (🔒 `parentMigrations: []`,
   `baseMigrationsPreserved: 0`, and the dry-run reports `emptyBaseModel: true`
   with an "empty base model" warning), this is still a **normal, in-spec
   squash** — the consolidated migration is a full `InitialCreate`. It is the
   common shape of the *first* feature branch that introduces a new DbContext.
   Say so in the plan ("consolidated as a full InitialCreate — empty base") and
   proceed on *proceed*. **Do NOT route this to `-BruteForce`** — there are no
   casualties to confirm; bruteForce would be a confusing, wrong UX here.

## Step 3 — Run the real squash (only after a "proceed")

Same spec without `dryRun`. The CLI:
- Backs up under `.efcore-squash-backup/<timestamp>/<Assembly>/`.
- Deletes **only** the branch-only migration files (+ their `.Designer.cs`).
- **Restores the reference-branch migrations + `ModelSnapshot.cs` verbatim** —
  *except* on an empty base (`emptyBaseModel: true`), where it **deletes** the
  snapshot so the regen is a full `InitialCreate`.
- Runs `dotnet ef migrations add <consolidated_name>`.
- **Re-freezes** any SQL objects (functions/views/procs from `SqlObjects/`) into
  the consolidated migration (`sqlObjectsInlined` — surface the list).
- **Logs the destructive run** to `.claude/Efcore-history/` (`historyLogPath` in
  the result — mention it).
- **Auto-rolls back on failure.** If the snapshot step or `dotnet ef migrations
  add` fails, the CLI restores the assembly from the backup so the run is a
  **no-op** (`rolledBack: true`, `deletedFiles: []`) — never a half-squashed
  tree. If the rollback itself fails it warns and keeps the backup.

Then run `dotnet build` to verify it compiles. If it fails, restore from the
backup and investigate. Finally commit the single new migration.

## Step 4 — `/efcore -BruteForce` (the ONE exception)

A full re-baseline: delete **every** migration (including those already on the
reference/prod branch) + the snapshot, and regenerate a single `InitialCreate`.
Legitimate only at release time, when `main`/`develop` are re-baselined in
**lock-step**. It is a two-step, confirmed, logged operation.

1. **Plan + casualties.** Run with `bruteForce` but no confirmation:
   ```bash
   npx ... squash/index.ts --spec '{ "cwd":"<WT>", "contextName":"CoreDbContext",
     "description":"InitialCreate", "bruteForce": true }'
   ```
   The CLI **refuses** with `requiresConfirmation: true` and lists the exact
   production migrations that would be destroyed.

2. **Hard confirmation.** Show the user that list and ask explicitly:
   ```yaml
   AskUserQuestion:
     header: "Brute force"
     question: "BRUTE FORCE re-baseline: this DELETES {N} migration(s) already on {baseBranch} (production history) — {names}. Proceed?"
     options:
       - label: "Yes — re-baseline & destroy them"
       - label: "No — cancel"
   ```

3. **Execute on "yes"** — re-run with `"confirmBruteForce": true`. The CLI
   re-baselines and writes an immutable audit record under
   `.claude/Efcore-history/` — surface `historyLogPath` to the user.

`main`/`master` stay **blocked even in brute-force** — the CLI refuses. Do the
re-baseline on `develop`/`release`, then merge to `main` in lock-step.

## What you must NEVER do

- Never offer to **keep**, **commit**, or **"decide later"** on a working tree
  that has deleted/renamed a reference-branch migration (the ☠️ case). Refuse.
- Never present `allowBehindBase` or `bruteForce` as a casual option to dodge a
  STOP. `bruteForce` is reached only by the user explicitly asking for a full
  re-baseline, and always with the named-casualties confirmation.
- Never hand-edit, hand-delete, or hand-rename migration files or the snapshot.
- Never squash on `main`/`master`, against the wrong reference, or while behind
  the reference.
- Never skip or delete the backup before validating the new migration in a real
  (dev) database.

## Recovery

If a real run went wrong, restore from the backup:

```bash
cp -r .efcore-squash-backup/<timestamp>/<Assembly>/* .
git checkout HEAD -- path/to/ModelSnapshot.cs
dotnet ef migrations remove --context <context> --project <csproj> --startup-project <startup>
```
