---
name: code-autopilot
description: >
  Full autonomous pipeline for a single GitLab issue: implement it, run a review loop until
  APPROVED, validate CI, then merge the MR. Delegates implementation to /code-implement and
  review to /code-review-fix. Use when the user says /code-autopilot, /autopilot, 'auto-pilot',
  'pilote automatique', 'pilote ce ticket', 'implémente et merge', 'implémente et review',
  'fais tout le ticket', 'gère ce ticket de bout en bout', 'implement and merge',
  'implement and review', 'implement and deploy', 'take this issue end to end',
  'do the whole ticket', or wants the full end-to-end flow from one issue to a merged MR.
  Distinct from /code-autopilot-batch, which drains many agent::ready tickets in a loop —
  /code-autopilot handles exactly one issue you point it at.
argument-hint: "<GitLab issue IID or URL> [--approve=auto|ask|skip] [--merge=auto|ask|skip] [--deploy=dev|skip] [--stop-before-merge]"
allowed-tools:
  - Bash
  - Skill
  - AskUserQuestion
---

Full autonomous pipeline: from GitLab issue to approved MR with CI validation.

## How I work

**Inputs I need**
- A GitLab issue reference in `$ARGUMENTS`: full URL preferred (`https://gitlab.com/pysae/shift/app/-/issues/252`), IID accepted only when the current repo context is unambiguous. Short refs like `pysae/shift/app#252` are NOT supported by downstream skills and will fail.
- Optional `--approve=auto|ask|skip` flag controlling the MR approval at Step 3 (only effective in local mode — CI never auto-approves regardless):
  - `auto` — approve automatically once the review loop converges (used by `/code-autopilot-batch`).
  - `ask` — prompt the user before approving.
  - `skip` — never approve; the MR is left waiting for a human reviewer.
  - **Default**: `auto` in all modes — approve is low-risk (it just stamps the MR; nothing ships until Step 6 merges). Override with `--approve=ask` or `--approve=skip` when you specifically want a human stamp.
- Optional `--merge=auto|ask|skip` flag controlling the merge gate at Step 6:
  - `auto` — merge immediately once the gate conditions pass. Used by `/code-autopilot-batch`, which always injects `--merge=auto`.
  - `ask` — prompt the user before merging (default in local manual mode).
  - `skip` — never merge; emit `status: escalated` so a human can take over.
  - **Default**: `ask` in local mode, `auto` in CI mode (`AskUserQuestion` is forbidden in CI, so a fallback to `auto` keeps the pipeline unblocked).

The two flags are independent — typical combinations: `--approve=auto --merge=ask` (auto-approve but human-gated merge), `--approve=ask --merge=skip` (full human review pre-approve, no merge), `--approve=auto --merge=auto` (full autonomy, what `/code-autopilot-batch` uses).

- Optional `--stop-before-merge` flag (boolean): run everything **through Step 5 (CI green) and approval**, then **stop before Step 6 merge** and emit `status: ready_to_merge` with `mr_iid` set. Distinct from `--merge=skip` (which emits `escalated` "merge skipped", read as a blocker): `ready_to_merge` is a *success-so-far, deferred-merge* state used by the parallel `/code-autopilot-batch`, whose serial merge-gate merges the MR later. Takes precedence over `--merge` (merge is not attempted). Approval still runs per `--approve` (default `auto`) so the MR is left **approved + green**, ready to merge. Step 6b (deploy) is skipped (nothing merged).

**What I produce**
- A merged MR on the target branch (or an escalated MR if blocked).
- A single `<<<AUTOPILOT_RESULT ... AUTOPILOT_RESULT>>>` JSON footer on every exit path — parsed by `/code-autopilot-batch`. Status is one of `success` (merged + issue closed), `ready_to_merge` (approved + green, merge deferred — only with `--stop-before-merge`), `escalated` (human required), `failed` (technical tool failure).

**What I never do**
- Never call `AskUserQuestion` in CI mode (`IS_CI == "true"`). Sub-skills must fail or best-effort, never escalate to a human.
- Never skip Step 1 ready-check, even if the user insists the ticket is ready.
- Never merge unless Step 3 ended APPROVED AND Step 5 CI is green.
- Never exit without emitting the AUTOPILOT_RESULT footer, even on crash.
- Never modify a ticket's acceptance criteria. Missing AC → `status: escalated`.

**When I delegate** (strict order)
1. `/code-implement` — read issue, set In Progress, branch, code, MR.
2. `/code-review-fix` — review/fix loop, max 5 iterations.
3. `/code-quality` — only if Step 2 produced commits.
4. `/ci-run` or `/ci-deploy` — `start` job by default; `review` deploy only if user explicitly asked AND the change touches the app (not tooling).
5. `/glab-mr-merge` — final merge with project defaults.
6. `/ci-deploy dev` - **only when `--deploy=dev`** - post-merge dev deploy + pod-health verdict (otherwise skipped).

**When I escalate** (`status: escalated`, `escalation_reason` ≤ 200 chars)
- Step 1 ready-check returns `ready: false`.
- Review loop exceeds 5 iterations without converging.
- Any required job in the Step 5 pipeline fails and `/ci-debug` cannot identify a recoverable cause.
- Merge blocked by unresolved conflict, missing approval, or draft state.

**Common failures**
- `glab` auth expired (token parser bug on year 2026) → emit `status: failed` with the install command for re-auth.
- Ticket arrives without `agent::ready` label → should never happen; `/code-autopilot-batch` filters upstream. If it does, escalate.
- Footer JSON missing in output → P0 bug, the batch orchestrator will mark the run unparseable.

## 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.

## Mode detection

```bash
CTX=$(pysae-ai-tools internal detect-context --local)
IS_CI=$(echo "$CTX" | jq -r '.is_ci')

# Parse the --approve and --merge flags out of $ARGUMENTS, then strip them
# so $ISSUE_REF carries only the GitLab issue ref for downstream skills.
APPROVE_MODE=$(echo "$ARGUMENTS" | grep -oE -- "--approve=(auto|ask|skip)" | sed 's/--approve=//' | tail -n1)
MERGE_MODE=$(echo "$ARGUMENTS" | grep -oE -- "--merge=(auto|ask|skip)" | sed 's/--merge=//' | tail -n1)
DEPLOY_MODE=$(echo "$ARGUMENTS" | grep -oE -- "--deploy=(dev|skip)" | sed 's/--deploy=//' | tail -n1)
STOP_BEFORE_MERGE=$(echo "$ARGUMENTS" | grep -oE -- "--stop-before-merge" | head -n1)
ISSUE_REF=$(echo "$ARGUMENTS" | sed -E 's/--(approve|merge)=(auto|ask|skip)//g; s/--deploy=(dev|skip)//g; s/--stop-before-merge//g' | tr -s ' ' | sed 's/^ //; s/ $//')

# APPROVE_MODE defaults to 'auto' in every mode — approve is low-risk
# (it only stamps the MR; nothing ships until Step 5 merges).
[ -z "$APPROVE_MODE" ] && APPROVE_MODE="auto"
# MERGE_MODE defaults to 'ask' in local, 'auto' in CI — merge is the
# point of no return, so manual runs ask by default.
if [ -z "$MERGE_MODE" ]; then
  [ "$IS_CI" = "true" ] && MERGE_MODE="auto" || MERGE_MODE="ask"
fi
# In CI, AskUserQuestion is forbidden — collapse any 'ask' to 'auto'.
[ "$IS_CI" = "true" ] && [ "$APPROVE_MODE" = "ask" ] && APPROVE_MODE="auto"
[ "$IS_CI" = "true" ] && [ "$MERGE_MODE" = "ask" ] && MERGE_MODE="auto"
# DEPLOY_MODE defaults to 'skip' everywhere: autopilot is issue->merge by
# default; pass --deploy=dev to extend through the dev deploy + pod verdict.
[ -z "$DEPLOY_MODE" ] && DEPLOY_MODE="skip"
# Slack coherence: when the merge is immediate (MERGE_MODE=auto), the
# pre-merge "validée par IA" post from /glab-mr-approve would read as a
# review request for an MR merged seconds later — incoherent noise. Suppress
# it (--notify=false propagated through /code-review-fix) and post a single
# post-merge summary instead (Step 6).
# Suppress the pre-merge "validée par IA" post when the merge is immediate (auto) OR
# deferred to the batch merge-gate (stop-before-merge) — a summary comes at real merge.
if [ "$MERGE_MODE" = "auto" ] || [ -n "$STOP_BEFORE_MERGE" ]; then NOTIFY_FLAG="--notify=false"; else NOTIFY_FLAG=""; fi
```

Use `$ISSUE_REF` (the cleaned-up arguments without the gate flags) when delegating to `/code-implement` and other sub-skills.

In CI mode (`IS_CI == "true"`), all delegated skills inherit the headless behavior:
- `/code-implement`: infers type automatically, uses worktree, no questions
- `/glab-mr`: skips confirmation, creates MR directly
- `/code-review-fix`: fails with exit code 1 after 5 iterations instead of asking
- `/glab-mr-approve`: skips manual testing gate
- **Never use `AskUserQuestion`** — if a sub-skill would normally escalate to the user, it must fail or make a best-effort decision instead

## Step 1 : Prerequisite gate

Before delegating to `/code-implement`, verify the ticket prerequisites:

```bash
READY_JSON=$(pysae-ai-tools glab issue-ready-check "$ISSUE_REF" --apply --json)
READY=$(echo "$READY_JSON" | jq -r '.ready')
```

If `READY == "false"`:
- Read the violations from `$READY_JSON` (`.violations[]`)
- Emit the `AUTOPILOT_RESULT` footer (see Step 7) with:
  - `status: escalated`
  - `escalation_reason: "ticket prerequisites missing: <short summary of violations>"`
  - `mr_url: null`, `mr_iid: null`
- Stop immediately. Do NOT proceed to Step 2.

If `READY == "true"`, continue.

## Step 2 : Implement and create MR

Invoke the `/code-implement` skill with the issue reference from `$ISSUE_REF`. This handles:
- Reading the issue and detecting type
- Setting the issue to In Progress
- Creating the branch
- Analyzing and planning
- Implementing with atomic commits
- Running tests/linters
- Creating the MR

Wait for `/code-implement` to complete before proceeding.

## Step 3 : Review and fix loop (approve gated by `--approve`)

Once the MR is created, invoke the `/code-review-fix` skill with the `COMMIT_PREFIX` as argument. It handles the code review, one commit per fix, and the re-review loop until the verdict is APPROVED.

Branch on the resolved `APPROVE_MODE` for the approval step that follows. Approval, when it happens, is always non-interactive (`/glab-mr-approve --ask=false`) since `/code-autopilot` itself has already decided whether a human needed to be consulted.

- **`auto`** — invoke `/code-review-fix --ask=false ${NOTIFY_FLAG}`. The sub-skill runs the review loop and delegates to `/glab-mr-approve --ask=false` (propagating both flags) once the verdict is APPROVED. No manual-testing prompt; with `--notify=false` (auto-merge runs) no pre-merge Slack post either — Step 6 posts the post-merge summary.
- **`ask`** — invoke `/code-review-fix --skip-approve` so the loop runs but no approval is posted. After the verdict is APPROVED, prompt the user via `AskUserQuestion` (e.g. « La review est convergée. On approuve la MR ? »). On yes, delegate to `/glab-mr-approve --ask=false`. On no, treat as `skip` for the approve step (continue to Step 3 without approving — the merge gate at Step 5 will likely fail without approval, which is the desired outcome).
- **`skip`** — invoke `/code-review-fix --skip-approve`. Do not approve. Step 5 will most likely block on missing approval (this is the intent: hand off to a human reviewer).

## Step 4 : Re-validate after review fixes

If the `/code-review-fix` loop produced commits (i.e. there were review fixes), invoke `/code-quality` again with the `COMMIT_PREFIX` to ensure the fixes didn't break anything. Skip this step if the review was APPROVED on the first iteration without any fixes.

## Step 5 : Run CI pipeline

Once the `/code-review-fix` skill completes (review **APPROVED**):

- **Default**: invoke the `/ci-run` skill with the `start` job on the MR pipeline
- **If the user explicitly requested a deployment** (e.g. "deploy", "déploie", "lance le deploy") **AND the changes affect the application** (not just tooling, scripts, CI config, dev utilities, or non-deployed code): invoke the `/ci-deploy` skill with `review` instead
- **If the changes only concern tooling** (e.g. `tooling/`, scripts, dev utilities, CLI commands, non-deployed code): always use `/ci-run` with `start`, even if the user requested a deployment — a review deploy would be pointless

### Wait for the full pipeline, not just the gate

`start` is **only a manual gate** — it unlocks the rest of the pipeline (tests, build, lint, security scans, etc.) and finishes almost instantly. The actual validation happens in the downstream jobs. Likewise, a `/ci-deploy` invocation triggers a chain of dependent jobs, not a single one.

After triggering `/ci-run` (start) or `/ci-deploy`:

1. **Wait for the entire pipeline to finish** — not just the triggered job. Use `pysae-ai-tools ci run wait` to block until the pipeline reaches a terminal state (`success`, `failed`, `canceled`). When you run as a `/code-autopilot-batch` subagent, this wait may hand control back to the orchestrator (async monitor) before the pipeline is terminal — that is expected: the orchestrator resumes you once it is done. On resume, continue the flow from here; do **not** treat the yield as completion and do **not** emit the footer until the pipeline is actually terminal.
2. **Inspect the status of every job** in the pipeline once it's terminal — `pysae-ai-tools ci run status` for the overview, `pysae-ai-tools ci run jobs` for the per-job breakdown. Collect the list of failed jobs along with their URLs.
3. **If any job failed**, invoke `/ci-debug <failed-job-url>` for **each** failed job (in parallel when possible). Aggregate the diagnostics into a single report for the user.
4. **Treat the pipeline as failed** if *any* required job failed — Step 5 must not proceed. Escalate with `status: escalated` and `escalation_reason: "CI pipeline failed: <jobs>"` if `/ci-debug` cannot identify a recoverable cause.
5. **Treat the pipeline as success** only if every required job reached `success`. Optional/manual jobs that remain in `manual` or `skipped` are fine — they do not block the merge.

**Note**: when the user asks for the "review URL", they mean the deployed review environment URL (output by the `/ci-deploy` skill after `deploy_review` completes), NOT the Slack message or MR link.

## Step 6 : Merge the MR (gated by `--merge`)

Only execute this step if **both** of the following hold:
- The review loop in step 3 ended with an **APPROVED** verdict
- The entire CI pipeline in step 5 reached `success` (every required job green)

**`--stop-before-merge` short-circuit**: if `STOP_BEFORE_MERGE` is set, do **not** merge. The MR is approved + green — emit the Step 7 footer with `status: ready_to_merge`, `mr_url` and `mr_iid` set, `escalation_reason: null`, then stop (skip the merge, the post-merge Slack summary, and Step 6b). This is the parallel-batch path: the batch's serial merge-gate merges it later. Only when both gate conditions above hold; otherwise fall through to the normal escalation (a non-green/unapproved MR is never `ready_to_merge`).

Otherwise, branch on the resolved `MERGE_MODE` (from `Mode detection`):

- **`auto`** — invoke `/glab-mr-merge` immediately with the MR IID. The skill re-checks the MR state (mergeable, no draft), handles rebase automatically if conflicts are detected (delegates to `/glab-mr-rebase`), then merges with the project-level defaults (no flags passed).
- **`ask`** — ask the user via `AskUserQuestion` (e.g. « La MR est prête à merger. On y va ? »). On a positive answer, behave like `auto`. On a negative answer, behave like `skip`.
- **`skip`** — do not merge. Emit the `AUTOPILOT_RESULT` footer with `status: escalated`, `escalation_reason: "merge skipped (--merge=skip or user declined)"`, `mr_url` and `mr_iid` set. Then stop.

If the merge fails (missing approvals, blocked by CI, unresolved conflicts after rebase), report the blocker and emit `status: escalated`.

### Post-merge Slack summary (only when `MERGE_MODE == "auto"`)

After a **successful** merge with `MERGE_MODE == "auto"`, post **one** Slack message to the repo tech channel — this replaces the pre-merge notification suppressed by `--notify=false`:

```bash
# The tech Slack channel is stable config (behind `project show`), not detect-context.
TECH_CHANNEL_ID=$(pysae-ai-tools project show slack.tech_channel_id)
if [ -z "$TECH_CHANNEL_ID" ]; then
  echo "[slack] post-merge summary skipped — tech channel not configured (slack.tech_channel_id in .pysae-ai-tools.yaml)" >&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 post-message --channel "$TECH_CHANNEL_ID" \
    --text ":robot_face: *Mergée par autopilot* — <MR_URL|!MR_IID TITLE> (Closes #ISSUE_IID). Review IA convergée, pipeline verte." || true
fi
```

Substitute `MR_URL`, `MR_IID`, `TITLE`, `ISSUE_IID` with the real values. Best-effort: a Slack failure must never flip the run status — the command logs the cause on stderr (`[slack] message not posted — …`); continue regardless. **Skip this post when `MERGE_MODE != "auto"`** (interactive flows already got the approve-time notification from `/glab-mr-approve`).

## Step 6b : Post-merge deploy and verify (gated by `--deploy`)

Run this only after a successful merge in Step 6, and only when `DEPLOY_MODE == "dev"` (default is `skip`):

1. Invoke `/ci-deploy dev` on the freshly merged `main`. It triggers the dev deploy chain via `/ci-run`, then auto-chains `/ci-deploy-status dev` for the live pod-health verdict and environment URL.
2. **Wait** for the deploy chain to reach a terminal state (`pysae-ai-tools ci run wait`), exactly like Step 5.
3. Capture the final verdict (`DEPLOYED` / `DEPLOYED_UNHEALTHY` / `DEPLOY_FAILED`) and the environment URL into the footer fields `deploy_verdict` and `env_url`.

A failed or unhealthy dev deploy does **not** flip a merged run to `failed`: the code already shipped to `main`. Keep `status: success`, set `deploy_verdict` accordingly, and call out the unhealthy deploy in the human-facing summary so it gets looked at.

## Step 7 : Emit machine-readable result footer

At the very end of execution, emit a single structured block to stdout. This block is parsed by `/code-autopilot-batch` to determine the outcome. Emit ONE block, even on failure.

````
<<<AUTOPILOT_RESULT
{
  "status": "success" | "ready_to_merge" | "escalated" | "failed",
  "mr_url": "https://gitlab.com/...",
  "mr_iid": 567,
  "issue_iid": 1234,
  "project_path": "pysae/shift",
  "escalation_reason": null,
  "deploy_verdict": null,
  "env_url": null,
  "tokens_used": 0,
  "duration_seconds": 0
}
AUTOPILOT_RESULT>>>
````

The content between `<<<AUTOPILOT_RESULT` and `AUTOPILOT_RESULT>>>` MUST be a single valid JSON object and nothing else. No prose, no markdown, no commentary outside the JSON braces.

Field semantics :
- `status: success` => MR merged AND issue closed (full pipeline green).
- `status: ready_to_merge` => review APPROVED + CI green, MR left approved but **not merged** (only emitted under `--stop-before-merge`). `mr_url`/`mr_iid` required. The batch merge-gate merges it later; a standalone caller can merge by hand.
- `status: escalated` => human required (review-fix did not converge, merge conflict, CI fail non-recoverable, merge blocker, ambiguous spec, or `--merge=skip` / user declined the prompt). Set `escalation_reason` to a short human string (max 200 chars) — explicitly state when the cause is a skipped merge so the batch orchestrator can distinguish "would have merged" from a real blocker.
- `status: failed` => technical failure not actionable by a human (rare; usually means a tool exploded).
- `mr_url` and `mr_iid` are required when a MR exists (regardless of status), otherwise null. An escalated run that already created a MR MUST set both fields.
- `deploy_verdict` and `env_url` are set only when Step 6b ran (`--deploy=dev`): the `/ci-deploy-status` verdict and the dev environment URL. Null otherwise. An unhealthy/failed dev deploy keeps `status: success` (the merge already shipped) but records the verdict here.
- `tokens_used` and `duration_seconds` are best-effort. If unknown, emit 0.

If an unrecoverable tool error occurs at any earlier step, emit the footer immediately with `status: failed` and `escalation_reason` describing the failure, then stop. Never abort without emitting the footer.

## Critical rules

- **ALWAYS run Step 1 ready-check** — never skip the prerequisite gate, even if the user passes a ticket they think is ready
- **ALWAYS complete ALL steps (1→7)** — never stop after implementation. The full flow is: implement → review loop → CI → merge → emit footer.
- **Delegate, don't duplicate** — use `/code-implement` for the implementation, `/code-review-fix` for the review loop, `/code-quality` for validation, `/ci-run` or `/ci-deploy` for CI, `/glab-mr-merge` for the final merge (it handles its own pre-merge rebase if needed)
- **Review loop safety**: max 5 iterations. In local mode, escalate to the user; in CI mode, fail with exit code 1
- **Always emit AUTOPILOT_RESULT footer** as the very last output, even on early abort. Failure to emit causes the parent batch orchestrator to treat the run as `outcome unparseable` and escalate the ticket.

$ARGUMENTS
