---
name: code-implement
description: >
  Implement the changes that resolve a GitLab issue, end to end: set it In Progress, create the
  branch, code with atomic commits, run tests and linters, then open the MR. Use when the user says
  /code-implement, /implement, 'implement this issue', 'resolve this ticket', 'code this ticket',
  'implémente ce ticket', 'implémente cette issue', 'résous ce ticket', 'code ce ticket',
  'fais ce ticket', 'développe cette fonctionnalité', 'work on this issue', 'build this feature',
  'fix this bug from the ticket', or needs to go from an issue to a merge request. Distinct from
  /glab-mr, which only opens an MR for a branch you already coded, and from /code-autopilot, which
  also reviews, validates CI and merges after implementation.
argument-hint: "[GitLab issue IID or URL] [--hotfix [vX.Y.Z]] (hotfix: branch from the latest tag, or a specific tag if given, and target a release/vX.Y.Z branch in the MR)"
allowed-tools:
  - Read
  - Edit
  - Write
  - Glob
  - Grep
  - Bash
  - Agent
  - AskUserQuestion
  - Skill
---

Implement the changes required to resolve a GitLab issue, from start to MR creation.

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

## Project configuration

Honour `.pysae-ai-tools.yaml` (this repo's `CLAUDE.md` is NOT in your context at runtime). Check flags with `pysae-ai-tools project flag <path>` (exit 0 = on, 1 = off — no `jq`):

- `ai_notes.enabled` — when `pysae-ai-tools project flag ai_notes.enabled` exits non-zero, **skip** the `docs/ai-notes/` file entirely (Steps 6–9): no notes file, no MR notes-summary comment.

## Mode detection

Detect the execution context using `detect_context` (pass `$ARGUMENTS` refs for automatic parsing of `#IID`, `!IID`, URLs):
```bash
CTX=$(pysae-ai-tools internal detect-context $ARGUMENTS_REFS)
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')
ISSUE_IID=$(echo "$CTX" | jq -r '.issue_iid // empty')
```

In CI mode (`IS_CI` is `true`):
- **Never use `AskUserQuestion`** — all decisions must be resolved automatically
- If the issue has no `type::*` label, infer the type from the issue title and description (use `feat` as default if ambiguous)
- Always use **worktree** for isolation (no IDE in CI)
- On uncertainty, make a best-effort choice and document it in the commit message — do not stop

### Hotfix mode detection

**Which line am I patching?** The deciding question is *which version line* the fix targets, never the word « hotfix » on its own:

- **The current line** (the latest released version) → `--hotfix` mode: branch off the latest tag (or the named one), open the MR against an **ephemeral** `release/vX.Y.Z` branch (Step 7). This is the **default** for a plain « fais un hotfix ».
- **An older line still maintained in parallel** while `main` has moved on → branch off and target a **long-lived** `support/vX[.Y].x` branch (the explicit-base-branch path in Step 4); the fix stays on that line, never on the default branch.

A plain « hotfix » lands on `release/*`. Only take the support/backport path when the user explicitly names an older `support/*` line.

Parse `$ARGUMENTS` for an explicit hotfix request **before doing anything else**, since it changes the base ref used for the work branch (Step 4) and the MR target (Step 7):

```bash
HOTFIX_MODE=false
HOTFIX_BASE_TAG=""   # filled below when HOTFIX_MODE=true
case " $ARGUMENTS " in
  *" --hotfix "*) HOTFIX_MODE=true ;;
esac
# --hotfix may be followed by an explicit tag: "--hotfix v2.3.1"
if $HOTFIX_MODE; then
  HOTFIX_BASE_TAG=$(printf '%s\n' "$ARGUMENTS" | sed -n 's/.*--hotfix[[:space:]]\+\(v\?[0-9]\+\.[0-9]\+\.[0-9]\+\).*/\1/p')
fi
```

The trigger is also any of: user typing « hotfix », « fais un hotfix sur la version X.Y.Z », « patch d'urgence basé sur le tag ... » — all on the **current line**, so they set `HOTFIX_MODE=true` (→ `release/*`), **not** a support backport. Resolve `HOTFIX_BASE_TAG` from the prose. When the user names a tag (with or without the `v` prefix), normalize it to `vX.Y.Z`. When the user doesn't name one, leave `HOTFIX_BASE_TAG` empty — Step 4 will resolve it to the project's latest semver tag.

`HOTFIX_MODE` and `HOTFIX_BASE_TAG` are read again in Step 4 (branch creation off the tag) and Step 7 (release branch creation + MR target). Nothing else changes — the rest of the flow (ready-check, issue read, in-progress transition, atomic commits, code-quality, ai-notes) is identical.

## Step 1 : Prerequisite gate (ready-check)

Verify the ticket has the structural prerequisites before doing anything else:

```bash
pysae-ai-tools glab issue-ready-check "$ARGUMENTS" --apply --json
```

Parse stdout JSON. If `.ready == false`:
- **Local mode**: report the violations to the user with a one-line summary, then stop (exit 0 — this is not a technical error). Suggest fixing the ticket and re-running.
- **CI mode**: exit 1.
- **When invoked from `/code-autopilot`**: do not emit `AUTOPILOT_RESULT` here — `/code-autopilot` Step 1 calls ready-check itself before delegating to this skill, so we should never reach this point in that flow.

If `.ready == true`, continue with Step 2.

## Step 2 : Read the issue and detect type

Use `ISSUE_IID` from detect_context (resolved in Mode detection). Fetch the issue details:

```bash
glab issue view <IID> --output json
```

Extract: title, description, labels, acceptance criteria, linked epic.

Determine the type from the `type::*` label — see [../references/issue-type-mapping.md](../references/issue-type-mapping.md) for the full mapping table. If no `type::*` label is found: in local mode, ask the user; in CI mode, infer from title/description (default to `feat`).

## Step 3 : Set issue to In Progress

Move the issue to "In Progress":
```bash
pysae-ai-tools glab workflow-transition ${ISSUE_IID} "workflow::In progress"
```

## Step 4 : Create worktree and branch

**Determine the base ref** using the first matching rule:
1. **Hotfix mode** (`HOTFIX_MODE=true` from the « Hotfix mode detection » block above):
   - If `HOTFIX_BASE_TAG` is empty, resolve it to the project's latest semver tag — call `pysae-ai-tools ci release next-version --project-id "${PROJECT_ID}"` and read `.latest_tag` from its JSON output.
   - Fetch the tag locally so the branch can be checked out from it: `git fetch origin "refs/tags/${HOTFIX_BASE_TAG}:refs/tags/${HOTFIX_BASE_TAG}"`.
   - Set `BASE_REF="${HOTFIX_BASE_TAG}"`. **Do not set `BASE_BRANCH` to the tag** — `BASE_BRANCH` (used as MR target in Step 7) will be resolved later to the `release/${NEW_VERSION}` branch we create just before opening the MR.
2. If the user explicitly requested a base branch — **including a `support/vX.Y.x` branch for a backport on an older, long-lived line** — use it as both `BASE_REF` and `BASE_BRANCH`. The branch then starts from the support line (see the `--base` handoff to `/git-worktree` below) and the Step 7 MR targets that support branch (`/glab-mr <IID> --target-branch <BASE_BRANCH>`), so the backport lands on the support line, never on the default branch.
3. If the user references a parent MR (by IID or URL), fetch it and:
   - If the MR is **open** (not merged): use its **source branch** (stacking on an in-progress MR).
   - If the MR is **merged**: use its **target branch** (the changes are already in that branch).
   Store the result as both `BASE_REF` and `BASE_BRANCH`.
4. Otherwise, detect the repository's default branch:
   ```bash
   BASE_BRANCH="$(git remote show origin | sed -n 's/.*HEAD branch: //p')"
   BASE_REF="${BASE_BRANCH}"
   ```

In hotfix mode, `BASE_REF` is the tag (e.g. `v2.3.1`) and `BASE_BRANCH` is **deferred** to Step 7 once `NEW_VERSION` is known. In every other mode, `BASE_REF` and `BASE_BRANCH` are the same. `BASE_REF` drives the local checkout below; `BASE_BRANCH` drives the MR target in Step 7.

**Determine the branch name** following the naming convention from the MR skill:
```bash
# Slugify: lowercase, replace spaces/special chars with hyphens, max 40 chars
SLUG=$(echo "<short issue title>" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | cut -c1-40 | sed 's/-$//')
BRANCH="${BRANCH_PREFIX}/<IID>-${SLUG}"
```

Choisir le mode d'isolation selon ces règles **dans l'ordre** :

1. **Demande explicite de l'utilisateur** → faire ce qu'il demande (branche simple ou worktree)
2. **IDE connecté** (le tool `mcp__ide__getDiagnostics` est disponible) → **branche simple** (un worktree désynchronise l'IDE)
3. **Pas d'IDE** (terminal / headless / CI) → **worktree** via `/git-worktree`

**Worktree** : invoquer `/git-worktree` avec le nom de branche calculé ci-dessus et **lui passer la base via `--base ${BASE_REF}`** (le tag en mode hotfix, la branche support en mode backport, sinon le default branch). `/git-worktree` honore cette base — il **ne rebase plus de force sur le default branch** — donc un fix sur `support/v5.2.x` part bien de la ligne support, jamais d'un `main` plus récent. Le worktree isole les changements et permet de travailler sur plusieurs tickets en parallèle. `/git-worktree` crée la branche et le worktree en une seule opération — ne pas créer la branche manuellement avant.

> ⚠️ **La base doit être le HEAD réel d'origin, jamais une réf locale périmée.** Quel que soit le mode (worktree ou branche simple), un `git fetch origin "${BASE_BRANCH}"` DOIT précéder la création de la branche : la réf locale `origin/${BASE_BRANCH}` n'est à jour que du dernier fetch, et brancher dessus sans fetch fait partir le ticket d'un main en retard (features mergées absentes, conflits artificiels). `/git-worktree` applique désormais ce fetch lui-même et vérifie la base après création.

**Branche simple** :
```bash
if $HOTFIX_MODE; then
  # BASE_REF is a tag — already fetched above
  git checkout -b "${BRANCH}" "${BASE_REF}"
else
  git fetch origin "${BASE_REF}"
  git checkout -b "${BRANCH}" "origin/${BASE_REF}"
fi
```

## Step 5 : Analyze and plan

Before coding, analyze the codebase to understand the problem and plan the implementation:

### Reformulate AC as testable checks (Goal pattern)

Before drafting the implementation plan, transform the ticket's acceptance criteria into a verifiable checklist:

1. **Reformulation** — for each acceptance criterion, write a Given/When/Then (or compact equivalent: `Quand X, alors Y`).
2. **Ambiguity log** — every AC that does not fit into a verifiable check goes into the `Open questions` section of the `docs/ai-notes/<...>.md` file (see Step 6):
   - 🟡 if implementation can continue with a documented hypothesis
   - 🔴 if blocking (rare — if blocking, the Step 1 gate should have caught it)
3. **Anti-scope** — list what is explicitly **out of scope** for this ticket. This list goes into the MR description (under a "Hors scope" section) to guard against scope creep.

This checklist becomes the source of truth for the implementation plan (« Read codebase and plan units of work » sub-step below) and for the verdict of `/code-review-fix`.

> Note: this step applies the "Define your goal" pattern recommended by Anthropic, integrated directly here without an external skill dependency.

### Critique spec proposals against the codebase

PM-authored specs often ship with code snippets, file paths, API calls, hook names, store selectors, types, or component names. They are **hypotheses**, not facts — the spec author may not be aware of the current codebase shape. Before drafting the implementation plan, you MUST challenge every proposal in the ticket description against reality. **Skip this step entirely if the ticket description contains no code snippet, no file path, and no named symbol (rare: pure prose specs).**

For each concrete reference in the description:

1. **Extract** — list every snippet, file path, imported module, exported symbol, hook, store, route, type, table name, env var, command, or config key that the description names explicitly.
2. **Verify existence** — for each reference, grep / `Read` the codebase:
   - File path → does it exist? Is the path correct?
   - Import / symbol → does the named export actually exist with that signature?
   - Hook / store / selector → does it exist? Is its return shape what the snippet assumes?
   - API route / endpoint → does it exist? Same verb, same path, same payload?
   - Table / column → is it in `schema.prisma` (or equivalent)? Same type, same nullability?
3. **Check pattern fit** — compare the proposed approach to the dominant pattern in the touched zone:
   - State: Zustand store vs Context vs prop drilling — which is used in adjacent components?
   - Data fetching: `apiFetch` wrapper vs raw fetch, TanStack Query vs direct calls
   - Forms: existing form lib / pattern in similar pages
   - Styling: design tokens (e.g. `var(--color-brand)`) vs hex / Tailwind primitives
4. **Decide and log** — for each reference, the outcome falls in one of three buckets, written to `Design decisions` in `$NOTES_PATH` (Step 6):
   - ✅ **Confirmed** — proposal matches reality. Batch these into a single line ("All imports / file paths in the spec verified against current code") rather than one bullet per match.
   - ⚠️ **Adjusted** — proposal is broadly right but needs adaptation (renamed symbol, slightly different API signature, different pattern). Record: what the spec proposed, what you'll do instead, one-sentence reason. **Proceed with the adjusted version.**
   - 🔴 **Contested** — proposal contradicts the codebase or breaks an existing invariant (missing API, wrong tenant scoping, anti-pattern for the area). Record the contradiction, propose an alternative, and add a 🔴 entry under `Open questions` if the alternative changes behavior or scope. **Pause before coding if the contradiction is load-bearing for the ticket.**

The bar: would a senior reviewer flag this snippet at code-review time? If yes, challenge it now, not after the MR is open.

Examples of what to challenge (non-exhaustive):
- Spec imports `useAppStore` but the store is `useUserStore`
- Spec calls `GET /api/foo` but the actual route is `GET /foo` (no `/api` prefix)
- Spec proposes a Zustand store next to existing TanStack Query — inconsistent with neighbor files
- Spec uses inline `style={{ color: 'green' }}` — design system requires `var(--color-brand)` token
- Spec proposes a new Prisma model but the migration would conflict with the multi-tenant isolation rules in `CLAUDE.md`
- Spec calls a hook with arguments the actual signature does not accept

Examples of what NOT to challenge (signal-to-noise hygiene):
- Variable names inside the snippet (cosmetic)
- Whitespace or formatting style
- Choices that are reasonable AND consistent with the area, even if you would have picked differently

### Read codebase and plan units of work

1. Read the repo's `CLAUDE.md` if it exists (project conventions, test commands, architecture)
2. Search for relevant files and understand the impacted code
3. Formulate an implementation plan with discrete, committable units of work

## Step 6 : Initialize implementation notes file

Create the implementation notes file in the worktree (do not commit yet):

```bash
NOTES_PATH="docs/ai-notes/$(date -u +%Y-%m-%d)-${ISSUE_IID}-${SLUG}.md"
STARTED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
mkdir -p docs/ai-notes
if [ ! -f docs/ai-notes/README.md ]; then
  cp "$CLAUDE_SKILLS_DIR/code-implement/references/ai-notes-readme-template.md" docs/ai-notes/README.md
fi
```

Write the initial frontmatter and empty section skeleton at `$NOTES_PATH`, substituting `<STARTED_AT>` with the `$STARTED_AT` value computed above (format `YYYY-MM-DDTHH:MM:SSZ` — must include hours/minutes/seconds, never `T00:00:00Z` from the date alone):

```markdown
---
issue: <full issue URL>
mr: <MR URL once created>
branch: <BRANCH>
model: <claude model>
started_at: <STARTED_AT>
completed_at:
---

# Implementation notes : <short issue title>

## Design decisions

## Deviations

## Tradeoffs

## Open questions
```

Keep this file updated throughout the implementation (Steps 7+). Sections with no entries can be omitted from the final commit.

### Ticket references in ai-notes

Any reference to a GitLab ticket (issue, MR, epic) **must** be written as a full Markdown link to the GitLab URL — never a bare `#1234` or raw IID. This applies to every section (`Design decisions`, `Deviations`, `Tradeoffs`, `Open questions`) and to the MR comment template below.

- Issue → `[#1234](https://gitlab.com/<group>/<project>/-/issues/1234)`
- MR → `[!42](https://gitlab.com/<group>/<project>/-/merge_requests/42)`
- Epic → `[&7](https://gitlab.com/groups/<group>/-/epics/7)`

Resolve the project/group path from the ticket context (the source issue URL in the frontmatter gives you the project path). Don't assume the reader knows which project the IID belongs to — ai-notes are read in Notion, Slack, archives where `#1234` isn't clickable.

## Step 7 : First commit, then create the MR immediately

Make the **first logical unit of work** following the commit checklist below, then **push and create the MR right away** — before continuing with the rest of the implementation. This makes the work visible early, gets a CI pipeline running, and lets reviewers track progress incrementally.

1. **Make the changes** for the first unit of work — edit/create the necessary files
2. **Run relevant tests** — execute tests related to the changed code:
   - Python: `pytest <relevant_test_files>` or the test command from CLAUDE.md
   - Node/TS: `yarn test` / `npm test` / `npx vitest run`
   - If no specific test exists for the change, run the full test suite
3. **Run linters** — execute the project's linter:
   - Python: `ruff check` / `flake8` / the lint command from CLAUDE.md
   - Node/TS: `yarn lint` / `npm run lint` / `npx eslint`
4. **Fix any failures** — if tests or linters fail, fix the issues before committing
5. **Commit** the first atomic change:
   ```bash
   git add <specific_files>
   git commit -m "$(cat <<'EOF'
   ${COMMIT_PREFIX}: <short english description of this unit>

   Co-Authored-By: Claude <noreply@anthropic.com>
   EOF
   )"
   ```
6. **Push and create the MR immediately**:
   ```bash
   git push -u origin "${BRANCH}"
   ```

   **In hotfix mode** (`HOTFIX_MODE=true`), insert the release-branch resolution **before** invoking `/glab-mr`. The first commit on `${BRANCH}` must already include a changelog entry (`changelogs/<branch-slug>.md`) — that's what drives the next-version computation; if it isn't there yet, invoke `/code-changelog` to create it and amend / re-commit before pushing. Then:
   ```bash
   # 1. Compute the next version from the hotfix base tag + the new changelog entry on this branch.
   RESULT=$(pysae-ai-tools ci release next-version \
     --project-id "${PROJECT_ID}" \
     --base-tag "${HOTFIX_BASE_TAG}" \
     --ref "${BRANCH}")
   NEW_VERSION=$(echo "$RESULT" | jq -r '.new_version')
   # 2. Create the release branch from the base tag (NOT from the hotfix branch), then push it.
   git fetch origin "refs/tags/${HOTFIX_BASE_TAG}:refs/tags/${HOTFIX_BASE_TAG}"
   git push origin "${HOTFIX_BASE_TAG}:refs/heads/release/${NEW_VERSION}"
   BASE_BRANCH="release/${NEW_VERSION}"
   ```
   The release branch starts at the exact tag commit — the hotfix's changes flow into it only when the MR is merged. Never push the hotfix branch's commits onto the release branch directly; that bypasses review.

   Then invoke the `/glab-mr` skill with the source issue IID. If `BASE_BRANCH` is not the default branch, pass it as well (e.g., `/glab-mr <IID> --target-branch <BASE_BRANCH>`). **Never create the MR as draft** — it must be ready for review immediately, even though more commits will follow.

### Implementation notes : update before each commit

Before each commit in Steps 7 and 8, update `$NOTES_PATH`:
- Add a bullet under `Design decisions` when you arbitrate an ambiguity
- Add a bullet under `Deviations` when you depart from the spec
- Add a bullet under `Tradeoffs` when you choose between alternatives
- Add a bullet under `Open questions` when something needs human validation:
  - 🔴 critical (must be resolved before merge)
  - 🟡 to validate (implementation continued under a documented hypothesis)
  - 🟢 informative (no action required)

If the file ends with all four sections empty, **do not commit it**. Otherwise, include it in the same commit as a substantial change and add an `AI Notes have been updated.` line at the end of the commit body (before any `Co-Authored-By:` trailer) — never in the subject line.

### Post the Open questions summary on the MR

After the MR is created, if `$NOTES_PATH` contains at least one 🔴 or 🟡 in `Open questions`, post (or edit an existing) MR comment with the HTML marker `<!-- pysae-ai-tools:ai-notes-summary -->`. Template:

```markdown
🤖 **Notes d'implémentation IA** : [`docs/ai-notes/<...>.md`](<file URL>)

Quelques décisions et questions à valider avant merge :

- 🔴 **<title>** : <one-line summary>
- 🟡 **<title>** : <one-line summary>

Voir le fichier complet pour `Design decisions`, `Deviations`, `Tradeoffs`.

<!-- pysae-ai-tools:ai-notes-summary -->
```

Use `glab mr note <MR_IID> --message "$comment"` to post; on subsequent pushes, search for the marker via `glab api projects/.../merge_requests/<iid>/notes` and edit the existing note rather than posting a duplicate.

## Step 8 : Continue with the remaining commits

For each subsequent logical unit of work, repeat the cycle (changes → tests → linters → fix → commit) and push after each commit so the MR stays current:

```bash
git push
```

### Keep the MR description in sync

After each push, check whether the new commit introduces context that the current MR description does not capture (a new sub-feature, a non-trivial refactor, a notable design choice, an added test surface, a removed/changed behavior, etc.). If yes, **rewrite** the MR description so it describes the **current state** of the MR:

```bash
glab mr update <MR_IID> --description "$(cat <<'EOF'
<full French description, rewritten to match the current state, following the existing template/structure>
EOF
)"
```

The description is a snapshot of where the MR stands now — **never keep history, changelog-style entries, or "added X / removed Y" trails inside it**. Just describe what the MR currently does, as if it had been written from scratch. The commit history already carries the chronology.

Skip the update when the new commit is purely cosmetic (typo, comment, formatting) or already covered by the existing description — do not churn the description on every push. The MR title is updated only if the scope of the work changes meaningfully.

### Commit rules

See [../references/commit-rules.md](../references/commit-rules.md) for the full set (message format, atomicity, pre-commit checks, forbidden files). Use the detected `COMMIT_PREFIX` from Step 2 consistently across every commit of this MR.

## Step 9 : Final validation

Once all units of work are committed and pushed, invoke the `/code-quality` skill with the `COMMIT_PREFIX` as argument. This runs the full test suite, linters, and pre-commit hooks, and fixes any remaining failures with atomic commits (which are then pushed to the existing MR).

Then **validate the changelog entry** — whoever created it (`/glab-mr`, `/code-changelog`, or a hand-written file), it must conform before the MR can be merged and feed the release:

```bash
pysae-ai-tools code changelog validate || {
  # Format issue → auto-fix; a too-long entry can NOT be auto-fixed
  # (trimming the description is a semantic edit) — reword it under 122 chars.
  pysae-ai-tools code changelog validate --fix
  pysae-ai-tools code changelog validate
}
```

If `validate` still fails after `--fix` (over-budget entry), shorten the description by the number of chars it reports, then commit the corrected `changelogs/<branch-slug>.md`.

### Implementation notes : finalize

Before declaring the work done:
- Fill the `completed_at` frontmatter with the current ISO8601 UTC timestamp computed via `date -u +%Y-%m-%dT%H:%M:%SZ` (must include hours/minutes/seconds — never `T00:00:00Z` from the date alone)
- If any sections are empty, remove them
- If all four sections are empty, **delete the file** (do not commit empty notes)
- Otherwise, commit the final state with the `AI Notes have been updated.` body line (see above — body, not subject)
- Re-post or edit the MR Open questions summary comment if 🔴/🟡 changed

## After MR creation

If this skill was invoked **directly by the user** (not delegated from another skill like `/code-autopilot`), suggest the next step:
> MR creee. Tu peux lancer `/code-review-fix` pour demarrer la boucle de review automatique.

In hotfix mode, also remind the user:
> Hotfix `${NEW_VERSION}` ciblant `release/${NEW_VERSION}` — une fois la MR mergée, lance `/ci-release` depuis (ou en passant) cette branche pour déclencher le pipeline qui crée le tag `${NEW_VERSION}` et déploie en prod.

## Critical rules

- **ALWAYS complete ALL steps (1→9)** — never stop early. The MR must be created right after the first commit (Step 7), and final validation (Step 9) must run after the last commit.
- **Never skip tests or linters** — every commit must have passing tests and clean lints
- **Synchroniser la doc & les tests** — tout changement de surface publique (API, CLI, skill, setup, comportement) met à jour la doc impactée (README / docs / SKILL.md) ET les tests dans le même MR. `pysae-ai-tools docs check` doit passer (skills ↔ README, liens). On ne laisse jamais la doc dériver.
- **Atomic commits** — one logical change per commit, not one giant commit
- **Zero hallucination**: if uncertain about implementation, ask the user in local mode (do not guess); in CI mode, make a best-effort choice and document the assumption in the commit message
- **Max files**: 30 for bug/feature, 20 for technical/refactoring
- **Forbidden files**: never modify `.gitlab-ci.yml`, `.env*`, secrets, credentials
- **Language**: commit messages and MR title in English; MR description in French
- **Emojis**: preserve exactly as they appear in the loaded templates — do not add or remove any
- **Delegate to skills**: use `/glab-mr` for MR creation, `/code-quality` for validation, `/code-changelog` for changelog — do not duplicate their logic

## Self-check before each commit

- [ ] Tests pass for the changed code
- [ ] Linters pass
- [ ] No forbidden files modified
- [ ] Commit is atomic (single logical change)
- [ ] Commit message uses correct prefix and is in English

## Self-check before MR

- [ ] All planned changes implemented
- [ ] All tests pass (full suite)
- [ ] All linters pass
- [ ] Changelog entry exists (created by /glab-mr skill) **and is conforme** — `pysae-ai-tools code changelog validate` exits 0 (strict conventional commits + ≤ 122-char budget)
- [ ] MR exists and is up to date
- [ ] File count within limits
- [ ] **Doc & tests synchronisés** : la doc (README / docs / SKILL.md) et les tests reflètent le changement ; `pysae-ai-tools docs check` exit 0

$ARGUMENTS
