---
name: gitflow
description: GitFlow workflow — routes to colocated CLI commands (skills/gitflow/cli/) for deterministic execution
argument-hint: "[-a] [-f|-r|-h|-s] <action> [name|version]"
cli: cli/
allowed-tools: [Read, Glob, Grep, Bash, AskUserQuestion]  # Bash: CLI invocation
---

## Current state (auto-injected)
- Branch: !`git branch --show-current 2>/dev/null || echo "UNAVAILABLE"`
- Status: !`git status --short 2>/dev/null | head -10`
- Config: !`cat .gitflow/config.json 2>/dev/null || cat ../.gitflow/config.json 2>/dev/null || cat ../../.gitflow/config.json 2>/dev/null || cat .claude/gitflow/config.json 2>/dev/null || echo "no config found"`

<objective>
Route GitFlow operations to colocated CLI commands in `skills/gitflow/cli/`.
Each CLI is self-contained (Zod types, validate, execute, orchestrate) and
returns a JSON envelope. You parse flags, call the RIGHT CLI, and interpret the
JSON for the user. The CLIs enforce the branch rules; you must not bypass them.
</objective>

<branch_rules>
The invariants the CLIs enforce — keep them in mind when interpreting results:

| Type | Base (branch FROM) | Target (PR/merge INTO) |
|------|--------------------|------------------------|
| feature | develop | **develop** (never main) |
| release | **develop** | main (+ merge-back develop, tag) |
| hotfix | main | main (+ merge-back develop, tag) |

A feature can NEVER target main. A release is cut from develop, then PR'd to main.
</branch_rules>

<flags>
**Mode flags — mutually exclusive, each ALWAYS routes to `start`:**
| Short | Meaning | Spec built for `start` |
|-------|---------|------------------------|
| `-f <name>` | feature | `{"type":"feature","name":"<name>"}` |
| `-r <version>` | release | `{"type":"release","name":"<version>"}` |
| `-h <name>` | hotfix | `{"type":"hotfix","name":"<name>"}` |

**Behavior flags:**
| Short | Meaning |
|-------|---------|
| `-a` | auto: create branch → commit → open PR, then **STOP** for review. NEVER runs `merge`/`finish`, NEVER auto-merges to main. |
| `-s` | status only |

⚠️ `-a` is NOT "do everything to main". For a release it means: create
`release/<version>` from develop and open its PR — then STOP. Merging the PR
(to main) and `finish` (tag + merge-back) are ALWAYS deliberate human steps.

⚠️ A mode flag (`-f`/`-r`/`-h`) ALWAYS means `start` for that type. NEVER map
`-r` to cleanup, abort, or any other action.

⚠️ `-r` REQUIRES a version. If none is given (e.g. bare `-r`, or `-r -a`), ASK
the user for it (AskUserQuestion) — never invent a version and never treat a
following flag like `-a` as the version. Versions keep their dots (`3.53.0` →
`release/3.53.0`); the CLI does the naming, don't pre-mangle it.
</flags>

<routing>

| User input            | CLI command                                                                  |
|-----------------------|------------------------------------------------------------------------------|
| `init <url>`          | 2-step deterministic flow — see `<init_flow>` (propose → confirm → create)    |
| `-f <name>`           | `npx --prefer-offline tsx skills/gitflow/cli/start/index.ts --spec '{"type":"feature","name":"<name>"}' --json` |
| `-r <version>`        | `npx --prefer-offline tsx skills/gitflow/cli/start/index.ts --spec '{"type":"release","name":"<version>"}' --json` |
| `-h <name>`           | `npx --prefer-offline tsx skills/gitflow/cli/start/index.ts --spec '{"type":"hotfix","name":"<name>"}' --json` |

**`start` spec fields:** `type` (required: "feature"|"release"|"hotfix"), `name` (required: branch/version name), `noPush` (bool, default: false), `dryRun` (bool, default: false).

| `commit [msg]`        | `npx --prefer-offline tsx skills/gitflow/cli/commit/index.ts --spec '{"message":"<msg>"}' --json`  |

**`commit` spec fields:** `message` (optional), `push` (bool, default: false), `noEfcore` (bool, default: false), `confirmDestructive` (bool, default: false, set after user confirms), `files` (string[], optional — scope the commit to these workdir-relative paths; omitted → stage everything; EF validation then scans only the listed files).

| `sync`                | `npx --prefer-offline tsx skills/gitflow/cli/sync/index.ts --spec '{}' --json`                    |

**`sync` spec fields:** `rebase` (bool, default: false) — for refreshing from the BASE branch prefer `update`; `sync --rebase` is legacy.

| `update`              | `npx --prefer-offline tsx skills/gitflow/cli/update/index.ts --spec '{}' --json`                  |

**`update` spec fields:** `strategy` ("merge"\|"rebase", default: "merge"), `push` (bool, default: false), `dryRun` (bool, default: false).
Safely updates the CURRENT branch from its BASE (feature/release ← develop, hotfix ← main) using the fetched `origin/<base>` ref.
Merge by default (ff when the branch has no own commits); on conflict the operation is ABORTED — the branch is left untouched — and
`guidance` explains the manual path (EF Core conflicts → `/efcore rebase-snapshot`). After `strategy:"rebase"`, surface the
force-push warning (origin diverges until a `--force-with-lease` push).

| `pr`                  | `npx --prefer-offline tsx skills/gitflow/cli/pr/index.ts --spec '{}' --json`                      |

**`pr` spec fields:** `draft` (bool, default: false), `confirmRebaseline` (bool, default: false —
overrides the migration-parity gate on a PR to main; deliberate lock-step re-baseline ONLY).

| `merge`               | `npx --prefer-offline tsx skills/gitflow/cli/merge/index.ts --spec '{}' --json`                   |

**`merge` spec fields:** `squash` (bool, optional), `merge` (bool, optional).

| `finish`              | `npx --prefer-offline tsx skills/gitflow/cli/finish/index.ts --spec '{}' --json`                  |

**`finish` spec fields:** `branch` (string, optional, defaults to current branch).

| `status` or `-s`      | `npx --prefer-offline tsx skills/gitflow/cli/status/index.ts --spec '{}' --json`                  |

**`status` spec fields:** `verbose` (bool, default: false).

| `cleanup`             | `npx --prefer-offline tsx skills/gitflow/cli/cleanup/index.ts --spec '{}' --json`                 |

**`cleanup` spec fields:** `force` (bool, default: false), `dryRun` (bool, default: false), `staleDays` (int, optional, positive).

| `abort`               | `npx --prefer-offline tsx skills/gitflow/cli/abort/index.ts --spec '{"git":true}' --json`         |
| `generate-msg`        | `npx --prefer-offline tsx skills/gitflow/cli/generate-msg/index.ts --spec '{}' --json`            |

**`cleanup`/`abort` ONLY run when the user types `cleanup`/`abort` explicitly.**
Never reach them from a `-f`/`-r`/`-h` invocation.

</routing>

<execution_discipline>
- **Without `-a` (default): run ONE step, then STOP and wait for the user.** Never
  chain steps automatically. The user drives the pace.
- **With `-a` (auto): run `start → commit → pr`, then STOP at the review gate.**
  Auto mode NEVER runs `merge` or `finish` — merging (especially the PR to **main**)
  and finalizing (tag + merge-back) are deliberate HUMAN steps after review.
  So `-r <version> -a` CREATES `release/<version>` (from develop) and opens its PR;
  it must NEVER jump straight to a merge on main. `-a` with no migrations/commits
  still creates the branch and stops.
- After review, the human runs `merge`, then `finish` — explicitly.
- Typical manual flow: `-f x` → `commit` → `pr` → (review) → `merge` → `finish`.
</execution_discipline>

<efcore_handling>
The commit / pr CLIs enforce EF Core migration rules (config-driven via
`config.efcore`; 4.x defaults: on). Handle their JSON like this:

- **`commit` returns `requiresConfirmation: true`** (a destructive migration —
  DropTable/DropColumn/DeleteData). Do NOT silently proceed and do NOT silently
  block — ASK:
  ```yaml
  AskUserQuestion:
    header: "Migration"
    question: "Destructive migration detected ({destructiveOps}). Continue?"
    options:
      - label: "Yes, I understand the risks"
      - label: "No, let me review"
  ```
  If yes → re-run commit with `confirmDestructive:true` in the spec. If no → stop.
- **`commit` succeeds with `excludedFiles`** → the migration changeset was
  incomplete (missing Migration.cs / Designer.cs / ModelSnapshot.cs), so the
  Migrations/ files were EXCLUDED and everything else committed. Tell the user to
  regenerate the migration, then commit it separately. Never treat this as fully done.
- **`commit` fails with an incomplete-migration error** → nothing BUT the broken
  changeset to commit, or migration files were already staged (exclusion
  impossible). Tell the user to regenerate.
- **`pr` fails asking to squash** (feature with >1 migration) → tell the user to run
  `/efcore squash`, then retry `pr`.
- **`pr` (release/hotfix) returns a migrations warning** (>1 migration headed to
  main) → ADVISORY, the PR is still created: surface the count + names and suggest
  a deliberate consolidation with `/efcore squash` on the release branch
  (reference = main — migrations already in main are never touched). N migrations
  on a release is a legitimate state; never treat this warning as a failure.
- **`pr` (to MAIN) fails on migration PARITY** (migrations present on main are
  MISSING from this branch) → this is the prod-regression gate, NOT a naming or
  count complaint. Do NOT retry, do NOT reach for `confirmRebaseline`. Tell the
  user the branch is missing production migrations (list them) and that the fix
  is `/gitflow update` on this branch — it merges `origin/main` — resolving the
  `Migrations/` + `ModelSnapshot.cs` conflicts by KEEPING BOTH sides, then retry
  `pr`. `confirmRebaseline:true` is legitimate ONLY when the user explicitly
  declares a lock-step re-baseline (an `/efcore squash --bruteForce` where main
  is re-baselined in the same operation) — ASK before ever passing it.
- Always surface any `warnings[]` from a result to the user.
</efcore_handling>

<migration_parity_gate>
On PRs targeting MAIN (release/hotfix), the `pr` CLI first enforces migration
parity: every migration present on the target's tree must still be present at
HEAD. Otherwise it returns `success:false` (lib/migration-parity-gate.ts).

- **Why**: `ModelSnapshot.cs` is a SINGLE file per DbContext, and release/hotfix
  → main is a real MERGE (never a squash). A release cut before a hotfix landed
  on main carries an older snapshot, so the merge can drop the hotfix's model
  and its migration file with NO git conflict — production keeps an
  `__EFMigrationsHistory` row with no code behind it, and the next scaffolded
  migration diffs against a mis-modelled schema.
- **What to do on failure**: `/gitflow update` on the branch (it merges
  `origin/main`), keep BOTH sides' migrations when resolving, retry `pr`.
- Same signal that hard-gates `/efcore squash` (`findMissingReferenceMigrations`),
  moved onto the path to production. Repos with no migrations are inert; an
  unreadable target tree fails OPEN with a loud warning.
</migration_parity_gate>

<seed_delta_gate>
On PRs targeting MAIN (release/hotfix), the `pr` CLI also enforces the
core-seed delta gate: when `.smartstack/core-seed/*.state.json` changed vs the
target without a committed SQL delta script whose header (`-- baseHash:` /
`-- newHash:`) bridges exactly that change, it returns `success:false`.

- **Why**: in generated SmartStack apps the nav/RBAC boot seed is strictly
  additive — prod data only follows renames/updates/removals through the
  delta scripts (generated by the core-seed skill's `derive-seed-delta` CLI,
  reviewed in the release PR, applied once at boot).
- **What to do on failure**: tell the user to run derive-seed-delta on the
  release branch with the release version, review + commit the generated
  `Persistence/Seeding/Scripts/{version}_{app}.sql`, then retry `pr`.
- Baseline (no state at the target yet) and repos without state files are
  exempt — the gate is inert outside generated client projects.
</seed_delta_gate>

<incident_guard>
If a `pr` or `merge` result reports a target that contradicts the branch type
(e.g. a feature PR targeting main), the CLI returns `success:false` with an
explicit error. SURFACE it loudly and tell the user to ABANDON the PR — do not
treat it as success. This is the guard against the feature→main incident; never
work around it.
</incident_guard>

<generate_msg_flow>

`generate-msg` is a two-phase process: YOU analyze, then the CLI formalizes.

**Phase 1 — Analysis (you do this):**

1. Run `git status --porcelain -uall` to get the list of changed files
2. Run `git diff --stat` to get an overview of changes
3. For each changed file, use the **Read** tool to understand the content
4. For modified files, run `git diff -- <file>` to see what changed precisely
5. Group changes by theme/module and identify the main intent
6. Determine:
   - `scope`: the primary module or component affected
   - `summary`: one-line description of what changed and WHY (max 72 chars, English)
   - Per-file: status (added/modified/deleted/renamed) + one-line summary

**Phase 2 — Formalization (CLI does this):**

Call the CLI with your analysis as structured JSON:

```bash
npx --prefer-offline tsx skills/gitflow/cli/generate-msg/index.ts --spec '{
  "branch": "<current branch>",
  "branchType": "<feature|release|hotfix|develop|main|other>",
  "scope": "<main module>",
  "summary": "<one-line summary>",
  "files": [
    {"path": "src/main/router.ts", "status": "modified", "summary": "add 7 new git operation routes"}
  ]
}' --json
```

The CLI returns a formatted conventional commit message in Markdown.

**Output:** The CLI result JSON has a `message` field. Output that message on the last line prefixed with:
```
COMMIT_MSG: <the message field from CLI result>
```

</generate_msg_flow>

<interpretation>

When the CLI returns JSON:
1. Check `success` field
2. If `false`: show `error` to the user, suggest the fix (and see `<incident_guard>` / `<efcore_handling>` for the special cases)
3. If `true`: summarize key fields in readable format
4. If `requiresConfirmation: true`: follow `<efcore_handling>`
5. If `needsInput: true` (init only): ask the user the required questions
6. Always surface `warnings[]` and show the **next step** suggestion
7. A `merge` result is trusted only on `success:true` — on Azure DevOps the CLI reads
   the PR's `status` after `az repos pr update` (exit 0 alone proved nothing: a PR left
   `active` with `mergeStatus: conflicts` used to come back as "merged" while the target
   had not moved). On `PR #N was NOT completed … mergeStatus: conflicts`, NOTHING was
   merged: tell the user to bring `origin/<target>` into the source branch (the hotfix
   merge-back case), push, then retry `merge`. Never run `finish` on that branch until
   `merge` really succeeds — it would tag the wrong commit.

</interpretation>

<init_flow>

`init <url>` builds the worktree architecture in ONE deterministic CLI call — no
per-step work, no Opus in the loop. The CLI does everything: bare-clone into
`<root>/.git`, import `main` + `develop` as worktrees (`01-Main` / `02-Develop`),
create `features/releases/hotfixes`, and write the canonical config to
`<root>/.gitflow/config.json`. You only confirm name + layout first.

**⛔ Scope guard — init creates the git INFRA and NOTHING else.** Its whole job
is the tree below: bare clone + worktrees + empty `features/releases/hotfixes`
+ `.gitflow/config.json`. It NEVER installs or scaffolds anything: no `ss init`,
no `ss install`, no `ss upgrade`, no `npm install` / `npm i -g` / `npx smartstack*`,
no fetching any published smartstack package (CLI or socle) — not even if the
cloned repo is empty, and not even if a tool seems missing. An empty worktree
after init is the EXPECTED result for a fresh repo. If the user wants the
application scaffolded, that is a SEPARATE, user-initiated step (`ss init` run
by the user inside the develop worktree) — you may mention it as a possible
next step, you never run it. If the init CLI itself cannot be found, STOP and
report the path you tried — do not install anything to "repair" it.

**Step 1 — propose (instant, touches nothing on disk):**
```bash
npx --prefer-offline tsx skills/gitflow/cli/init/index.ts --spec '{"url":"<url>","propose":true}' --json
```
Returns `proposal.name` (derived from the URL), `proposal.alternates`, `proposal.source`, `proposal.exists`.

**Step 2 — confirm with the user. Ask ONLY what a user can actually answer:**
- **Project name** — default `proposal.name`
- **Location** — where the project root goes. Default `<source>/<name>` (source = cwd). To clone INTO an existing folder whose name differs from the project (e.g. a client folder `D:/clients/TPF`), pass `root` directly. If `proposal.exists` is true the folder already holds a git repo → pick another root or pass `force: true`.
- **Folder naming** — the user's choice. Present it CONCRETELY, never as the git-jargon "organized / simple / disabled":

  ```yaml
  AskUserQuestion:
    - header: "Dossiers"
      question: "Nommage des dossiers worktree ?"
      multiSelect: false
      options:
        - label: "Numéroté — 01-Main / 02-Develop (recommandé)"
          description: "Préfixe 01-/02- (standard SmartStack)"
        - label: "Sans numéro — main / develop"
          description: "Dossiers au nom des branches, sans préfixe 01-/02-"
  ```

  → "Numéroté" sets `numbered: true`; "Sans numéro" sets `numbered: false`. That's the whole choice. The config's internal `mode` is DERIVED from it (numbered → `organized`, plain → `simple`) — never ask about `mode` directly.

**Before Step 3 — RECAP and get an explicit yes.** Show the resolved root, the detected branches, and the exact tree that will be created, using the folder names the user just chose (numbered shown here; plain = `main` / `develop`):
```
<root>/.git                          (bare repo)
<root>/01-Main      (or main)        ← <mainBranch>   (main/master, NOT the repo's default)
<root>/02-Develop   (or develop)     ← develop
<root>/{features, releases, hotfixes}
<root>/.gitflow/config.json                     (shared by all worktrees)
```
Only run Step 3 once the user confirms the path + naming + structure.

**Step 3 — create (one deterministic call, does all the work):**
```bash
npx --prefer-offline tsx skills/gitflow/cli/init/index.ts --spec '{"url":"<url>","name":"<name>","source":"<source>","numbered":<true|false>}' --json
# …or with an explicit root (folder name ≠ project name):
# --spec '{"url":"<url>","name":"<name>","root":"D:/clients/TPF","numbered":true,"force":false}'
```
On success, `structure` lists the created `root` / `main` / `develop` / `configPath`.
Show them and suggest `cd <develop>` as the next step.

If `url` is missing the CLI returns `needsInput` with the reason — ask the user for it and retry.

</init_flow>

<workflows>

```
FEATURE:  -f → commit* → sync → update? → pr ‖STOP review‖ → merge → finish  (target develop)
RELEASE:  -r <ver> → commit* → sync → pr ‖STOP review‖ → merge → finish      (+ tag + merge-back)
HOTFIX:   -h → commit* → sync → pr ‖STOP review‖ → merge → finish            (+ tag + merge-back)
```
`update` refreshes the branch from its base when `status` (or a `pr` warning)
reports the base ahead — run it before `pr` so the merge sees a current base.
`-a` automates ONLY up to `pr` (the ‖STOP‖). `merge` and `finish` are always
human-triggered after review — auto mode never crosses that line.

Release branches off develop, hotfix off main; both PR to main and merge back
to develop with a `v{version}` tag on finish.

</workflows>

<cli_structure>

Each CLI follows the Skill+CLI 4-file pattern:
```
cli/<command>/
├── types.ts      ← Zod schemas (input spec + output result)
├── validate.ts   ← Pure validation (no I/O, no git)
├── execute.ts    ← Execution (uses ../lib/, no validation)
└── index.ts      ← Orchestration: parse args → validate → execute → JSON
```

Shared utilities in `cli/lib/` (git, branch, config, worktree, provider, version, efcore, output, platform, paths).

</cli_structure>

<flags_cli>

| Flag | Description |
|------|-------------|
| `--json` | All commands support JSON output |
| `--spec` | JSON input spec (required for all commands) |
| `--workdir` | Working directory git runs in (defaults to cwd). Accepted by **every** command. |

**Targeting a repo other than the session cwd.** Each command acts on the git
repo at its working directory. When the project to act on is NOT the current
working directory — e.g. the session runs in one project but the target is a
client repo under `D:\…\<client>\<repo>` — pass
`--workdir <path-to-worktree>` and point it at the **specific worktree** (the
`develop` / `main` / feature folder), never the bare root. Omitting it makes the
command run silently against the cwd repo (wrong target), so always pass it for a
cross-repo operation.

</flags_cli>
