---
name: glab-mr-merge
description: >
  Merge a GitLab merge request via glab CLI.
  Use when the user says /glab-mr-merge, 'merge cette MR', 'merge la MR',
  'fusionne la MR', 'merge !42', or wants to merge an approved MR.
argument-hint: "[optional: MR !IID — defaults to the MR for the current branch]"
allowed-tools:
  - Read
  - Bash
  - Skill
  - ExitWorktree
---

Merge a GitLab merge request using the `glab` CLI.

## Required tools

- `glab`
- `jq`

Run `pysae-ai-tools tools require <tools>` before starting; if it exits non-zero, abort and report the install command from stderr to the user.

## How it works

All the dispatch on `detailed_merge_status` (auto-fix draft, transient polls, blockers, unknown states, the 405 raw-API fallback, and the workflow transition of the linked issue) lives in `pysae-ai-tools glab merge-mr`. The skill's job is to resolve the MR IID, run the script, and react to its outcome.

The script's exit codes:
- **0** — `merged`. Done.
- **2** — `needs_skill`. The script can't proceed without delegating to another skill (rebase or CI). The JSON payload tells you which skill and which args.
- **1** — `blocked` or `error`. Stop and report.

## Mode detection

```bash
CTX=$(pysae-ai-tools internal detect-context $ARGUMENTS_REFS)
IS_CI=$(echo "$CTX" | jq -r '.is_ci')
MR_IID=$(echo "$CTX" | jq -r '.mr_iid // empty')
MR_SOURCE_BRANCH=$(echo "$CTX" | jq -r '.mr_source_branch // empty')
DEFAULT_BRANCH=$(echo "$CTX" | jq -r '.default_branch // "main"')
PROJECT_URL=$(echo "$CTX" | jq -r '.project_url // empty')
# Stable routing (Slack channel) lives behind `project show`, not detect-context.
TECH_SLACK_CHANNEL_ID=$(pysae-ai-tools project show slack.tech_channel_id)
```

If `MR_IID` is empty, inform the user and stop.

Squash and source-branch removal are **not** controlled by this skill: GitLab applies the project-level defaults (`squash_option`, `remove_source_branch_after_merge`).

## Process

1. **Resolve MR IID** as above.

2. **Run the merge script** with `--json` so you can dispatch on the structured outcome:
   ```bash
   OUTCOME=$(pysae-ai-tools glab merge-mr "$MR_IID" --json)
   STATUS=$(echo "$OUTCOME" | jq -r '.status')
   ```
   Read the rest of the JSON only when the status requires it (`sha`, `mr_url`, `skill`, `skill_args`, `blocker`, `message`, `detailed_merge_status`).

3. **Dispatch on `STATUS`** (loop max 5 times, the script already caps its own internal retries):

   - **`merged`** → success. Print the human-readable form for the user:
     ```bash
     pysae-ai-tools glab merge-mr "$MR_IID"  # second call only if you want pretty output
     ```
     In practice just echo `✓ MR mergée : <mr_url> (<sha[:8]>)`, plus the issue transition line from `.issue_transitioned` when present. Then **update the MR's Slack message** (step 3b) and run the **post-merge cleanup** (step 4), and stop.

     **Step 3b — mark the Slack message as merged.** If the MR was announced on Slack (review request / AI-validation post), add a « Mergée » line to it. Best-effort — never fail the merge over this; skip silently when no message or no token.
     ```bash
     if [ -z "$TECH_SLACK_CHANNEL_ID" ]; then
       echo "[slack] mark-merged skipped — tech channel not configured (slack.tech_channel_id in .pysae-ai-tools.yaml)" >&2
     elif [ -z "$PROJECT_URL" ]; then
       echo "[slack] mark-merged skipped — project URL unknown" >&2
     else
       if [ -n "${CI:-}" ]; then
         eval "$(pysae-ai-tools env resolve --set SLACK_BOT_TOKEN)" 2>/dev/null
       else
         eval "$(pysae-ai-tools env resolve --set SLACK_USER_TOKEN)" 2>/dev/null \
           || eval "$(pysae-ai-tools env resolve --set SLACK_BOT_TOKEN)" 2>/dev/null
       fi
       pysae-ai-tools slack mark-merged \
         --channel "$TECH_SLACK_CHANNEL_ID" \
         --project-url "$PROJECT_URL" \
         --mr-iid "$MR_IID" || true
     fi
     ```
     The command finds the message by MR URL and inserts `:twisted_rightwards_arrows: *Mergée*` just above the MR link (below `Validée par IA` when present). It is idempotent and a no-op when the MR was never posted.

   - **`needs_skill`** → invoke the named skill **without asking the user for confirmation** (no `AskUserQuestion`, no « tu veux que je rebase ? »):
     - `skill == "glab-mr-rebase"` → `/glab-mr-rebase !<MR_IID>`. The sub-skill rebases on target, auto-resolves conflicts (with merge fallback if too divergent), and pushes. It also re-applies your approval if the force-push reset it.
     - `skill == "ci-run"` → `/ci-run <skill_args[0]>` (typically `start`). The script never assumes a project requires a pipeline: it **tries the merge directly first** and only asks for `ci-run` when the API actually refused the merge because the project enforces a passing pipeline. `/ci-run` plays the manual gate (`start`, which gates `validate-docker`, `validate-changelog`, `lint`, `test`, …), chains the `needs:` dependents, and surfaces real progress (job names, durations, links) instead of a silent wait.
     - After the sub-skill returns successfully, **re-run step 2** to resume the merge. If it fails, report and stop.

   - **`blocked`** → report the raw blocker and the `message` field, then stop. The script already collapses the eleven hard-blocker statuses, transient timeouts, and unknown statuses into this branch — never try to bypass them.

   - **`error`** → report the `message`, then stop.

4. **Post-merge cleanup** (only after a `merged` outcome, both in local and CI mode) — leave the working copy clean so the user (or the next skill) can start a new task immediately:

   ```bash
   BRANCH_TO_DELETE="${MR_SOURCE_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}"
   GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
   GIT_COMMON_DIR=$(git rev-parse --git-common-dir 2>/dev/null)
   ```

   - **Linked worktree** (`$GIT_DIR` ≠ `$GIT_COMMON_DIR`): call `ExitWorktree` to remove the worktree and return to the main repo. The runtime tool handles the `cd` and worktree teardown.
   - **Main repo** (no worktree): switch back to the default branch and delete the local source branch:
     ```bash
     git checkout "$DEFAULT_BRANCH"
     git branch -D "$BRANCH_TO_DELETE"  # force delete: squash merges leave the branch "unmerged" locally even though GitLab merged it
     ```
   - If `BRANCH_TO_DELETE` equals `$DEFAULT_BRANCH` (rare — somebody merged the default branch into itself, or detect_context fell back to HEAD on the default branch), skip the delete and just stay where you are.
   - Failures in this step are not fatal — log them and continue. The merge itself succeeded; cleanup is best-effort.

5. **CI mode** — same logic, but never invoke skills that depend on user input. `/glab-mr-rebase` and `/ci-run` already detect CI mode internally; if either fails in CI, exit non-zero with the script's last `message` on stderr.

## Rules

- **Single source of truth** — never re-implement the dispatch in this skill; always go through `pysae-ai-tools glab merge-mr`.
- **Auto-delegate** — on `needs_skill`, invoke the sub-skill immediately, never ask the user.
- **Loop cap** — the skill iterates at most 5 times over (script → sub-skill → script …). Past that, report and stop.
- **Language**: messages en français, commandes en anglais.

$ARGUMENTS
