---
name: glab-epic-attach-issues
description: >
  Find and assign parent epics for orphan GitLab issues. Two modes: all epics (Phase 1 + Phase 2),
  or a single target epic. Phase 1: match orphan issues to existing epics with per-epic grouped
  validation. Phase 2: propose new epics for remaining unmatched issues by theme.
  Use when the user says /glab-epic-attach-issues, 'rattache les tickets aux epics',
  'trouve les epics manquantes', 'orphan issues', 'issues sans epic',
  'rattache des tickets à cette epic', 'epic audit',
  or wants to organize unassigned issues into epics.
argument-hint: "[epic IID or URL] [--project PROJECT] [--all-projects] [--me] [--user NAME] [--dry-run] [--scoring-mode claude|script]"
allowed-tools:
  - Read
  - Bash
  - Skill
  - AskUserQuestion
---

Find orphan GitLab issues (no parent epic) and assign them to the best matching epic.

**Two modes:**
- **All epics** (no argument) — Phase 1: score orphans against all epics, validate per epic. Phase 2: cluster remaining orphans by theme, propose new epics.
- **Single epic** (epic IID or URL as argument) — find orphans that best match this specific epic. Score against all epics for accuracy, but only present issues whose best match is the target epic. Phase 2 is skipped.

## Required tools

- `glab`

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)
IS_CI=$(echo "$CTX" | jq -r '.is_ci')
PROJECT_ID=$(echo "$CTX" | jq -r '.project_id // empty')
```

In CI mode (`IS_CI` is `true`):
- **Never use `AskUserQuestion`** — print the analysis to stdout and exit (report-only, no modifications)
- Exit code 0 if no orphans, 1 if orphans found

## Scenario detection

Parse `$ARGUMENTS`:
- If the argument is a **number** (e.g. `236`) or a **GitLab epic URL** → **single-epic mode**: pass `--epic <IID>` to the script
- Otherwise → **all-epics mode** (default)

## Step 1 — Collect data

Run the analysis script:
```bash
# Single-epic mode
RESULT=$(pysae-ai-tools glab epic-attach-issues scan --epic ${EPIC_IID})
# All-epics mode
RESULT=$(pysae-ai-tools glab epic-attach-issues scan)
```

In single-epic mode, the script scores all orphans against **all** epics (for accuracy), but only returns the group for the target epic. Phase 2 is skipped automatically.

The script outputs a structured prompt for LLM-based semantic analysis (mode `claude` par défaut):
```bash
PROMPT=$(echo "$RESULT" | jq -r '.prompt')
```

The script handles:
- Fetching all open epics with their child issue titles (for context enrichment)
- Fetching all orphan issues (no parent epic) with pagination
- **Excluding** issues with labels `type::bug`, `Support 📞`, or `ANNULE`
- **Excluding** issues without a board column label (`Refinement`, `Ready`, `workflow::To Do/In progress/Under review/To deploy`)

## Phase 1 — Semantic scoring

### Step 2 — Analyse the prompt

Read the prompt returned by the script. It contains all epics (with descriptions and child issue titles) and all orphan issues (with labels).

Analyse each orphan issue semantically against each epic and assign a confidence level:
- **High** → the issue clearly belongs to this epic's scope
- **Medium** → plausible match, but could also fit elsewhere
- **None** → no epic is a good fit

Group the results by epic. In single-epic mode, only keep matches for the target epic.

### Step 3 — Present matches grouped by epic

Group all matched issues (high + medium confidence) by their best-matching epic. Present one epic at a time using `AskUserQuestion`:

> **Epic #42 — Refonte du système de notifications** (8 tickets proposés)
>
> | # | Ticket | Projet | Confiance | Titre | Labels |
> |---|--------|--------|-----------|-------|--------|
> | 1 | [api#301](https://gitlab.com/pysae/api/-/work_items/301) | api | haute | Add webhook retry mechanism | `API` `priority::P2` `type::feature` |
> | 2 | [op#88](https://gitlab.com/pysae/op/-/work_items/88) | op | haute | Display notification delivery status | `Op` `Ready` `type::feature` |
> | 3 | [api#156](https://gitlab.com/pysae/api/-/work_items/156) | api | moyenne | Implement push notification batching | `API` `Refinement` `priority::P3` |
> | 4 | [op#92](https://gitlab.com/pysae/op/-/work_items/92) | op | moyenne | Filter notifications by type in dashboard | `Op` `Ready` `type::feature` |
>
> **Format des liens** : `[<project>#<iid>](<web_url>)` — use the `web_url` field from the script output.
> **Labels** : display only labels defined in [../references/gitlab-labels.md](../references/gitlab-labels.md) (domain, type, priority, board column). Omit workflow meta-labels like `augmented`, `Maintenance 🧹`, `Evolution 💡`, etc.
>
Then ask with `AskUserQuestion` (single-select, **not** multiSelect):

- **Valider tout** — rattacher tous les tickets listés
- **Haute confiance uniquement** — rattacher uniquement les tickets marqués « haute » (omettre cette option si tous sont haute confiance)
- **Passer** — ne rien rattacher à cette epic

The user can also pick **Other** to specify exact numbers (ex : « 1, 3, 5-8 »).

**Always show ALL matched issues in the table** — never truncate or summarize with « +N autres ».

Process the user's response:
- **Valider tout** → attach all listed issues to this epic
- **Haute confiance uniquement** → attach only high-confidence issues ; the rest go to Phase 2
- **Other** (numbers) → attach only the specified issues ; rejected issues go to the Phase 2 pool
- **Passer** → all issues go to the Phase 2 pool

### Step 4 — Apply attachments

Use the `epic-attach` command to attach confirmed issues:

```bash
pysae-ai-tools glab epic-attach --epic ${EPIC_IID} \
  --issue api#301 --issue op#88 --issue api#156
```

The command handles ID resolution, API calls, and error reporting (422 for incompatible work item types).

If `--dry-run` is set, skip this step and only report what would be done.

Report progress after each epic:
> Rattaché 3/4 tickets à l'epic #42. Reste 1 ticket en attente (Phase 2).

## Phase 2 — Propose new epics or manual assignment

### Step 5 — Analyze remaining orphans

Collect all issues that were not matched in Phase 1 (no match + rejected from Step 3).

If no issues remain, report completion and stop.

Analyze these remaining issues and try to identify **thematic clusters** — groups of 2+ issues that share a common theme (similar domain, related functionality, shared labels, or related titles). Use semantic analysis to detect clusters.

### Step 6 — Present proposals

Present the analysis to the user with `AskUserQuestion`. For each identified cluster:

> **Thème détecté : « Amélioration de l'export GTFS »** (3 tickets)
>
> | Ticket | Projet | Titre | Labels |
> |--------|--------|-------|--------|
> | [api#203](https://gitlab.com/pysae/api/-/work_items/203) | api | Support GTFS-Flex in export pipeline | `API` `Ready` `type::feature` |
> | [api#207](https://gitlab.com/pysae/api/-/work_items/207) | api | Add trip shape simplification to GTFS export | `API` `Refinement` `type::feature` |
> | [gtfs-validator#45](https://gitlab.com/pysae/gtfs-validator/-/work_items/45) | gtfs-validator | Validate GTFS-Flex extensions | `Infra` `type::technical` |
>
> **Actions :**
> - **Créer l'epic** — créer une nouvelle epic « Amélioration de l'export GTFS » et y rattacher les 3 tickets
> - **Renommer et créer** — proposer un autre titre pour l'epic
> - **Rattacher à une epic existante** — spécifier le numéro d'epic (ex : « epic #38 »)
> - **Passer** — laisser ces tickets sans epic

Then list any truly isolated issues (not part of any cluster) individually:

> **Tickets isolés** (aucun thème commun détecté) :
>
> | Ticket | Projet | Titre | Labels |
> |--------|--------|-------|--------|
> | [op#312](https://gitlab.com/pysae/op/-/work_items/312) | op | Fix timezone display in schedule view | `Op` `Ready` `priority::P3` |
> | [driver#78](https://gitlab.com/pysae/driver/-/work_items/78) | driver | Update splash screen animation | `Driver` `Refinement` |
>
> Pour chaque ticket, tu peux :
> - **Rattacher à une epic existante** — « #312 → epic #25 »
> - **Ignorer** — laisser sans epic

### Step 7 — Execute decisions

For each decision:

**Create new epic and delegate to `/glab-epic-create`:**
1. Create the epic with a temporary title and empty description. Add `[AI]` suffix to mark it as auto-generated:
   ```bash
   RESPONSE=$(glab api -X POST "groups/pysae/epics" \
     -H "Content-Type: application/json" \
     --input - <<< "$(jq -n --arg title "[AI] WIP — $THEME" '{title: $title}')")
   EPIC_IID=$(echo "$RESPONSE" | jq -r '.iid')
   ```

2. Attach all issues:
   ```bash
   pysae-ai-tools glab epic-attach --epic ${EPIC_IID} \
     --issue project1#iid1 --issue project2#iid2 ...
   ```

3. Delegate to `/glab-epic-create` to generate the title (format `[YYYY-SN]`) and description from the child issues:
   ```
   /glab-epic-create <EPIC_IID>
   ```

**Attach to existing epic:** same as Step 4.

**Skip:** no action.

If `--dry-run` is set, skip all mutations and only report what would be done.

## Step 8 — Final report

Present the summary:

> **Résultat du rattachement aux epics**
>
> - **Phase 1** : X tickets rattachés à Y epics existantes
> - **Phase 2** : Z nouvelles epics créées, W tickets rattachés manuellement
> - **Ignorés** : N tickets laissés sans epic
> - **Total** : A/B tickets orphelins traités

## Rules

- **Never delete or close** issues or epics — only add parent epic links and create new epics
- **Always use `pysae-ai-tools glab epic-attach`** for attaching issues — the `/parent_epic` quick action via notes does not work reliably via API
- **One epic per issue** — GitLab only supports one parent epic per issue
- **Group epics by best match** in Phase 1 — present the most confident matches first
- **Minimum cluster size is 2** for Phase 2 epic proposals — a single isolated issue does not justify a new epic
- **French** for communication, English for commands and issue/epic titles
- **`--dry-run`** must be respected at every mutation step
- **Pagination** — always handle paginated API responses (check `X-Next-Page` header)

$ARGUMENTS
