---
description: Phase 9 - Merge Pull Request with all checks validated
agent: gitflow-merge
model: sonnet
---

# Phase 9: MERGE - Merge PR avec validation complete

Tu es expert GitFlow. Execute le merge avec tous les checks de securite.

**Argument:** `$ARGUMENTS` = numero PR (optionnel, **SCAN AUTOMATIQUE** si absent)

---

## ETAPE 0: DETECTION DU PROVIDER GIT

> **CRITIQUE:** Detecter si le remote est GitHub ou Azure DevOps pour utiliser le bon CLI.

```bash
REMOTE_URL=$(git remote get-url origin 2>/dev/null || echo "")

if [[ "$REMOTE_URL" == *"dev.azure.com"* ]] || [[ "$REMOTE_URL" == *"visualstudio.com"* ]]; then
  GIT_PROVIDER="azuredevops"
  if [[ "$REMOTE_URL" =~ dev\.azure\.com/([^/]+)/([^/]+)/_git/([^/]+) ]]; then
    AZURE_ORG="${BASH_REMATCH[1]}"
    AZURE_PROJECT="${BASH_REMATCH[2]}"
    AZURE_REPO="${BASH_REMATCH[3]}"
  fi
  echo "Provider: Azure DevOps (org: $AZURE_ORG, project: $AZURE_PROJECT)"
elif [[ "$REMOTE_URL" == *"github.com"* ]]; then
  GIT_PROVIDER="github"
  echo "Provider: GitHub"
else
  GIT_PROVIDER="unknown"
fi
```

---

## ETAPE 1: SCAN PROACTIF (si pas d'argument)

> **COMPORTEMENT PROACTIF:** Si aucun numero de PR fourni, scanner TOUTES les PRs ouvertes pour proposer celles qui sont pretes a merger.

### 1.1 Scanner les PRs ouvertes

```bash
# Lister toutes les PRs ouvertes avec leur statut
if [ "$GIT_PROVIDER" = "azuredevops" ]; then
  # Azure DevOps - format different
  az repos pr list \
    --repository "$AZURE_REPO" \
    --status active \
    --organization "https://dev.azure.com/$AZURE_ORG" \
    --project "$AZURE_PROJECT" \
    --query "[].{number:pullRequestId, title:title, branch:sourceRefName, status:status, mergeStatus:mergeStatus}" \
    -o json
else
  # GitHub
  gh pr list --state open --json number,title,headRefName,mergeable,reviewDecision,statusCheckRollup \
    --jq '.[] | {
      number: .number,
      title: .title,
      branch: .headRefName,
      mergeable: .mergeable,
      review: .reviewDecision,
      checks: (.statusCheckRollup | if . then (map(select(.conclusion == "SUCCESS")) | length) else 0 end)
    }'
fi
```

### 0.2 Categoriser les PRs

```bash
for PR in $ALL_PRS; do
  # Extraire les infos
  NUMBER=$(echo "$PR" | jq -r '.number')
  MERGEABLE=$(echo "$PR" | jq -r '.mergeable')
  REVIEW=$(echo "$PR" | jq -r '.review')
  CHECKS_OK=$(echo "$PR" | jq -r '.checks')

  # Categoriser
  if [[ "$MERGEABLE" == "MERGEABLE" && "$REVIEW" == "APPROVED" ]]; then
    READY_TO_MERGE+=("$PR")
  elif [[ "$REVIEW" == "CHANGES_REQUESTED" ]]; then
    NEEDS_CHANGES+=("$PR")
  elif [[ "$REVIEW" != "APPROVED" ]]; then
    NEEDS_REVIEW+=("$PR")
  elif [[ "$MERGEABLE" != "MERGEABLE" ]]; then
    HAS_CONFLICTS+=("$PR")
  else
    WAITING_CI+=("$PR")
  fi
done
```

### 0.3 Afficher le tableau des PRs

```
================================================================================
                    GITFLOW MERGE - SCAN DES PULL REQUESTS
================================================================================

✅ PRETES A MERGER (approuvees + CI vert):
┌────────┬─────────────────────────────┬─────────────────────┬─────────────────┐
│ PR #   │ Titre                       │ Branche             │ Reviews         │
├────────┼─────────────────────────────┼─────────────────────┼─────────────────┤
│ #42    │ feat: add user auth         │ feature/user-auth   │ 2 approvals     │
│ #45    │ release: v1.5.0             │ release/v1.5.0      │ 1 approval      │
└────────┴─────────────────────────────┴─────────────────────┴─────────────────┘

⏳ EN ATTENTE DE REVIEW:
┌────────┬─────────────────────────────┬─────────────────────┬─────────────────┐
│ PR #   │ Titre                       │ Branche             │ Statut          │
├────────┼─────────────────────────────┼─────────────────────┼─────────────────┤
│ #43    │ feat: add orders            │ feature/add-orders  │ Awaiting review │
└────────┴─────────────────────────────┴─────────────────────┴─────────────────┘

⚠️ CHANGEMENTS DEMANDES:
┌────────┬─────────────────────────────┬─────────────────────┬─────────────────┐
│ PR #   │ Titre                       │ Branche             │ Reviewer        │
├────────┼─────────────────────────────┼─────────────────────┼─────────────────┤
│ #44    │ fix: login bug              │ hotfix/login        │ @john requested │
└────────┴─────────────────────────────┴─────────────────────┴─────────────────┘

❌ CONFLITS A RESOUDRE:
┌────────┬─────────────────────────────┬─────────────────────┬─────────────────┐
│ PR #   │ Titre                       │ Branche             │ Action          │
├────────┼─────────────────────────────┼─────────────────────┼─────────────────┤
│ #46    │ feat: refactor api          │ feature/refactor    │ Rebase requis   │
└────────┴─────────────────────────────┴─────────────────────┴─────────────────┘

================================================================================
```

### 0.4 Proposition interactive

```javascript
if (READY_TO_MERGE.length > 0) {
  // Trier par priorite: hotfixes > releases > features
  const sorted = READY_TO_MERGE.sort((a, b) => {
    const priority = { 'hotfix': 0, 'release': 1, 'feature': 2 }
    return priority[getType(a.branch)] - priority[getType(b.branch)]
  })

  AskUserQuestion({
    questions: [{
      question: "Quelle PR voulez-vous merger ?",
      header: "Merge",
      options: sorted.map(pr => ({
        label: `#${pr.number}`,
        description: `${pr.title} (${pr.branch})`
      })).concat([{
        label: "Toutes",
        description: "Merger toutes les PRs pretes (dans l'ordre recommande)"
      }]),
      multiSelect: false
    }]
  })
} else if (NEEDS_REVIEW.length > 0 || NEEDS_CHANGES.length > 0) {
  console.log("❌ Aucune PR prete a merger.")
  console.log("")
  if (NEEDS_REVIEW.length > 0) {
    console.log("→ PRs en attente de review: " + NEEDS_REVIEW.map(p => '#'+p.number).join(', '))
    console.log("  Demandez une review ou utilisez: /gitflow:8-review <numero>")
  }
  if (NEEDS_CHANGES.length > 0) {
    console.log("→ PRs avec changements demandes: " + NEEDS_CHANGES.map(p => '#'+p.number).join(', '))
    console.log("  Adressez le feedback puis re-demandez une review")
  }
  if (HAS_CONFLICTS.length > 0) {
    console.log("→ PRs avec conflits: " + HAS_CONFLICTS.map(p => '#'+p.number).join(', '))
    console.log("  Resolvez les conflits avec: git rebase origin/<target>")
  }
  exit(1)
} else {
  console.log("✅ Aucune PR ouverte!")
  console.log("→ Creez une PR avec: /gitflow:7-pull-request")
  exit(0)
}
```

### 0.5 Ordre recommande pour "Toutes"

```
ORDRE DE MERGE RECOMMANDE:
  1. Hotfixes (urgents, patches production)
  2. Releases (vers main, puis merge back)
  3. Features (par date de creation, plus anciennes d'abord)
```

---

## Pre-merge checks

### 1. Status PR

```bash
if [ "$GIT_PROVIDER" = "azuredevops" ]; then
  az repos pr show --id {number} \
    --organization "https://dev.azure.com/$AZURE_ORG" \
    --project "$AZURE_PROJECT" \
    --query "{status:status, mergeStatus:mergeStatus, reviewers:reviewers}"
else
  gh pr view {number} --json state,mergeable,mergeStateStatus,reviews,checks
fi
```

| Check | Requis | Action si echec |
|-------|--------|-----------------|
| State = OPEN/active | OUI | "PR deja mergee ou fermee" |
| Mergeable = true | OUI | "Resoudre conflits d'abord" |
| MergeStateStatus = CLEAN | OUI | Attendre CI ou resoudre |

### 2. Reviews

| Check | Condition | Action |
|-------|-----------|--------|
| Min approvals | Config: `minReviewers` | Attendre reviews |
| No changes requested | Aucun "Request changes" actif | Resoudre feedback |
| Required reviewers | Si configure | Verifier presence |

```bash
# Verifier reviews
if [ "$GIT_PROVIDER" = "azuredevops" ]; then
  az repos pr show --id {number} \
    --organization "https://dev.azure.com/$AZURE_ORG" \
    --project "$AZURE_PROJECT" \
    --query "reviewers[?vote>0]"
else
  gh pr view {number} --json reviews --jq '.reviews[] | select(.state=="APPROVED")'
fi
```

### 3. CI/CD Checks

```bash
if [ "$GIT_PROVIDER" = "azuredevops" ]; then
  # Azure DevOps - checks via policy status
  az repos pr policy list --id {number} \
    --organization "https://dev.azure.com/$AZURE_ORG" \
    --project "$AZURE_PROJECT"
else
  gh pr checks {number}
fi
```

| Status | Action |
|--------|--------|
| Tous ✓ | Continuer |
| En cours | Attendre |
| Echec | BLOQUER - afficher details |

### 4. Branch Protection

| Rule | Verification |
|------|--------------|
| Up-to-date | `git rev-list --count HEAD..origin/{base}` = 0 |
| Linear history | Si requis, rebase avant merge |
| Signed commits | Si requis, verifier signatures |

---

## EF Core Pre-merge (si applicable)

### Validation migrations

```bash
# Lister migrations dans la PR
git diff origin/{base}...HEAD --name-only | grep -E "Migrations/.*\.cs$"
```

| Check | Validation | Bloquant |
|-------|------------|----------|
| Sync ModelSnapshot | Pas de conflit avec develop | OUI |
| Build migrations | `dotnet ef migrations script` OK | OUI |
| Pas de data loss | Scan DropTable/DropColumn | WARNING |

### Si conflit ModelSnapshot detecte

```
⚠️ CONFLIT MODELSNAPSHOT DETECTE
────────────────────────────────
Le ModelSnapshot a diverge de develop.

Options:
1. Rebase + recreer migration (recommande)
2. Merge manuel (risque)

Commande recommandee:
  git rebase origin/develop
  dotnet ef migrations remove
  dotnet ef migrations add {MigrationName}
────────────────────────────────
```

---

## Execution Merge

### Strategie selon type

| Type branche | Merge strategy | GitHub | Azure DevOps |
|--------------|----------------|--------|--------------|
| `feature/*` | Squash | `--squash` | `--squash true` |
| `hotfix/*` | Merge commit | `--merge` | `--squash false` |
| `release/*` | Merge commit | `--merge` | `--squash false` |

### Commande GitHub

```bash
if [ "$GIT_PROVIDER" = "github" ]; then
  # Squash (feature)
  gh pr merge {number} --squash --delete-branch

  # Merge commit (hotfix/release)
  gh pr merge {number} --merge --delete-branch
fi
```

### Commande Azure DevOps

```bash
if [ "$GIT_PROVIDER" = "azuredevops" ]; then
  # Complete la PR (merge)
  az repos pr update --id {number} \
    --organization "https://dev.azure.com/$AZURE_ORG" \
    --project "$AZURE_PROJECT" \
    --status completed \
    --squash {true|false} \
    --delete-source-branch true

  # Note: Si merge policies bloquent, utiliser --bypass-policy (admin requis)
fi
```

### Options supplementaires

| Option | GitHub | Azure DevOps |
|--------|--------|--------------|
| Auto-merge quand CI vert | `--auto` | `--auto-complete true` |
| Supprimer branche source | `--delete-branch` | `--delete-source-branch true` |
| Override protections | `--admin` | `--bypass-policy` (danger) |

---

## Post-merge actions

### 1. Versioning (si release/hotfix)

```bash
# Lire version actuelle
VERSION=$(cat package.json | jq -r '.version')

# Creer tag
git tag -a "v$VERSION" -m "Release v$VERSION"
git push origin "v$VERSION"
```

### 2. Changelog (optionnel)

```bash
# Ajouter entree CHANGELOG
echo "## v$VERSION - $(date +%Y-%m-%d)" >> CHANGELOG.md
gh pr view {number} --json title,body >> CHANGELOG.md
```

### 3. Notifications (si configure)

| Canal | Message |
|-------|---------|
| Slack | "PR #{number} merged to {base}" |
| Teams | Idem |
| Email | Idem |

### 4. Cleanup

```bash
# Supprimer branche locale
git branch -d {branch}

# Fetch et prune
git fetch --prune
```

---

## Resume

```
MERGE COMPLETE
════════════════════════════════════════
PR:       #{number}
Titre:    {title}
Base:     {base}
Strategy: {squash|merge}

Checks passes:
  ✓ Reviews:     {count} approvals
  ✓ CI:          All green
  ✓ Conflicts:   None
  ✓ EF Core:     {OK|N/A}

Post-merge:
  ✓ Branch deleted: {branch}
  ✓ Tag created:    {tag|N/A}

════════════════════════════════════════
```

---

## Erreurs et recovery

| Erreur | Cause | Solution |
|--------|-------|----------|
| "Not mergeable" | Conflits | `git rebase origin/{base}` |
| "Checks failing" | CI rouge | Corriger et re-push |
| "Review required" | Pas d'approval | Demander review |
| "Branch protected" | Rules non respectees | Verifier requirements |
| "az: command not found" | Azure CLI manquant | `winget install Microsoft.AzureCLI` |
| "gh: command not found" | GitHub CLI manquant | https://cli.github.com |
| "Please run 'az login'" | Non authentifie | `az login` |

## Providers supportes

| Provider | CLI | Merge command |
|----------|-----|---------------|
| **GitHub** | `gh pr merge` | Squash/Merge/Rebase |
| **Azure DevOps** | `az repos pr update --status completed` | Squash ou Merge |

### Rollback si probleme post-merge

```bash
# Identifier commit merge
MERGE_COMMIT=$(git log --merges -1 --format="%H")

# Revert
git revert -m 1 $MERGE_COMMIT
git push origin {base}
```

---

## Modes

| Commande | Action |
|----------|--------|
| `/gitflow:9-merge 123` | Merge PR #123 |
| `/gitflow:9-merge 123 --auto` | Merge auto quand CI vert |
| `/gitflow:9-merge 123 --dry-run` | Simulation |
| `/gitflow:9-merge 123 --admin` | Override protections (danger) |
