---
name: stats-weekly-summary
description: >
  Generate a formatted weekly recap of engineering activity across Pysae repos:
  merged MRs, closed issues, production deploys, and epic progress with attached tickets.
  Output is Slack mrkdwn ready to paste.
  Use when the user says /stats-weekly-summary, /weekly-recap, 'recap de la semaine', 'weekly summary',
  'résumé hebdo', 'quoi de neuf cette semaine', 'what shipped this week',
  'recap pour le standup', 'bilan de la semaine', or wants a summary of recent
  engineering activity. Also trigger when the user asks for a team status update,
  sprint review prep, or weekly report.
argument-hint: "[--all | --summary (default)] [number of days (default 7)] [specific repo] [date range] [--channel #channel-name]"
allowed-tools:
  - Bash
  - Read
  - Glob
  - Grep
  - Write
---

Generate a weekly recap of engineering activity across the Pysae GitLab group, formatted for Slack.

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

## Parse arguments

If `$ARGUMENTS` contains:
- `--all` → verbose mode (read `references/verbose-template.md` for the full template)
- `--summary` or nothing → summary mode (default): narrative recap by epic, PM-style
- A number → use as the lookback period in days (default: 7)
- A repo name → restrict to that repo only
- A date range (`YYYY-MM-DD..YYYY-MM-DD`) → use those exact bounds
- `--channel #name` → target Slack channel for posting

Compute `SINCE` date (ISO 8601: `YYYY-MM-DDT00:00:00Z`).

Resolve Slack channel (priority): `--channel` arg → `$WEEKLY_RECAP_SLACK_CHANNEL` env var → ask user (local) or `#tech-product` (CI).

## Collect data

Run the collection command. It fetches merged MRs, closed issues, prod deploys, and epics with activity in a single pass:

```bash
pysae-ai-tools glab weekly-data --since YYYY-MM-DD [--repo REPO_NAME]
```

The command outputs a JSON object with keys: `merged_mrs`, `closed_issues`, `deploys`, `epics`. Each epic includes `closed_issues` (array), `total`, `closed_total`, and `description`.

If the command fails or a specific query times out, fall back to running individual `glab api` calls manually. The module source (`pysae_ai_tools/glab/weekly_data.py`) shows the exact queries.

**Edge cases:**
- **0 MRs returned** — likely a date-window issue, not a quiet week. Check that `$SINCE` is correct before assuming empty data.
- **0 epics with activity (or a `--repo` scope whose issues aren't attached to epics)** - do NOT dump everything into "Hors périmètre". First try **title-prefix grouping** (see "Grouping fallback" below); only fall back to a flat per-repo summary if no consistent prefix convention exists.
- **`--repo` filter + epics** — epics span multiple repos. When a repo filter is active, still show epics that have at least one closed issue in the filtered repo, but only list issues from that repo under the epic.
- **Script partial failure** — if deploys fail but MRs/issues succeed, continue with partial data and add a warning line: _"⚠️ Les données de déploiement n'ont pas pu être récupérées."_

## Enrich and deduplicate

- Cross-reference closed issues with merged MRs (via branch name pattern `*/<IID>-*` or MR closing references)
- Tag issues closed by a `~code-agent` MR (AI-assisted) → :robot_face: badge
- Group MRs and issues by project path, sort chronologically (most recent first)
- For each epic, determine deployment status: all issues closed AND deploys exist → "développement terminé et déployé"

For each epic, fetch issue descriptions and recent comments to enrich the narrative:
```bash
glab api "projects/${PROJECT_ID}/issues/${ISSUE_IID}" | jq '{title, description, labels, weight}'
glab api "projects/${PROJECT_ID}/issues/${ISSUE_IID}/notes?sort=desc&per_page=10" | \
  jq --arg since "$SINCE" '[.[] | select(.created_at >= $since and .system == false)]'
```

Also check for PM comments on the epic itself — they often contain the exact narrative tone to reflect:
```bash
glab api "groups/${GROUP_ID}/epics/${EPIC_IID}/notes?sort=desc&per_page=10" | \
  jq --arg since "$SINCE" '[.[] | select(.created_at >= $since and .system == false)]'
```

Use PM comments as the primary source when available. Complement with issue data when absent.

## Grouping fallback: title-prefix convention

Some repos (e.g. `shift`) organise work with a bracketed prefix in the **issue title** (`[E1]`, `[E8]`, ...) instead of GitLab epics. When the in-scope issues carry such a prefix but are not attached to epics, group by that prefix instead of dropping everything into "Hors périmètre":
- Derive a **readable theme name** from the shared subject of each group's issues/MRs (e.g. all `[E8]` issues mention sidebar/nav -> "Navigation & menu"). Never show the raw `[E8]` code to the reader.
- Apply the same narrative + jargon rules as for epics; order groups by work delivered this week (most active first).
- Items with no prefix stay under "Hors périmètre".

## Compute epic progress

```
raw_current  = closed_total / total * 100
raw_previous = (closed_total - closed_this_week) / total * 100
current  = round(raw_current / 5) * 5     # 52% → 50%, 67% → 65%
previous = round(raw_previous / 5) * 5
```

Show as delta: "passé de {previous} à {current}%".

## Format: summary mode (default)

Recap organized by epic (or prefix group), hybrid layout: a single emoji + `*bold*` title, then 2-3 concrete bullets. Written for #tech-product (audience: CEO, PM, CS, devs).

**Template:**
```
Bonjour à tous ! L'actualité côté produit-tech :

{emoji} *{theme_title}*{ (progress%) — only when it comes from a real GitLab epic}
- {concrete deliverable: what now exists or works, with a specific}
- {another concrete deliverable; skip if the theme only warrants one}

{repeat for each epic / prefix group with activity, one standard emoji per theme}

:open_file_folder: *Hors périmètre*
- {significant standalone MRs/issues; skip trivial fixes}

:chart_with_upwards_trend: *{TOTAL_MR_COUNT} MRs · {TOTAL_ISSUE_COUNT} tickets · {TOTAL_DEPLOY_COUNT} mises en prod*
```

**Writing rules:**

The audience is non-technical. Write for people who care about user impact, not implementation details.

- Strip the `[2026-S1]` / `[E8]` prefix from titles — show only the human-readable theme name
- **Hybrid layout** — each theme is `{emoji} *bold title*` then 2-3 concrete bullets. Prefix each title with a single *standard* Slack emoji (avoid flaky ones like `:balance_scale:`; safe palette: `:bus:` `:compass:` `:bricks:` `:lock:` `:speech_balloon:` `:bar_chart:` `:shield:` `:zap:` `:satellite:` `:open_file_folder:` `:chart_with_upwards_trend:`).
- **Concrete over vague** — each bullet states what now exists or works, with a specific (a number, a feature name). Ban progress-filler: never "nous avons bien avancé", "nette amélioration", "le chantier progresse".
- **No empty intensifiers** — drop "complet", "réaliste", "robuste", "stratégique" unless a fact backs them. Vary bullet length; a uniform title + one-sentence rhythm reads as AI-generated.
- Progress percentage (rounded to nearest 5, e.g. "(50%)") only when it comes from a real GitLab epic; omit it for prefix groups.
- **Zero jargon** — never mention: Redis, MongoDB, cache, index, WebSocket, ddtrace, Python, CI, Helm, k6, ArgoCD, endpoint, API (sauf "l'API"), refactoring, component, model, repository, branch, MR, commit, deploy (dire "mis en production" ou "livré")
- **User/business impact** — what it changes for the user, not the technical solution. "Les pages se chargent en moins d'une seconde" not "ajout de cache sur DeviceRepository"
- French, tutoiement collectif ("nous"), factual tone, no cheerleading

**Example output:**
```
Bonjour à tous ! L'actualité côté produit-tech :

:zap: *Charge sur les bases de données* (50%)
- Les pages les plus consultées se chargent en moins d'une seconde
- Les accès aux données les plus fréquents ont été optimisés et mis en production

:satellite: *Architecture événementielle* (65%)
- Correction d'un traitement qui pouvait perdre des messages sous forte charge

:open_file_folder: *Hors périmètre*
- Côté Op : envoi de SMS depuis la page d'alertes, nouveau système de notifications

:chart_with_upwards_trend: *41 MRs · 38 tickets · 8 mises en prod*
```

## Format: verbose mode (`--all`)

Read `references/verbose-template.md` for the full template and formatting rules.

## Output

### Mode interactif (local)

1. Print the formatted recap to stdout
2. Save to `weekly-recap-{START_DATE}.md`
3. Save a copy to `${CLAUDE_PLUGIN_DATA}/recaps/weekly-recap-{START_DATE}.md` for history
4. If `--channel` was provided, propose posting there. Otherwise ask: _"Prêt à poster sur Slack ? Si oui, sur quel channel ?"_
5. If confirmed, post via `pysae-ai-tools slack post-message --channel <CHANNEL_ID> --ai-footer --text -` (pipe the recap on stdin). `--ai-footer` appends the discreet "généré par Claude" attribution as a Block Kit divider + context block - do not hand-write it in the body.

### Mode CI (headless)

When `$CI` is set:
1. Save to `/tmp/weekly-recap-{START_DATE}.md`
2. Post to the resolved channel (see "Parse arguments" for priority order) with `pysae-ai-tools slack post-message --channel <CHANNEL_ID> --ai-footer --text -`, no confirmation. Auth uses `SLACK_BOT_TOKEN`; the bot must be a member of the channel.
3. Always use `--summary` mode

### Previous recap comparison

Before generating, check `${CLAUDE_PLUGIN_DATA}/recaps/` for the most recent previous recap. If found, read it to:
- Compute week-over-week deltas (MR count, issue count, deploy count)
- Note epics that made progress vs stalled vs completed since last week
- Mention trends in the footer: "En hausse par rapport à la semaine dernière (32 MRs → 41 MRs)"

## Scheduling (weekly cron)

To post on a fixed cadence (e.g. every Monday 09:00), use a **GitLab scheduled pipeline**, not a local cron:
- Schedule cron `0 9 * * 1`, timezone `Europe/Brussels`, with `WEEKLY_RECAP=true`.
- On Pysae repos including the infra templates (ref 2.x), the job `extends: .ai-tools` (its image + `setup --auth` provide claude / glab / Slack) and runs `claude-ci "/stats-weekly-summary --summary --repo <repo> --channel <id>"`.
- A `$WEEKLY_RECAP` guard in `env_rules.yml` keeps every regular job off the recap pipeline; `workflow.rules` must allow `$CI_PIPELINE_SOURCE == 'schedule' && $WEEKLY_RECAP == "true"`.

See `references/gitlab-schedule.example.yml` for the ready job.

## Rules

- **No empty sections** — omit any section with 0 items
- **Slack mrkdwn** — `*bold*`, `<url|text>` links, `:emoji:`, `•` bullets
- **AI badge** — :robot_face: on any MR or issue with the `code-agent` label
- **Weight** — show points only when issues have weight, skip `0pt`
- **Respect rate limits** — the collect script handles this with `sleep 0.2`
- **Timeout gracefully** — if any API call fails, log warning and continue with partial data
- **French** — all section headers and summary in French
- **Langue française** : respecter les [règles de rédaction en français](../references/french-writing-rules.md) pour tout texte généré
- **Summary default** — `--summary` is default, `--all` must be explicitly requested

## Gotchas

- **GitLab API rate limits** — the collect script paginates automatically (up to 500 results per query). If the week has >500 MRs, raise `max_pages` in the script. The group ID is resolved from: `--group-id` arg → `$PYSAE_GROUP_ID` env var → fallback `918966`.
- **Prod environment naming** — Pysae uses `<app>-prod` (e.g. `api-prod`), NOT `production`. The script filters with `grep -E '\-prod$'`. If a new app uses a different naming convention, it won't be detected.
- **Epic progress rounding** — rounding to nearest 5% can hide small movements. If 1 issue out of 50 is closed, that's 2% → rounds to 0%. The delta will show "0 → 0%" even though work was done. Mention the closed issue explicitly in this case.
- **MR merged_at vs updated_after** — the API filters by `updated_after` not `merged_after`. An old MR updated (commented on) this week will appear in results even if it was merged months ago. The script's `jq` filter on `merged_at >= $SINCE` handles this, but if the script is bypassed, remember to filter.
- **Slack mrkdwn ≠ markdown** — `*bold*` not `**bold**`, no headers (`#`), no code blocks with language hints. Links are `<url|text>` not `[text](url)`. No `---` horizontal rule (renders literally; use the `--ai-footer` divider or blank lines).
- **Never re-edit a footed message** — `slack update-message` rewrites only `--text` (plain mrkdwn) and strips the footer's divider + context blocks. For recurring posts, always post fresh; never edit to refresh the footer.
- **Emoji must render** — only use standard Slack emoji confirmed to display in the target workspace; some (e.g. `:balance_scale:`) are missing and show as raw text.
- **`glab` auth scope** — the bot token needs `read_api` on the group. If deploys return empty, check that the token has access to the project's environments.

$ARGUMENTS
