---
name: code-get-next-version
description: >
  Determine the next semantic version of a Pysae application from its latest semver tag and the
  changelog entries merged since. In local mode it shows the aggregated changelog grouped by commit
  type and asks you to confirm or force a different bump; in CI it returns the auto-detected version
  without prompting. Use when the user says /code-get-next-version, /next-version,
  'quelle prochaine version', 'quelle est la prochaine version', 'quel sera le prochain numéro',
  'calcule la prochaine version', 'quel bump', 'major minor ou patch', 'what is the next version',
  'compute the next version', 'which version bump', 'next semver', or when /ci-release or
  /code-review-pre-release needs to resolve the version before a release. Distinct from
  /code-changelog (which writes an entry) and /code-release-notes (which writes user-facing notes) —
  this only computes the version number.
argument-hint: "[optional: app name] [optional: --bump major|minor|patch] [optional: --version vX.Y.Z (or vX.Y.Z-label.N) to force an explicit version] [optional: --prerelease <label> e.g. beta/rc to produce or iterate a prerelease line] [optional: --base-tag vX.Y.Z to bump from a specific tag (hotfix flow)] [optional: --ref <branch> to read changelogs from a non-default ref] [optional: --support-line vX.Y.x|vX.x.x to constrain to a support/* branch's range]"
allowed-tools:
  - Read
  - Glob
  - Grep
  - Bash
  - AskUserQuestion
---

Resolve the next semantic version of an app and return it as a parseable JSON block so the caller (`/ci-release`, `/code-review-pre-release`) can consume it without re-doing the work.

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

## Step 1 — Resolve 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')
```

**Target application** — **default**: `pysae/api` (or the current repo). For another repo, resolve `PROJECT_ID` via `glab api "projects/<url-encoded-path>"` (list repos with `pysae-ai-tools project list`).

**Default branch** — read it from GitLab so the script never assumes `main`:
```bash
DEFAULT_BRANCH=$(glab api "projects/${PROJECT_ID}" | jq -r '.default_branch')
```

**Argument parsing** — extract from `$ARGUMENTS` upfront and store in shell variables (default empty):
```bash
EXPLICIT_VERSION=""   # set if $ARGUMENTS contains --version vX.Y.Z (or vX.Y.Z-label.N)
EXPLICIT_BUMP=""      # set if $ARGUMENTS contains --bump major|minor|patch
PRERELEASE=""         # set if $ARGUMENTS contains --prerelease <label> (e.g. beta, rc)
BASE_TAG=""           # set if $ARGUMENTS contains --base-tag vX.Y.Z (hotfix flow)
CHANGELOG_REF=""      # set if $ARGUMENTS contains --ref <branch> (read changelogs from a non-default ref)
SUPPORT_LINE=""       # set if $ARGUMENTS contains --support-line vX.Y.x | vX.x.x (support/* range)
```
- `--version vX.Y.Z` → explicit version, skip auto-detection and the user prompt entirely. Validate the format (`^v?\d+\.\d+\.\d+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$` — a prerelease suffix like `-beta.1` is allowed).
- `--bump major|minor|patch` → forces the bump type instead of auto-detection.
- `--prerelease <label>` → produce or iterate a prerelease line. From a final tag it applies the bump to get the base and appends `-<label>.1` (`v5.4.0` + `--bump major --prerelease beta` → `v6.0.0-beta.1`); from a prerelease of the **same** label it increments the counter (`v6.0.0-beta.1` → `v6.0.0-beta.2`); a *different* label restarts at `.1` on the same base. **Omitting `--prerelease` on a prerelease line finalizes it** (`v6.0.0-beta.2` → `v6.0.0`, no re-bump). Betas (alpha/beta/rc) require `release.allow_prerelease: true` in the repo's `.pysae-ai-tools.yaml` — otherwise `next-version` returns an `error`.
- **Hotfix versions (`vX.Y.Z-hotfix.N` or the bare `vX.Y.Z-N`, e.g. `v5.3.16-1`) are store-bound, not betas.** They are typically passed via `--version` (a hotfix is a deliberate, pre-arrested version, not an auto-bump). The result carries `is_hotfix: "true"`, and — unlike a beta — a hotfix **bypasses the `release.allow_prerelease` gate** (it ships store notes and publishes to the stores like a final release; it is not a beta). Downstream, `/code-release-notes` and `ci release verify` generate/require the store variants for it, and `/ci-deploy` offers store publication.
- `--base-tag vX.Y.Z` → bump from this tag instead of the project's latest semver tag. Used in hotfix flows where the new version branches off an older tag, not the most recent release. The skill returns `latest_tag = <base_tag>` so the caller can keep using the field uniformly.
- `--ref <branch>` → read `changelogs/` from this git ref instead of `main`. Pair with `--base-tag` in hotfix flows: pass the hotfix branch where the entry was just added (the `changelogs/` folder is pruned at each release, so any file on the hotfix branch is by definition a new unreleased entry).
- `--support-line vX.Y.x | vX.x.x` → constrain the resolution to a `support/*` branch's range. The base tag becomes the **highest tag inside the range** (never a later major on the main line) and the bump is **clamped** to what stays inside it (a minor-pinned line like `v5.2.x` only ever patches; `v5.x.x` allows minor too, never major). The script echoes the constraint as `support_line` and, when a clamp happened, the requested bump as `clamped_from`. Forwarded by `/ci-release` when releasing from a support branch. Pair with `--ref <support-branch>` so the changelog bump is read from the support line, not `main`.
- An app name positional → already handled above.

CI mode (`IS_CI == "true"`): never use `AskUserQuestion`. Skip Step 3 and Step 4 entirely — return the auto-detected version.

## Step 2 — Compute the candidate versions

```bash
ARGS="--project-id ${PROJECT_ID}"
[ -n "$EXPLICIT_VERSION" ] && ARGS="$ARGS --version $EXPLICIT_VERSION"
[ -n "$EXPLICIT_BUMP" ] && ARGS="$ARGS --bump $EXPLICIT_BUMP"
[ -n "$PRERELEASE" ] && ARGS="$ARGS --prerelease $PRERELEASE"
[ -n "$BASE_TAG" ] && ARGS="$ARGS --base-tag $BASE_TAG"
[ -n "$CHANGELOG_REF" ] && ARGS="$ARGS --ref $CHANGELOG_REF"
[ -n "$SUPPORT_LINE" ] && ARGS="$ARGS --support-line $SUPPORT_LINE"
VERSION_JSON=$(pysae-ai-tools ci release next-version $ARGS)
```

The script returns: `latest_tag`, `new_version`, `bump`, `is_prerelease`, `prerelease_label`, `final_version`, `support_line`, `clamped_from`, and (only when the latest tag is a final release) `version_patch`, `version_minor`, `version_major`. If it returns an `error`, stop and surface the message to the user (a prerelease requested on a repo with `release.allow_prerelease: false`, or a support line with no in-range tag / an out-of-range explicit version, all land here).

**Clamp warning** — when `clamped_from` is non-empty, the requested bump exceeded the support range and was clamped down (e.g. a `feat` on `v5.2.x` → `clamped_from: "minor"`, `bump: "patch"`). Always surface this to the user (Step 3/4 header) so the semver narrowing is visible, never silent.

If `--version` or `--prerelease` was passed — or `new_version` is a prerelease / a finalization of one (`is_prerelease == "true"`, or `bump` is `"explicit"`/`"prerelease"`/`"release"`) — there is no major/minor/patch choice to offer: skip Step 3's bump rendering nuance and **Step 4's force-bump options**, just confirm `new_version` (local) or return it (CI).

## Step 3 — Show the aggregated changelog (local only)

Skip if CI mode or if `--version` was passed.

List all changelog entries merged on the **changelog ref** — the support branch when `--ref` was passed (support release), otherwise the default branch (resolved at Step 1): `CL_REF="${CHANGELOG_REF:-$DEFAULT_BRANCH}"`.

```bash
glab api "projects/${PROJECT_ID}/repository/tree?path=changelogs&ref=${CL_REF}" \
  | jq -r '.[] | select(.name | endswith(".md")) | .path'
```

For each file, fetch its content:

```bash
glab api "projects/${PROJECT_ID}/repository/files/<url-encoded-path>/raw?ref=${CL_REF}"
```

Aggregate the entries in French, grouped by conventional-commit type — **feat / fix / refactor / perf / tech / docs / chore / BREAKING**. Skip empty groups. Keep the original commit subject; do not rewrite content.

When `clamped_from` is set, append a clamp warning line under the header (e.g. `⚠️ bump <clamped_from> clampé à <bump> pour rester dans la plage support <support_line>`).

Display:
```
### Changelog `<latest_tag>` → `<new_version>` (bump auto : `<bump>`)

**feat** (<N>)
- <subject>
- <subject>

**fix** (<N>)
- <subject>
...
```

## Step 4 — Ask the user to confirm (local only)

Skip if CI mode or if `--version` was passed.

**Prerelease / finalization** (`is_prerelease == "true"`, or `bump` ∈ {`explicit`, `prerelease`, `release`}): the force-bump options don't apply (the base is fixed by the prerelease line). Just confirm `new_version` with a simple Oui/Annuler — e.g. `v6.0.0-beta.1` → `v6.0.0-beta.2`, or finalize `v6.0.0-beta.2` → `v6.0.0`.

**Support line** (`support_line` non-empty): the force-bump options **don't apply** — they would let the version escape the support range (`Force minor` on `v5.2.x` → `v5.3.0`). Just confirm `new_version` with a simple Oui/Annuler, showing the range and any clamp: `Prochaine version support \`<support_line>\` : \`<latest_tag>\` → \`<new_version>\`` (+ the clamp warning line when `clamped_from` is set). To deliberately leave the range, the user must re-run without `--support-line` or on a different support branch.

Otherwise, use `AskUserQuestion` with the four options below. **Exclude the option whose bump matches the auto-detected one** (no point offering `Force minor` when the auto-detection already says minor):

> Prochaine version de `<app>` : `<latest_tag>` → `<new_version>` (`<bump>`)
>
> - **Oui** *(Recommended)* — confirmer `<new_version>`
> - **Force patch** — `<version_patch>` *(only if bump ≠ patch)*
> - **Force minor** — `<version_minor>` *(only if bump ≠ minor)*
> - **Force major** — `<version_major>` *(only if bump ≠ major)*
> - **Annuler**

Set the final `NEW_VERSION` to the chosen one (`version_patch` / `version_minor` / `version_major` come directly from the script output — no need to re-run anything). If the user picks **Annuler**, set `CANCELLED=true`.

## Step 5 — Return the result and hand back control

Print the JSON block **followed immediately by a one-line handoff marker** so the calling skill (a) can parse the JSON unambiguously and (b) sees an explicit signal that this delegation is finished and its own flow must resume.

```
NEXT_VERSION_RESULT:
{
  "latest_tag": "<latest_tag>",
  "new_version": "<chosen_version>",
  "bump": "<final_bump>",
  "support_line": "<support_line or empty>",
  "clamped_from": "<clamped bump or empty>",
  "cancelled": <true|false>
}

— `code-get-next-version` terminé. Le contrôle revient au skill appelant : **ne t'arrête pas ici, enchaîne sur l'étape suivante de son flow** (typiquement la génération des release notes ou le déclenchement du pipeline).
```

Contract — every field is **always present** so the parser only handles one shape:
- `latest_tag` — always set (even `"v0.0.0"` if no prior tag).
- `new_version` — the version the user confirmed/forced/explicited. Set to `""` when `cancelled` is `true`.
- `bump` — the bump matching the chosen version (`patch` / `minor` / `major` / `explicit`). When the user picked a force option, this is the forced one — not the auto-detected one. Set to `""` when `cancelled` is `true`.
- `support_line` — the constraining support range (`""` on a normal release). Echoes the script's field so the caller knows the release is range-bound.
- `clamped_from` — the requested bump before clamping it down to the support range (`""` when no clamp happened). Lets the caller log the semver narrowing.
- `cancelled` — `true` only if the user picked **Annuler** at Step 4.

The closing `}` must be on its own unindented line (the calling skill's `sed -n '/^NEXT_VERSION_RESULT:/,/^}/p'` extractor stops at the first `^}` and ignores any prose that follows — the handoff marker is safe to keep). The marker line itself is mandatory: it's the explicit cue that tells the calling LLM to continue rather than treat the JSON as a terminal response.

The calling skill reads the printed block directly from this skill's response, then extracts `new_version` and `cancelled` from the JSON, then **continues with its own next step without pausing** unless `cancelled` is `true`.

## Rules

- **Single source of truth** for next-version computation — `/ci-release` and `/code-review-pre-release` both delegate here. Never duplicate the changelog rendering or the AskUserQuestion logic elsewhere.
- **CI mode never prompts** — auto-detect and return immediately.
- **`--version` override skips everything** — useful when an upstream skill already arrested the version with the user.
- **Read-only** — no commits, no tags, no pipeline triggers. The skill only reads GitLab metadata.
- **French** for the user-facing changelog rendering and the confirmation prompt; English for commands and JSON keys.

$ARGUMENTS
