---
name: ci-deploy
description: >
  Deploy an application to an environment (prod, review, dev) or rollback production.
  For mobile projects (driver, info) it offers three cumulative deploy levels — build only,
  build + deploy web, or build + deploy web + publish to the app stores (Google Play / App Store).
  Use when the user says /ci-deploy, /deploy, 'deploy en prod',
  'deploy en review', 'deploy en dev', 'deploie', 'rollback', 'rollback prod', 'mets en prod',
  'publie sur les stores', 'publish to the stores', 'build only', 'deploy web', 'release mobile',
  'déploie driver', 'deploy review all', 'déploie tout en review', 'review avec les pollers',
  or wants to deploy a specific version.
argument-hint: "<env and optional version, e.g. 'prod', 'review', 'review all', 'dev', 'prod v1.2.3', 'rollback', 'prod web', 'prod stores'>"
allowed-tools:
  - Read
  - Glob
  - Grep
  - Bash
  - Skill
  - AskUserQuestion
  - mcp__kubernetes__kubectl_get
  - mcp__kubernetes__kubectl_describe
---

Deploy a Pysae application to an environment or rollback production. Delegates job execution to the `/ci-run` skill.

## Required tools

- `glab`
- `kubectl`
- `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.

## Mode detection

Detect the execution context using `detect_context`:
```bash
CTX=$(pysae-ai-tools internal detect-context)
IS_CI=$(echo "$CTX" | jq -r '.is_ci')
PROJECT_ID=$(echo "$CTX" | jq -r '.project_id // empty')
PROJECT_PATH=$(echo "$CTX" | jq -r '.project_path // empty')
```

In CI mode (`IS_CI` is `true`):
- **Never use `AskUserQuestion`** — all required parameters (environment, version, app) must come from `$ARGUMENTS`
- **Skip production/rollback confirmations** — proceed directly if arguments are explicit
- If a required argument is missing, **fail with exit code 1** and print the missing parameter to stdout
- **Mobile deploy level is opt-in** (mobile projects, see Step 2.5 / Scenario E) — three cumulative levels selected from `$ARGUMENTS`:
  - `stores` / `--stores` → **build + deploy web + deploy store** (full release): build the binaries, deploy the webapp (`deploy_prod`), publish to Google Play / App Store.
  - `web` / `--web` → **build + deploy web**: build the binaries and deploy the webapp, **no** store publication.
  - neither flag → **build only** (safe default): build the binaries, deploy nothing. Never push to Google Play / App Store without an explicit `stores` flag.

## Step 1 : Target repository resolution

If the user mentions a specific application (e.g. "deploy op en prod", "deploy driver", "deploy l'API"), resolve the target repository, then read its deploy topology **live** from its config: `pysae-ai-tools project show --project pysae/<repo> --json` → `.k8s.services[]`. Each service has a templated `deployment` (the ArgoCD app == k8s deployment, e.g. `<env>-api-ndp`): substitute `<env>` with `dev` / `prod` / `review-<slug>` to get the actual name (e.g. `prod-api-ndp`). Resolve the GitLab project ID via `glab api "projects/pysae%2F<repo>"`.

**Default**: if no application is specified, assume `pysae/api`.

Set `PROJECT_ID` accordingly. When operating on a different repo than the current working directory, use the project ID directly with the API.

## Step 2 : Check redeploy support

For production and rollback scenarios, check if the project supports the fast `redeploy` job:
```bash
CI_YAML=$(glab api "projects/${PROJECT_ID}/repository/files/.gitlab-ci.yml/raw?ref=main" 2>/dev/null)
```
Check if the YAML includes `redeploy.auto.yml` (from `pysae/infra/gitlab-templates`). Store the result as `SUPPORTS_REDEPLOY` (true/false) for use in Scenario A and B.

If `redeploy.auto.yml` is not included, the project does not support fast redeploy — always use `deploy_prod`.

## Step 2.5 : Mobile project detection

A **mobile project** (e.g. `driver`, `info`) builds app binaries and can publish them to the app stores. Detect it from the job names in the project's CI config (reuse `CI_YAML` from Step 2; fetch it if not already loaded):

```bash
# Fetch the CI config once if Step 2 didn't (e.g. dev scenario)
CI_YAML="${CI_YAML:-$(glab api "projects/${PROJECT_ID}/repository/files/.gitlab-ci.yml/raw?ref=main" 2>/dev/null)}"

# Top-level mobile job names
MOBILE_JOBS=$(printf '%s\n' "$CI_YAML" | grep -oE '^(publish_play_store|publish_app_store|build_apk_[a-z0-9_-]+):' | tr -d ':' | sort -u)
HAS_PLAY_STORE=$(printf '%s\n' "$MOBILE_JOBS" | grep -qx 'publish_play_store' && echo true || echo false)
HAS_APP_STORE=$(printf '%s\n' "$MOBILE_JOBS" | grep -qx 'publish_app_store' && echo true || echo false)
```

The project is **mobile** (`IS_MOBILE=true`) when at least one `publish_play_store` / `publish_app_store` job exists. Record `IS_MOBILE`, `HAS_PLAY_STORE`, `HAS_APP_STORE`, and the list of `build_apk_<env>` jobs found. When `IS_MOBILE` is true and the target environment is **prod** or **dev**, follow **Scenario E** instead of Scenario A / D.

> The mobile **web deploy capability** (`HAS_WEB_DEPLOY`) is **not** detected here: the webapp ships through the shared `deploy_prod` job, which comes from an included template and is therefore absent from the raw `.gitlab-ci.yml` (the API does not resolve `include:`). It is resolved in **Scenario E** from the actual pipeline jobs instead (the same way Scenario A inspects `deploy_prod`).

## Step 3 : Scenario detection

Interpret `$ARGUMENTS` to determine the deployment scenario. **If `IS_MOBILE` is true (Step 2.5) and the target environment is prod or dev, use Scenario E** (mobile) — it supersedes Scenario A / D for those projects.

### Scenario A — Deploy to production

**Triggers**: `deploy prod`, `deploie en prod`, `mets en prod`, `prod`

1. **Find the target tag** — use the latest tag by default, or the version specified by the user:
   ```bash
   # Latest tag
   TAG=$(glab api "projects/${PROJECT_ID}/repository/tags?per_page=1" | jq -r '.[0].name')
   # Or user-specified: tag name, commit hash, or version number
   ```

2. **Find the pipeline for that tag**:
   ```bash
   PIPELINE_ID=$(glab api "projects/${PROJECT_ID}/pipelines?ref=${TAG}&per_page=1" | jq -r '.[0].id')
   ```
   If no pipeline exists for this tag, create one:
   ```bash
   glab api -X POST "projects/${PROJECT_ID}/pipeline" -f "ref=${TAG}"
   ```

3. **Choose the deploy job — prefer `redeploy` when the project supports it**:

   If `SUPPORTS_REDEPLOY` is true, list jobs in the pipeline and check statuses:
   ```bash
   JOBS=$(glab api "projects/${PROJECT_ID}/pipelines/${PIPELINE_ID}/jobs?per_page=100&include_retried=false")
   HAS_REDEPLOY=$(echo "$JOBS" | jq '[.[] | select(.name == "redeploy")] | length > 0')
   DEPLOY_PROD_STATUS=$(echo "$JOBS" | jq -r '[.[] | select(.name == "deploy_prod")][0].status // empty')
   ```

   - If `SUPPORTS_REDEPLOY` is true **and** `redeploy` job exists in the pipeline **and** `deploy_prod` has already succeeded (status `success`) → **use `redeploy`** as target job. This is much faster: it only updates the git ref and image tag on the ArgoCD app, without rebuilding or retesting.
   - Otherwise → use `deploy_prod` as target job (standard full deploy).

   Inform the user which path is taken:
   > Utilisation du job `redeploy` (deploy rapide, sans rebuild) — `deploy_prod` déjà en succès sur cette pipeline.

   or:
   > Utilisation du job `deploy_prod` (deploy complet avec build et tests).

4. **Confirm with the user** — production deployments require explicit confirmation:
   > Deploy de `<TAG>` en production pour `<app>`. Tu confirmes ?

5. Invoke `/ci-run` with the chosen target job and pipeline context.

### Scenario B — Rollback production

**Triggers**: `rollback`, `rollback prod`, `reviens en arriere`

1. **Find the current running version** on the EKS prod cluster:
   ```bash
   kubectl get deployment <k8s-deployment-name> -n pysae-prod \
     -o jsonpath='{.spec.template.spec.containers[0].image}'
   ```
   Extract the image tag from the result.

2. **Find the previous working version** — list recent tags and identify the one before the current:
   ```bash
   glab api "projects/${PROJECT_ID}/repository/tags?per_page=10" | jq -r '.[].name'
   ```
   If the user specifies a target version, use that instead.

3. **Find the pipeline for the rollback tag** — same as Scenario A step 2.

4. **Choose the deploy job — prefer `redeploy`** — same logic as Scenario A step 3: if `SUPPORTS_REDEPLOY` is true, `redeploy` exists and `deploy_prod` already succeeded on the rollback tag's pipeline, use `redeploy`. This is the typical rollback case since the previous version was already deployed once.

5. **Confirm with the user** — rollbacks are critical operations:
   > Rollback de `<current_version>` vers `<rollback_version>` sur la prod pour `<app>`. Tu confirmes ?

6. Invoke `/ci-run` with the chosen job on the rollback tag's pipeline.

### Scenario C — Deploy to review

**Triggers**: `deploy review`, `deploie en review` (the standard app services). For **every
service including the pollers/workers**: `deploy review all`, `review all`, `déploie tout en
review`, `tous les services`, `(review) avec les pollers`, or an explicit `--all` / `all` token.

1. **Pipeline context** — use `MR_IID` from `detect_context` (resolved in Mode detection).
   If no MR exists (`MR_IID` is empty), inform the user and stop.

2. **Pick the target job** — the repo may expose two review deploy jobs:
   - `deploy_review` — the default; deploys the standard app services only.
   - `deploy_review_all` — deploys **all** services, **including the pollers/workers**.

   Choose `deploy_review_all` **only when the user explicitly asked for "all" / "tous" /
   "pollers" / `--all`**; otherwise use `deploy_review`. Before selecting `deploy_review_all`,
   confirm it exists on the MR pipeline (it may be template-provided, so check the resolved
   jobs rather than grepping the raw YAML):
   ```bash
   MR_PIPELINE_ID=$(glab api "projects/${PROJECT_ID}/merge_requests/${MR_IID}" | jq -r '.head_pipeline.id // .pipeline.id // empty')
   glab api "projects/${PROJECT_ID}/pipelines/${MR_PIPELINE_ID}/jobs?per_page=100" \
     | jq -e '[.[] | select(.name == "deploy_review_all")] | length > 0' >/dev/null \
     && echo "deploy_review_all available" || echo "absent"
   ```
   If the user asked for "all" but `deploy_review_all` is absent for this repo, fall back to
   `deploy_review` and tell them `deploy_review_all` isn't defined here.

3. Invoke `/ci-run` with the chosen job.

### Scenario D — Deploy to dev

**Triggers**: `deploy dev`, `deploie en dev`

1. **Pipeline context** — use the pipeline for the `main` branch (or user-specified ref):
   ```bash
   PIPELINE_ID=$(glab api "projects/${PROJECT_ID}/pipelines?ref=main&per_page=1" | jq -r '.[0].id')
   ```
   If the user specifies a version (tag, commit hash), use that ref instead of `main`.

2. **Target job**: the deploy job for the dev environment (check `.gitlab-ci.yml` for the exact name — usually `deploy_dev` or similar)

3. Invoke `/ci-run` with the target job and pipeline context.

### Scenario E — Deploy a mobile app (driver / info)

**Applies when** `IS_MOBILE` is true (Step 2.5) and the target environment is **prod** or **dev**. A mobile app ships on up to three independent tracks: the **webapp** (the `web` track, deployed to Kubernetes via the same `deploy_prod` job as a plain web app), and the **native binaries** (`build_apk_<env>`), which can be published to the **stores** (`publish_play_store` / `publish_app_store`). The user picks **how far to go** along three cumulative levels:

| Level | Native build | Web deploy (`deploy_prod`) | Store publish |
|-------|:---:|:---:|:---:|
| **Build only** | ✅ | — | — |
| **Build + deploy web** | ✅ | ✅ | — |
| **Build + deploy web + deploy store** | ✅ | ✅ | ✅ |

1. **Resolve the pipeline** — same as the web scenario for that environment: the tag's pipeline for **prod** (Scenario A steps 1–2), the `main` (or user ref) pipeline for **dev** (Scenario D step 1). Once the pipeline is known, **resolve the web deploy capability from its actual jobs** (the raw YAML can't be grepped — `deploy_prod` comes from an included template). This is the reliable source, identical to how Scenario A step 3 inspects `deploy_prod`:
   ```bash
   JOBS=$(glab api "projects/${PROJECT_ID}/pipelines/${PIPELINE_ID}/jobs?per_page=100")
   HAS_WEB_DEPLOY=$(echo "$JOBS" | jq -e '[.[] | select(.name == "deploy_prod")] | length > 0' >/dev/null && echo true || echo false)
   ```

2. **Pick the build job** — match the `build_apk_<env>` job to the target environment (e.g. `build_apk_prod` for prod, `build_apk_dev` for dev). If no exact match exists among the detected `build_apk_*` jobs, list them and ask the user which one (local) or fail with the available names (CI).

3. **Decide the deploy level**:
   - **Betas never publish to the stores** — when the target version/tag is a **beta** prerelease (`vX.Y.Z-<label>.N` with an alpha/beta/rc label, e.g. `v6.0.0-beta.1`), the store level is **off** and is **not even offered**: a beta ships the build and the webapp (`build + deploy web`), never the stores. Force level **build + deploy web** when `HAS_WEB_DEPLOY` is true, otherwise **build only** — do not prompt. (The final `vX.Y.Z` release offers all levels normally.)
     - **A hotfix is store-bound, not a beta** — a `-hotfix.N` or bare `-N` suffix (e.g. `v5.3.16-1`) is a post-release rebuild of the same `X.Y.Z` whose whole point is to reach the stores (it mirrors the Android versionCode scheme — a hotfix stays at the final stage and outranks the bare release). Treat it like a **final release**: offer every level, including store publication. Do **not** force-drop the store level for a hotfix. On a **support release** (older line, web is a single live deployment) the store/APK tracks are the only targets anyway — see `/ci-release` support mode.
   - **Local mode** — ask with `AskUserQuestion` (drop the "deploy web" choices when `HAS_WEB_DEPLOY` is false — the webapp can't be deployed, so only **Build uniquement** / **Build + stores** remain):
     > Jusqu'où déployer `<app>` `<TAG>` ?
     > - **Build + deploy web + stores** — build natif, déploiement de la webapp (`deploy_prod`) **et** publication sur les stores (`publish_play_store` / `publish_app_store` présents). La release mobile complète.
     > - **Build + deploy web** — build natif et déploiement de la webapp, **sans** publier sur les stores.
     > - **Build uniquement** — build natif des binaires, aucun déploiement.
   - **CI mode** — read the level from `$ARGUMENTS`: `stores` / `--stores` → full (build + web + store); `web` / `--web` → build + deploy web; neither → build only. Never publish to the stores without an explicit `stores` flag.

   Record the chosen level as `DEPLOY_WEB` (true/false) and `PUBLISH_STORE` (true/false). `PUBLISH_STORE=true` implies `DEPLOY_WEB=true` (the full level is cumulative). Guard against impossible levels: never set `DEPLOY_WEB=true` when `HAS_WEB_DEPLOY` is false, never set `PUBLISH_STORE=true` when neither store job exists.

4. **Choose target job(s)** and delegate to `/ci-run`. Build the job list from the level, then pass **all of them to a single `/ci-run` invocation** so shared `needs:` (the `build_apk_<env>` dependency) trigger once and the jobs run concurrently:
   - **Always** include the `build_apk_<env>` job (every level builds).
   - **`DEPLOY_WEB=true`** → add the web deploy job. Reuse Scenario A step 3's logic to pick `redeploy` vs `deploy_prod` (fast redeploy when `SUPPORTS_REDEPLOY` and `deploy_prod` already succeeded on this pipeline; otherwise `deploy_prod`).
   - **`PUBLISH_STORE=true`** → add the **publish jobs that exist** (`HAS_PLAY_STORE` / `HAS_APP_STORE`). Drop whichever is absent (Android-only or iOS-only projects).
     ```bash
     # e.g. full level on an app with both stores + redeploy unavailable:
     pysae-ai-tools ci run run build_apk_prod deploy_prod publish_play_store publish_app_store
     ```
     `/ci-run` resolves the `needs:` chain, so `build_apk_<env>` runs first automatically.

5. **Confirm before any prod side effect** — a prod web deploy and (above all) a store publication are public, hard-to-reverse releases; in local mode confirm explicitly with the resolved level before triggering:
   > Déploiement de `<TAG>` pour `<app>` — **<niveau choisi>** (`<liste des jobs>`). Tu confirmes ?

6. **Verify and report**:
   - **`DEPLOY_WEB=true`** → run the Kubernetes cross-check from the top-level **Step 5 (Verify and report)** (`/ci-deploy-status <env>`) on the webapp deployment, exactly like a web app, and surface its verdict + URL.
   - **`DEPLOY_WEB=false`** (build only) → skip the `/ci-deploy-status` cross-check (no cluster deploy happened) and report the build job outcome only.
   - Always report the build job link; for each store the publish job link and status (when `PUBLISH_STORE=true`).

## Step 4 : Execute via /ci-run

Once the scenario is resolved (target job, pipeline, and project), delegate execution to the `/ci-run` skill. Pass the exact job name so `/ci-run` handles dependency resolution and job execution.

If `/ci-run` reports a failure, first check whether it matches a known Pysae transient (see `/ci-debug` -> `references/failure-patterns.md`, section "Pysae transient quirks"): `FailedPrecondition` / "another operation is already in progress" (ArgoCD sync race on first deploy) or `EvictionByEvictionAPI` (runner evicted after the deploy already applied). On either, **retry the same job once** before escalating. If it fails again, or the failure is anything else, propose `/ci-debug` to diagnose.

## Step 5 : Verify and report

After the deployment job completes:

- **Auto-chain `/ci-deploy-status <env>`** (dev and prod) to cross-check live pod health (Running + Ready), the deployed image tag, and rollout status before reporting. Surface its verdict (`DEPLOYED` / `DEPLOYED_UNHEALTHY` / `DEPLOYING`) together with the URL in a single message - this is the pod validation users otherwise ask for by hand. Skip for `review` (no dedicated cluster verdict needed).
- **Idempotent re-run**: if invoked again for the same target and `/ci-deploy-status` already returns `DEPLOYED`, the deploy has landed - report it and stop instead of re-triggering the job. This keeps a resumed/retried run safe.
- Confirm the deployed version and environment
- Include the pipeline and job links
- **Always fetch and display the environment URL** from the deployment job:
  ```bash
  glab api "projects/${PROJECT_ID}/pipelines/${PIPELINE_ID}/jobs?per_page=100" | \
    jq -r '.[] | select(.name == "<deploy_job_name>") | .environment.external_url // empty'
  ```
  If available, display it prominently — this is the URL the user expects when they ask for it

## Rules

- **Production and rollback require explicit user confirmation** — never deploy to prod or rollback without asking (local mode only; CI mode skips confirmation)
- **Review and dev do not require confirmation** — execute directly
- **Mobile deploy is a three-level choice** — for mobile projects the user picks how far to go: **build only** (safe default), **build + deploy web**, or **build + deploy web + deploy store** (full release). Local: `AskUserQuestion`; CI: `web` / `stores` arguments. The levels are cumulative — store implies web. Never publish to the stores or deploy the webapp to prod without the explicit level being chosen.
- **Store publication is never automatic** — only trigger `publish_play_store` / `publish_app_store` at the full level (local opt-in or CI `stores` argument). A build without publication is the safe default.
- **Publish only the stores that exist** — pass the present publish jobs to a single `/ci-run` call; never assume both Play Store and App Store are configured.
- **Default app is API** — if the user doesn't specify which app, assume `pysae/api`
- **Language**: French for communication, English for commands
- **Delegate to /ci-run** for all job execution — do not duplicate job triggering logic
- If `glab` is not available or fails, inform the user and stop

$ARGUMENTS
