⏳ This skill is pending AI review.

Scores will appear once the review pipeline completes.

version unknown

project-kickoff

@mahelmouzoun07-afk⭐ 0 stars

Prépare un projet web, SaaS, mobile, e-commerce, scolaire ou Odoo AVANT d'écrire du code, adapté au profil de l'utilisateur (niveau, pays, stacks préférées, leçons des projets passés). Entretien de cadrage, PRD relu par un avocat du diable, stack, estimation, design, repo GitHub, CI, CLAUDE.md, permissions, hooks, MCP, issues, et un tableau de préparation vérifié automatiquement par script. À utiliser pour « nouveau projet », « je veux créer un site/une app », « kickoff », « reprendre/auditer un projet existant », « /kickoff-status », « où en est le projet ? », « rétro », « fin de projet ».

Choose how to use this skill

You do not need every option. Choose the path your AI client supports. The stable page stays the same; versioned files are immutable.

1. Native installer

This listing has no registered native installer command. Use the complete package or source fallback below, depending on what your client supports.

Do not guess an installer command or replace an existing version without reviewing the diff.

2. Complete package recommended

Download the ZIP when available. It includes SKILL.md plus the references, security notes and version metadata.

No complete ProSkills package is published for this listing yet.

3. Prompt-only

Copy the prompt above when the agent can read the stable page or when you want to adopt the workflow without installing a skill.

Need only the instruction file?

Download SKILL.md only if your client requires a single file. The complete ZIP is safer for a full installation because it preserves the references and release context.

No path installs or executes anything by itself. Your agent still needs access to the project files. Before updating, compare the installed version and review the diff.

—/10

// RATINGS

⭐GitHub Stars
⭐ 0 on GitHubGitHub ↗

New / niche

🟢ProSkills Score
—
📍

Not yet listed on ClawHub or SkillsMP

// README

project-kickoff

Un skill Claude Code qui empêche de coder tant que le projet n'est pas compris, décidé et outillé.

En vibe coding, les projets ratent presque toujours pour les mêmes raisons :

  • le besoin est flou ;
  • la stack est choisie au hasard ;
  • Claude oublie les conventions d'une session à l'autre ;
  • sans tests ni CI, les régressions passent inaperçues ;
  • les secrets se retrouvent dans Git ;
  • sans plan, on avance de patch en patch.

project-kickoff transforme une idée en un socle vérifiable avant la première ligne de code. Il s'adapte à ton niveau, à ton pays, à tes stacks préférées et aux leçons de tes projets passés.

Le tableau de préparation

Le cœur du skill : un tableau vérifié par un script, pas déclaré à la main.

#ÉlémentStatutPreuveTypeRisque si sauté
2PRD + critères d'acceptation✅6 user stories, sections complètesbloquantClaude devine le besoin
3Revue « avocat du diable »⚠️4 points bloquants non résolusrecommandéCas limites découverts en plein dev
10Secrets protégés❌SECRET SUIVI PAR GIT : .envbloquantClé API publiée sur GitHub
…

Score : 8/15 — ❌ Pas prêt. Le script signale aussi les régressions depuis la dernière vérification. Le tableau est rappelé au début de chaque session et à tout moment avec /kickoff-status.

Ce que fait le skill

  1. Profil : 6 questions la première fois, enregistrées dans ~/.claude/kickoff-profile.md, puis réutilisées pour chaque projet.
  2. Pack et mode : 7 types de projet (vitrine, SaaS, e-commerce, mobile, outil interne, école, module Odoo) et 3 niveaux de profondeur (express, standard, complet). Un projet existant passe d'abord par un audit.
  3. Cadrage → docs/PRD.md : 12 questions générales, dont les cas limites, plus les questions du pack et du pays.
  4. Avocat du diable → docs/REVIEW.md : un sous-agent sans contexte critique le PRD.
  5. Stack et estimation → docs/ARCHITECTURE.md, docs/adr/, docs/ESTIMATION.md (prix vérifiés en ligne).
  6. Design → docs/DESIGN.md.
  7. Repo GitHub : CI, Dependabot, modèles d'issue et de PR, secrets protégés.
  8. Claude Code : CLAUDE.md, permissions, hooks (formatage et rappel en début de session), MCP de projet, /kickoff-status.
  9. Plan → docs/PLAN.md en tranches verticales, puis issues et milestones GitHub.
  10. Rétrospective en fin de projet : les leçons enrichissent ton profil, donc le prochain kickoff.

Toute action visible de l'extérieur (créer un repo, pousser, créer des issues, déployer) est soumise à ton accord.

Installation

Pour tous tes projets :

git clone https://github.com/mahelmouzoun07-afk/project-kickoff.git ~/.claude/skills/project-kickoff

Pour un seul projet, clone-le dans .claude/skills/project-kickoff à la racine du projet.

Prérequis : Claude Code, Git et Node.js ≥ 20. GitHub CLI (gh) est recommandé.

Utilisation

Dans Claude Code, dis simplement :

  • « nouveau projet : une app de réservation pour coiffeurs »
  • « reprends ce projet existant »
  • /kickoff-status (une fois le kickoff fait)
  • « rétro » en fin de projet

Structure

SKILL.md                    orchestration des phases
references/                 profil, pays, reprise, avocat du diable, estimation, issues, rétro
references/packs/           un fichier par type de projet
scripts/check.mjs           le tableau de préparation (copié dans le projet)
templates/                  CLAUDE.md, settings.json, hooks, /kickoff-status, kickoff.json
evals/                      tests du script et scénarios de bout en bout

Tester après une modification

node evals/check.test.mjs

Voir evals/README.md pour les scénarios joués par Claude.

Limites

  • Les informations par pays et les prix sont des pistes. Le skill impose de les vérifier en ligne à chaque projet.
  • La partie en ligne du tableau (CI, issues, preview) nécessite gh connecté.

Licence

MIT

// HOW IT'S BUILT

// FILES

30 files shipped with this skill. Inline text is the catalog copy. The upstream link is the source repository.

  • SKILL.md13 KB24dfca1d9800upstream ↗
    ---
    name: project-kickoff
    description: Prépare un projet web, SaaS, mobile, e-commerce, scolaire ou Odoo AVANT d'écrire du code, adapté au profil de l'utilisateur (niveau, pays, stacks préférées, leçons des projets passés). Entretien de cadrage, PRD relu par un avocat du diable, stack, estimation, design, repo GitHub, CI, CLAUDE.md, permissions, hooks, MCP, issues, et un tableau de préparation vérifié automatiquement par script. À utiliser pour « nouveau projet », « je veux créer un site/une app », « kickoff », « reprendre/auditer un projet existant », « /kickoff-status », « où en est le projet ? », « rétro », « fin de projet ».
    ---
    
    # Project Kickoff — on ne code rien tant que le tableau n'est pas vert
    
    **Problème résolu.** En vibe coding, les projets ratent pour les mêmes raisons :
    - le besoin est flou ;
    - la stack est choisie au hasard ;
    - Claude oublie les conventions d'une session à l'autre ;
    - sans tests ni CI, les régressions passent inaperçues ;
    - les secrets se retrouvent dans Git ;
    - sans plan, on avance de patch en patch.
    
    Ce skill produit un **socle vérifiable**, adapté à la personne et au type de projet. Il le résume dans un **tableau de préparation** que le script `scripts/check.mjs` vérifie réellement.
    
    ## Fichiers du skill (lis seulement ce qui sert)
    | Fichier | Quand le lire |
    |---|---|
    | `references/profil.md` | Étape A, toujours |
    | `references/packs/<type>.md` | Étape B, seulement le pack choisi : `vitrine`, `saas`, `ecommerce`, `mobile`, `outil-interne`, `ecole`, `odoo` |
    | `references/pays.md` | Phases 1, 2 et 8 |
    | `references/reprise.md` | Si le dossier contient déjà du code |
    | `references/avocat-du-diable.md` | Phase 1b |
    | `references/estimation.md` | Phase 2b |
    | `references/issues-github.md` | Phase 6b |
    | `references/retrospective.md` | Sur « rétro » ou « fin de projet » |
    | `scripts/check.mjs` | Copié dans le projet en `scripts/kickoff-check.mjs` à l'étape B |
    | `templates/` | `CLAUDE.md`, `settings.json`, `hooks/format.mjs`, `commands/kickoff-status.md`, `kickoff.json` |
    | `evals/` | Seulement pour modifier ou tester le skill lui-même |
    
    ## Règles de conduite
    - Avance phase par phase. **À la fin de chaque phase, relance le script et montre le tableau**, puis attends le « ok » de l'utilisateur.
    - **Ne marque jamais une ligne ✅ à la main.** Seul le script décide. Une ligne ne devient ➖ que par `na` dans `docs/kickoff.json`, avec une raison acceptée par l'utilisateur. Les lignes bloquantes ne peuvent pas être écartées, sauf par le mode express.
    - Pose les questions par lots de 3 à 4 (AskUserQuestion), avec une réponse recommandée. Ne demande jamais ce que le profil, le pack ou le code disent déjà.
    - Adapte le ton, la quantité de questions et le vocabulaire au **niveau** (tableau dans `references/profil.md`).
    - **Demande toujours une confirmation explicite** avant toute action visible de l'extérieur : créer un repo, faire un push, créer des issues, déployer, acheter.
    - Les fichiers sont la source de vérité, pas la conversation.
    
    ---
    
    ## Étape A — Profil de l'utilisateur
    Lis `~/.claude/kickoff-profile.md`.
    - **S'il est absent** : pose les 6 questions de `references/profil.md` et écris le fichier.
    - **S'il existe** : résume-le en 3 lignes et demande « Toujours vrai ? ». Applique ses **Leçons** : chacune devient une question ou une vérification en plus.
    
    ## Étape B — Situation, pack et mode
    1. **Projet existant ?** Si le dossier contient du code, suis `references/reprise.md` : audit d'abord, puis seulement les phases nécessaires.
    2. **Pack** : déduis le type de projet de la description et confirme-le (vitrine, saas, ecommerce, mobile, outil-interne, ecole, odoo). Lis ce pack. Si aucun ne colle, utilise `saas` et adapte.
    3. **Mode** (propose celui que le pack conseille) :
    
    | | Express (~10 min) | Standard (~25 min) | Complet (~45 min) |
    |---|---|---|---|
    | Pour | prototype, hackathon, test d'idée | la plupart des projets | paiement, données sensibles ou de mineurs, client final |
    | PRD | 1 page, stories Must seulement | complet | complet + matrice des rôles |
    | Avocat du diable | ➖ | recommandé | **bloquant** |
    | Estimation | ➖ | recommandée | **bloquante** |
    | Design | tokens dans CLAUDE.md | DESIGN.md | DESIGN.md + parcours détaillés |
    | CI, Dependabot, issues | ➖ | oui | oui |
    | Phase 8 (production) | domaine + secrets | complète | complète + sauvegardes testées et revue de sécurité |
    
    Données de santé, de paiement, biométriques ou de mineurs : le mode **complet** est obligatoire.
    
    4. **Initialiser le tableau.** Crée `docs/kickoff.json` depuis `templates/kickoff.json` (projet, mode, niveau, pack, pays, gestionnaire de paquets, `currentMilestone: "Cadrage"`). Copie `scripts/check.mjs` vers `scripts/kickoff-check.mjs`, ajoute `.claude/kickoff-state.json` au `.gitignore`, puis lance `node scripts/kickoff-check.mjs`. **Montre ce premier tableau**, presque entièrement ❌ : c'est le point de départ.
    
    ## Phase 0 — Machine
    ```bash
    git --version; node --version; npm --version; pnpm --version; bun --version; gh --version; gh auth status; docker --version
    ```
    Ajoute les outils propres au pack (Python, ruff et Docker pour Odoo ; EAS CLI pour mobile).
    - **Gestionnaire de paquets** : un gestionnaire **déjà installé** (préférence du profil, sinon pnpm, puis bun, puis npm). Il remplace `<pm>` dans tous les modèles. Note-le dans `kickoff.json` et dans le profil.
    - **Windows** : `git config core.longpaths true` dans le repo, et un `.gitattributes` avec `* text=auto eol=lf`.
    
    ## Phase 1 — Comprendre le projet → `docs/PRD.md`
    Questions générales (un débutant n'a que les n° 1, 2, 3, 4, 5, 7, 10 et 12 ; le reste reçoit une valeur par défaut notée en Hypothèses) :
    1. Quel problème, pour qui ? Persona principal et secondaire.
    2. Quelle action unique l'utilisateur doit-il pouvoir faire au jour 1 ?
    3. Les 5 à 10 fonctionnalités du MVP, en MoSCoW.
    4. **Hors périmètre** v1.
    5. Rôles et authentification.
    6. Données : entités, relations, données personnelles, fichiers.
    7. Paiement, notifications, temps réel ?
    8. Langues, SEO, accessibilité, mobile d'abord.
    9. Contraintes : budget, échéance, compétences, hébergeur imposé.
    10. Références visuelles et identité de marque.
    11. Mesure du succès.
    12. **Cas limites** : actions simultanées, double clic, action dans le passé, suppression d'un élément lié, fuseau horaire, devise, réseau coupé. Chaque garantie d'intégrité est imposée **en base**.
    
    Ajoute ensuite les **questions du pack** et celles qui viennent du **pays** (`pays.md` : droit des données, paiement, canaux de notification, réseau).
    Structure du PRD : Vision · Utilisateurs · User stories avec **critères d'acceptation** testables · MVP (MoSCoW) · Hors périmètre · Exigences non fonctionnelles · Hypothèses · Questions ouvertes · Mesure du succès.
    
    ### Phase 1b — Avocat du diable (standard et complet)
    Suis `references/avocat-du-diable.md` : un sous-agent sans contexte relit le PRD et écrit `docs/REVIEW.md`. Corrige le PRD avec l'utilisateur jusqu'à ce qu'aucun point `[Bloquant]` ne reste ouvert.
    
    ## Phase 2 — Stack → `docs/ARCHITECTURE.md` + `docs/adr/`
    Ordre de priorité : **stack imposée** > **préférences du profil** (si elles conviennent au besoin) > **stack du pack**. Une préférence inadaptée doit être signalée honnêtement dans l'ADR, avec les alternatives écartées.
    Socle transverse si pertinent : Zod, react-hook-form, Vitest + Playwright, Biome, Resend, Sentry, Plausible ou PostHog.
    `ARCHITECTURE.md` contient : un schéma Mermaid du flux, le modèle de données (`erDiagram`), l'arborescence, la **liste des variables d'environnement**, et la stratégie d'authentification et d'autorisation.
    Pour un niveau débutant, une seule recommandation, expliquée simplement. Pour un expert, un tableau comparatif.
    
    ### Phase 2b — Estimation (standard et complet) → `docs/ESTIMATION.md`
    Suis `references/estimation.md`. Les prix sont **vérifiés en ligne**, jamais donnés de mémoire. Compare le résultat au budget et à l'échéance. S'ils sont dépassés, réduis le MVP avec l'utilisateur.
    
    ## Phase 3 — Design → `docs/DESIGN.md`
    Tokens (couleurs clair et sombre, typo, espacements, rayons), composants de base, chaque écran avec ses états chargement, vide et erreur, et le parcours critique. Utilise les skills de design installés (`frontend-design`, `design-system-creation`, `tailwind-v4-shadcn`, `responsive-web-design`). En mode express, les tokens vont dans `CLAUDE.md`.
    
    ## Phase 4 — Repo GitHub
    Après confirmation : `git init`, puis `gh repo create <nom> --private --source=. --remote=origin`.
    - `.gitignore` (inclut `.env*` sauf l'exemple, et `.claude/kickoff-state.json`), `.gitattributes`, `.env.example` documenté (ou `odoo.conf.example` : dans ce cas, renseigne `envExample` dans `kickoff.json`), `README.md` et `LICENSE` si le projet est public.
    - `.github/workflows/ci.yml` : install → lint → typecheck → test → build avec `<pm>` (bun : `oven-sh/setup-bun@v2` ; pnpm : `pnpm/action-setup`). Pour Odoo, utilise la CI du pack. La CI reste rouge jusqu'à ce que J0 crée les scripts.
    - `.github/dependabot.yml`, modèles d'issue et de PR (la checklist reprend la Definition of Done).
    - Donne à l'utilisateur le lien des réglages GitHub pour activer la protection de `main`, le secret scanning et la push protection.
    
    ## Phase 5 — Configurer Claude Code
    1. **`CLAUDE.md`** depuis `templates/CLAUDE.md`, entièrement rempli. Le script signale tout `<…>` restant. Ajoute les **Leçons** du profil qui concernent ce projet.
    2. **`.claude/settings.json`** depuis `templates/settings.json` (remplace `<pm>`), et **`.claude/hooks/format.mjs`** :
       - permissions : interdit la lecture des vrais `.env` (pas de l'exemple), le `push --force` et le `rm -rf` ;
       - hook **PostToolUse** : formate chaque fichier modifié avec Biome, Prettier ou ruff ;
       - hook **SessionStart** : à chaque nouvelle session, Claude reçoit le jalon en cours et les lignes non vertes du tableau.
    3. **`.claude/commands/kickoff-status.md`** : la commande `/kickoff-status`.
    4. **MCP de projet** (après accord), enregistrés dans `.mcp.json` pour être partagés et vérifiables :
       ```bash
       claude mcp add --scope project context7 -- npx -y @upstash/context7-mcp
       claude mcp add --scope project playwright -- npx @playwright/mcp@latest
       ```
       Ajoute aussi les MCP propres à la stack : GitHub, Supabase, Vercel ou Cloudflare.
    5. **Skills** : liste ceux qui sont installés et propose ceux qui manquent pour la stack. Sources fiables : `anthropics/skills` (officiel) et `obra/superpowers`. **Lis toujours un skill tiers avant de l'installer.**
    
    ## Phase 6 — Plan → `docs/PLAN.md`
    Des jalons en **tranches verticales** (de la base de données à l'écran, avec le test), en partant des jalons types du pack. J0 : squelette, CI verte et preview en ligne dès le premier jour. Chaque jalon a un critère « terminé quand ». Tailles et durées viennent de l'estimation.
    
    ### Phase 6b — Issues GitHub (standard et complet)
    Suis `references/issues-github.md` : un milestone par jalon, une issue par story avec ses critères en cases à cocher. Montre la liste et attends l'accord avant de créer quoi que ce soit.
    
    ## Phase 7 — Definition of Done (dans CLAUDE.md)
    Une tâche est terminée seulement si :
    - ses critères sont vérifiés dans un vrai navigateur ;
    - les tests sont ajoutés et la CI est verte ;
    - les états chargement, vide et erreur sont gérés et le rendu mobile est vérifié ;
    - il n'y a aucun secret dans le code et les entrées sont validées côté serveur ;
    - la documentation est à jour.
    
    ## Phase 8 — Production
    Hébergeur et environnements · domaine · variables d'environnement chez l'hébergeur · Sentry · analytics · sauvegardes (testées en mode complet) · rate limiting · en-têtes de sécurité · pages légales **selon le pays** (`pays.md`) · `robots.txt`, sitemap et Open Graph.
    
    ---
    
    ## La porte de sortie : le tableau de préparation
    Lance `node scripts/kickoff-check.mjs` (en ligne). Le script :
    - vérifie **réellement** chaque ligne : sections du PRD, ADR, placeholders du CLAUDE.md, hook exécuté, `.env` ignoré, variables utilisées mais non documentées, secret suivi par Git, dernière CI, issues, preview qui répond ;
    - écrit `docs/KICKOFF.md` avec les colonnes **Statut** (✅ ⚠️ ❌ ➖), **Preuve**, **Type** (bloquant, recommandé, après J0, info) et **Risque si sauté** ;
    - calcule le **score** et le **verdict** : ✅ Prêt · ⚠️ Prêt avec réserves · ❌ Pas prêt ;
    - liste les **régressions** depuis la dernière vérification.
    
    Montre le tableau en entier.
    - **❌ Pas prêt** : propose de traiter les lignes bloquantes.
    - **⚠️ Réserves** : demande à l'utilisateur s'il les accepte explicitement.
    - **✅ Prêt**, ou réserves acceptées : passe `currentMilestone` à `"J0"` et propose de démarrer.
    
    ## Après le kickoff
    - **`/kickoff-status`** à tout moment : tableau à jour, régressions expliquées, 3 prochaines actions.
    - **À chaque jalon terminé** : mets à jour `currentMilestone` dans `docs/kickoff.json` et `previewUrl` dès que la preview existe.
    - **Fin de projet ou « rétro »** : `references/retrospective.md`. Les leçons enrichissent le profil, donc le prochain kickoff.
    
    ## Modifier ce skill
    Après toute modification, lance `node evals/check.test.mjs` (tests du script) et rejoue au moins un scénario de `evals/scenarios/` selon `evals/README.md`.
    
  • evals/README.md2.0 KB973e6aa4bee7upstream ↗
    # Tester le skill project-kickoff
    
    Deux niveaux de test, à lancer après **chaque** modification du skill.
    
    ## 1. Tests du script du tableau (automatiques, ~30 s)
    ```bash
    node evals/check.test.mjs
    ```
    Ils créent des projets temporaires (vide, complet, secret commité, variable non documentée, régression, modes express, standard et complet, Odoo…) et vérifient chaque statut. Tous doivent passer.
    
    ## 2. Scénarios de bout en bout (avec Claude)
    Chaque fichier de `scenarios/` décrit un utilisateur fictif, ses réponses et ce que le skill **doit** produire.
    
    Procédure pour un scénario :
    1. Crée un dossier vide hors de tout projet réel, par exemple `test-run/<scenario>`.
    2. Lance un sous-agent avec ce prompt :
       > Utilise le skill project-kickoff dans le dossier `<chemin>`. Tu joues aussi l'utilisateur : réponds aux questions uniquement avec les « Réponses de l'utilisateur » du scénario `<fichier>`. Si une question n'y est pas, choisis la réponse recommandée. Utilise un profil temporaire : écris-le dans `<chemin>/.profile-test.md` au lieu de `~/.claude/kickoff-profile.md`. Ne crée rien sur GitHub, ne pousse rien et n'installe aucun MCP : simule ces étapes en notant les commandes que tu aurais lancées. Termine en lançant `node scripts/kickoff-check.mjs --offline`.
    3. Compare le résultat à la section « Attendus » du scénario. Chaque attendu non respecté est un défaut à corriger dans le skill.
    4. Ajoute un scénario chaque fois qu'un vrai projet révèle un oubli (voir `references/retrospective.md`).
    
    ## Grille de notation (sur 10)
    | Critère | Points |
    |---|---|
    | Le pack et le mode attendus sont choisis | 2 |
    | Les questions spécifiques du pack et du pays ont été posées | 2 |
    | Les cas limites attendus figurent dans le PRD ou la revue | 2 |
    | Les lignes ➖ du tableau sont celles attendues, avec leur raison | 2 |
    | Aucun ✅ mis à la main, aucune action externe sans accord | 2 |
    
    Un score inférieur à 8 signale un défaut dans le skill.
    
  • README.md4.1 KB99a40cfc2f97upstream ↗
    # project-kickoff
    
    **Un skill Claude Code qui empêche de coder tant que le projet n'est pas compris, décidé et outillé.**
    
    En vibe coding, les projets ratent presque toujours pour les mêmes raisons :
    - le besoin est flou ;
    - la stack est choisie au hasard ;
    - Claude oublie les conventions d'une session à l'autre ;
    - sans tests ni CI, les régressions passent inaperçues ;
    - les secrets se retrouvent dans Git ;
    - sans plan, on avance de patch en patch.
    
    `project-kickoff` transforme une idée en un socle vérifiable avant la première ligne de code. Il s'adapte à ton niveau, à ton pays, à tes stacks préférées et aux leçons de tes projets passés.
    
    ## Le tableau de préparation
    Le cœur du skill : un tableau **vérifié par un script**, pas déclaré à la main.
    
    | # | Élément | Statut | Preuve | Type | Risque si sauté |
    |---|---|---|---|---|---|
    | 2 | PRD + critères d'acceptation | ✅ | 6 user stories, sections complètes | bloquant | Claude devine le besoin |
    | 3 | Revue « avocat du diable » | ⚠️ | 4 points bloquants non résolus | recommandé | Cas limites découverts en plein dev |
    | 10 | Secrets protégés | ❌ | SECRET SUIVI PAR GIT : .env | bloquant | Clé API publiée sur GitHub |
    | … | | | | | |
    
    **Score : 8/15 — ❌ Pas prêt.** Le script signale aussi les régressions depuis la dernière vérification. Le tableau est rappelé au début de chaque session et à tout moment avec `/kickoff-status`.
    
    ## Ce que fait le skill
    1. **Profil** : 6 questions la première fois, enregistrées dans `~/.claude/kickoff-profile.md`, puis réutilisées pour chaque projet.
    2. **Pack et mode** : 7 types de projet (vitrine, SaaS, e-commerce, mobile, outil interne, école, module Odoo) et 3 niveaux de profondeur (express, standard, complet). Un projet existant passe d'abord par un audit.
    3. **Cadrage** → `docs/PRD.md` : 12 questions générales, dont les cas limites, plus les questions du pack et du pays.
    4. **Avocat du diable** → `docs/REVIEW.md` : un sous-agent sans contexte critique le PRD.
    5. **Stack et estimation** → `docs/ARCHITECTURE.md`, `docs/adr/`, `docs/ESTIMATION.md` (prix vérifiés en ligne).
    6. **Design** → `docs/DESIGN.md`.
    7. **Repo GitHub** : CI, Dependabot, modèles d'issue et de PR, secrets protégés.
    8. **Claude Code** : `CLAUDE.md`, permissions, hooks (formatage et rappel en début de session), MCP de projet, `/kickoff-status`.
    9. **Plan** → `docs/PLAN.md` en tranches verticales, puis issues et milestones GitHub.
    10. **Rétrospective** en fin de projet : les leçons enrichissent ton profil, donc le prochain kickoff.
    
    Toute action visible de l'extérieur (créer un repo, pousser, créer des issues, déployer) est soumise à ton accord.
    
    ## Installation
    Pour tous tes projets :
    ```bash
    git clone https://github.com/mahelmouzoun07-afk/project-kickoff.git ~/.claude/skills/project-kickoff
    ```
    Pour un seul projet, clone-le dans `.claude/skills/project-kickoff` à la racine du projet.
    
    Prérequis : [Claude Code](https://claude.com/claude-code), Git et Node.js ≥ 20. GitHub CLI (`gh`) est recommandé.
    
    ## Utilisation
    Dans Claude Code, dis simplement :
    - « nouveau projet : une app de réservation pour coiffeurs »
    - « reprends ce projet existant »
    - `/kickoff-status` (une fois le kickoff fait)
    - « rétro » en fin de projet
    
    ## Structure
    ```
    SKILL.md                    orchestration des phases
    references/                 profil, pays, reprise, avocat du diable, estimation, issues, rétro
    references/packs/           un fichier par type de projet
    scripts/check.mjs           le tableau de préparation (copié dans le projet)
    templates/                  CLAUDE.md, settings.json, hooks, /kickoff-status, kickoff.json
    evals/                      tests du script et scénarios de bout en bout
    ```
    
    ## Tester après une modification
    ```bash
    node evals/check.test.mjs
    ```
    Voir [evals/README.md](evals/README.md) pour les scénarios joués par Claude.
    
    ## Limites
    - Les informations par pays et les prix sont des pistes. Le skill impose de les vérifier en ligne à chaque projet.
    - La partie en ligne du tableau (CI, issues, preview) nécessite `gh` connecté.
    
    ## Licence
    MIT
    
  • scripts/check.mjs21 KB57976ef8da5bupstream ↗
    #!/usr/bin/env node
    // Tableau de préparation du projet : vérifie réellement chaque ligne, écrit docs/KICKOFF.md
    // et signale ce qui s'est dégradé depuis la dernière vérification.
    // Usage : node scripts/kickoff-check.mjs [--offline] [--summary] [--json] [--strict]
    //   --offline  pas d'appel réseau (gh, preview) : réutilise le dernier résultat connu
    //   --summary  résumé court sur stdout, n'écrit aucun fichier (utilisé par le hook SessionStart)
    //   --json     sortie JSON
    //   --strict   code de sortie 1 si le projet n'est pas prêt
    import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync, statSync } from "node:fs";
    import { spawnSync } from "node:child_process";
    import { join, extname } from "node:path";
    import { homedir } from "node:os";
    
    const args = new Set(process.argv.slice(2));
    const OFFLINE = args.has("--offline");
    const root = process.env.KICKOFF_ROOT || process.env.CLAUDE_PROJECT_DIR || process.cwd();
    const p = (...parts) => join(root, ...parts);
    const read = (file) => (existsSync(p(file)) ? readFileSync(p(file), "utf8") : "");
    const run = (cmd, cmdArgs) => {
      const r = spawnSync(cmd, cmdArgs, { cwd: root, encoding: "utf8", timeout: 20000 });
      return { ok: r.status === 0, out: (r.stdout || "").trim(), code: r.status };
    };
    
    const OK = "✅", PARTIAL = "⚠️", MISSING = "❌", NA = "➖";
    
    let config = {};
    try {
      config = JSON.parse(read("docs/kickoff.json") || "{}");
    } catch {
      config = { _invalid: true };
    }
    const mode = config.mode || "standard";
    // Fichier d'exemple des secrets : .env.example par défaut, odoo.conf.example pour un module Odoo, etc.
    const envExample = config.envExample || ".env.example";
    const statePath = p(".claude", "kickoff-state.json");
    let previous = {};
    try {
      previous = JSON.parse(readFileSync(statePath, "utf8"));
    } catch {}
    
    const isGit = run("git", ["rev-parse", "--is-inside-work-tree"]).ok;
    const remote = isGit ? run("git", ["remote", "get-url", "origin"]) : { ok: false };
    const ghRepo = !OFFLINE && remote.ok ? run("gh", ["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"]) : { ok: false };
    
    // Résultat d'une ligne dépendant du réseau, quand on est hors ligne : on réutilise le dernier connu.
    const fromCache = (id, fallback) => {
      const last = previous.rows?.find((r) => r.id === id);
      if (!last) return fallback;
      const when = previous.checkedAt ? new Date(previous.checkedAt).toLocaleString("fr-FR", { dateStyle: "short", timeStyle: "short" }) : "inconnue";
      return { status: last.status, proof: `${last.proof} (dernière vérif. en ligne : ${when})` };
    };
    
    const missingSections = (text, sections) => sections.filter(([, re]) => !re.test(text)).map(([name]) => name);
    
    // Variables d'environnement utilisées dans le code mais absentes de .env.example.
    const envDrift = () => {
      const documented = new Set([...read(".env.example").matchAll(/^\s*([A-Z][A-Z0-9_]*)\s*=/gm)].map((m) => m[1]));
      const ignoredNames = /^(NODE_ENV|CI|PORT|VERCEL_.*|NEXT_RUNTIME|TZ)$/;
      const used = new Set();
      const exts = new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".vue", ".svelte", ".astro", ".py"]);
      const skip = new Set(["node_modules", ".git", ".next", "dist", "build", ".venv", "venv", "coverage", ".claude", "scripts"]);
      const walk = (dir, depth = 0) => {
        if (depth > 8) return;
        let entries = [];
        try {
          entries = readdirSync(dir);
        } catch {
          return;
        }
        for (const name of entries) {
          if (skip.has(name)) continue;
          const full = join(dir, name);
          let st;
          try {
            st = statSync(full);
          } catch {
            continue;
          }
          if (st.isDirectory()) walk(full, depth + 1);
          else if (exts.has(extname(name)) && st.size < 500_000) {
            const src = readFileSync(full, "utf8");
            for (const m of src.matchAll(/(?:process\.env|import\.meta\.env|Deno\.env\.get\(["'])\.?([A-Z][A-Z0-9_]*)/g)) used.add(m[1]);
            for (const m of src.matchAll(/os\.(?:environ(?:\.get)?\(?\[?|getenv\()["']([A-Z][A-Z0-9_]*)/g)) used.add(m[1]);
          }
        }
      };
      walk(root);
      return [...used].filter((v) => !documented.has(v) && !ignoredNames.test(v)).sort();
    };
    
    const rows = [
      {
        id: "profil", label: "Profil utilisateur", where: "~/.claude/kickoff-profile.md", type: "info",
        risk: "Questions répétées à chaque projet, ton inadapté au niveau",
        check: () => (existsSync(join(homedir(), ".claude", "kickoff-profile.md")) ? { status: OK, proof: "profil trouvé" } : { status: MISSING, proof: "aucun profil" }),
      },
      {
        id: "prd", label: "PRD + critères d'acceptation", where: "docs/PRD.md", type: "bloquant",
        risk: "Claude devine le besoin : fonctionnalités inutiles, oublis, scope creep",
        check: () => {
          const t = read("docs/PRD.md");
          if (t.trim().length < 200) return { status: MISSING, proof: t ? "fichier quasi vide" : "fichier absent" };
          const miss = missingSections(t, [
            ["critères d'acceptation", /crit[èe]res? d.acceptation/i],
            ["hors périmètre", /hors[ -]p[ée]rim[èe]tre|won.t/i],
            ["hypothèses", /hypoth[èe]ses?/i],
            ["exigences non fonctionnelles", /non[ -]fonctionnel/i],
          ]);
          const stories = (t.match(/en tant qu/gi) || []).length;
          if (miss.length) return { status: PARTIAL, proof: `manque : ${miss.join(", ")}` };
          return { status: OK, proof: `${stories} user stories, sections complètes` };
        },
      },
      {
        id: "revue", label: "Revue « avocat du diable »", where: "docs/REVIEW.md", type: mode === "complet" ? "bloquant" : "recommandé",
        risk: "Cas limites et contradictions découverts en plein développement",
        check: () => {
          const t = read("docs/REVIEW.md");
          if (!t.trim()) return { status: MISSING, proof: "revue non faite" };
          const open = (t.match(/- \[ \] \[Bloquant\]/gi) || []).length;
          const done = (t.match(/- \[x\]/gi) || []).length;
          return open ? { status: PARTIAL, proof: `${open} point(s) bloquant(s) non résolu(s)` } : { status: OK, proof: `${done} point(s) traité(s), aucun bloquant ouvert` };
        },
      },
      {
        id: "architecture", label: "Architecture + modèle de données + ADR", where: "docs/ARCHITECTURE.md, docs/adr/", type: "bloquant",
        risk: "Stack choisie au hasard, données mal modélisées, refonte coûteuse",
        check: () => {
          const t = read("docs/ARCHITECTURE.md");
          if (!t.trim()) return { status: MISSING, proof: "fichier absent" };
          const adrs = existsSync(p("docs/adr")) ? readdirSync(p("docs/adr")).filter((f) => f.endsWith(".md")).length : 0;
          const miss = missingSections(t, [
            ["variables d'environnement", /variables? d.environnement/i],
            ["modèle de données", /erDiagram|mod[èe]le de donn[ée]es/i],
          ]);
          if (!adrs) miss.push("ADR");
          return miss.length ? { status: PARTIAL, proof: `manque : ${miss.join(", ")}` } : { status: OK, proof: `${adrs} ADR, modèle de données et variables documentés` };
        },
      },
      {
        id: "estimation", label: "Estimation coûts + temps", where: "docs/ESTIMATION.md", type: mode === "complet" ? "bloquant" : "recommandé",
        risk: "Facture d'hébergement ou délai qui surprennent en cours de route",
        check: () => (read("docs/ESTIMATION.md").trim() ? { status: OK, proof: "estimation présente" } : { status: MISSING, proof: "fichier absent" }),
      },
      {
        id: "design", label: "Design system + écrans", where: "docs/DESIGN.md", type: "recommandé",
        risk: "Interface incohérente d'une page à l'autre",
        check: () => {
          const t = read("docs/DESIGN.md");
          if (!t.trim()) return { status: MISSING, proof: "fichier absent" };
          const miss = missingSections(t, [["tokens", /tokens?|couleurs?/i], ["écrans", /[ée]crans?/i]]);
          return miss.length ? { status: PARTIAL, proof: `manque : ${miss.join(", ")}` } : { status: OK, proof: "tokens et écrans décrits" };
        },
      },
      {
        id: "plan", label: "Plan par jalons", where: "docs/PLAN.md", type: "bloquant",
        risk: "Développement par patchs successifs, sans fin claire",
        check: () => {
          const t = read("docs/PLAN.md");
          if (!t.trim()) return { status: MISSING, proof: "fichier absent" };
          const jalons = new Set(t.match(/\bJ\d+\b/g) || []).size;
          return /\bJ0\b/.test(t) ? { status: OK, proof: `${jalons} jalons, J0 défini` } : { status: PARTIAL, proof: "pas de jalon J0 (squelette + preview)" };
        },
      },
      {
        id: "claude_md", label: "Mémoire projet", where: "CLAUDE.md", type: "bloquant",
        risk: "Claude oublie les conventions d'une session à l'autre",
        check: () => {
          const t = read("CLAUDE.md");
          if (!t.trim()) return { status: MISSING, proof: "fichier absent" };
          const placeholders = t.match(/<pm>|<Nom du projet>|<ex\.[^>]*>|<Une phrase[^>]*>|<commande>/g);
          if (placeholders) return { status: PARTIAL, proof: `modèle non rempli : ${[...new Set(placeholders)].slice(0, 3).join(", ")}` };
          if (!/definition of done/i.test(t)) return { status: PARTIAL, proof: "Definition of Done absente" };
          return { status: OK, proof: `${t.split("\n").length} lignes, DoD présente` };
        },
      },
      {
        id: "settings", label: "Permissions + hooks", where: ".claude/settings.json", type: "recommandé",
        risk: "Claude peut lire les secrets ; code non formaté",
        check: () => {
          let s;
          try {
            s = JSON.parse(read(".claude/settings.json"));
          } catch {
            return { status: MISSING, proof: existsSync(p(".claude/settings.json")) ? "JSON invalide" : "fichier absent" };
          }
          const problems = [];
          if (!s.permissions?.deny?.some((d) => d.startsWith("Read(./.env"))) problems.push("lecture de .env non interdite");
          if (s.permissions?.deny?.includes("Read(./.env.*)")) problems.push(".env.example bloqué par Read(./.env.*)");
          const hook = p(".claude/hooks/format.mjs");
          if (!existsSync(hook)) problems.push("hook de formatage absent");
          else {
            const r = spawnSync(process.execPath, [hook], { cwd: root, input: '{"tool_input":{"file_path":"x.png"}}', encoding: "utf8", timeout: 10000, env: { ...process.env, CLAUDE_PROJECT_DIR: root } });
            if (r.status !== 0) problems.push("le hook de formatage plante");
          }
          return problems.length ? { status: PARTIAL, proof: problems.join(" ; ") } : { status: OK, proof: "secrets protégés, hook testé" };
        },
      },
      {
        id: "secrets", label: "Secrets protégés", where: `${envExample}, .gitignore`, type: "bloquant",
        risk: "Clé API publiée sur GitHub, compte piraté ou facture énorme",
        check: () => {
          if (!existsSync(p(envExample))) return { status: MISSING, proof: `${envExample} absent` };
          if (isGit) {
            const tracked = run("git", ["ls-files", "--", ".env", ".env.local", ".env.production", ".env.development"]).out;
            if (tracked) return { status: MISSING, proof: `SECRET SUIVI PAR GIT : ${tracked.replace(/\n/g, ", ")}` };
            if (!run("git", ["check-ignore", "-q", ".env.local"]).ok) return { status: MISSING, proof: ".env.local n'est pas ignoré par Git" };
            if (run("git", ["check-ignore", "-q", envExample]).ok) return { status: PARTIAL, proof: `${envExample} est ignoré (il doit être commité)` };
          } else if (!/^\.env/m.test(read(".gitignore"))) return { status: MISSING, proof: ".gitignore n'ignore pas .env" };
          if (envExample !== ".env.example") return { status: OK, proof: `.env ignoré, ${envExample} présent` };
          const drift = envDrift();
          if (drift.length) return { status: PARTIAL, proof: `variables utilisées mais absentes de .env.example : ${drift.slice(0, 5).join(", ")}` };
          return { status: OK, proof: ".env ignoré, .env.example à jour" };
        },
      },
      {
        id: "repo", label: "Repo Git + GitHub", where: ".git, origin", type: "recommandé",
        risk: "Pas d'historique ni de sauvegarde distante",
        check: () => {
          if (!isGit) return { status: MISSING, proof: "pas de dépôt Git" };
          const notes = [];
          if (process.platform === "win32" && !existsSync(p(".gitattributes"))) notes.push(".gitattributes absent (fins de ligne)");
          if (!remote.ok) return { status: PARTIAL, proof: ["dépôt local seulement", ...notes].join(" ; ") };
          return notes.length ? { status: PARTIAL, proof: notes.join(" ; ") } : { status: OK, proof: `origin : ${remote.out}` };
        },
      },
      {
        id: "ci", label: "CI (lint, types, tests, build)", where: ".github/workflows/", type: "bloquant",
        risk: "Les régressions passent inaperçues",
        check: () => {
          const dir = p(".github/workflows");
          const files = existsSync(dir) ? readdirSync(dir).filter((f) => /\.ya?ml$/.test(f)) : [];
          if (!files.length) return { status: MISSING, proof: "aucun workflow" };
          if (OFFLINE) return fromCache("ci", { status: PARTIAL, proof: `${files.join(", ")} présent, exécution non vérifiée (hors ligne)` });
          if (!ghRepo.ok) return { status: PARTIAL, proof: `${files.join(", ")} présent, pas encore sur GitHub` };
          const r = run("gh", ["run", "list", "--limit", "1", "--json", "status,conclusion,workflowName,headBranch"]);
          const last = r.ok ? JSON.parse(r.out || "[]")[0] : null;
          if (!last) return { status: PARTIAL, proof: "workflow présent, aucune exécution" };
          if (last.status !== "completed") return { status: PARTIAL, proof: `exécution en cours (${last.headBranch})` };
          return last.conclusion === "success"
            ? { status: OK, proof: `dernière exécution verte (${last.headBranch})` }
            : { status: MISSING, proof: `dernière exécution : ${last.conclusion} (${last.headBranch})` };
        },
      },
      {
        id: "dependabot", label: "Mises à jour des dépendances", where: ".github/dependabot.yml", type: "recommandé",
        risk: "Dépendances vulnérables jamais mises à jour",
        check: () => (existsSync(p(".github/dependabot.yml")) ? { status: OK, proof: "Dependabot configuré" } : { status: MISSING, proof: "fichier absent" }),
      },
      {
        id: "mcp", label: "MCP + skills", where: ".mcp.json", type: "recommandé",
        risk: "Code écrit pour d'anciennes API, interface jamais vérifiée dans un navigateur",
        check: () => {
          try {
            const servers = Object.keys(JSON.parse(read(".mcp.json")).mcpServers || {});
            if (servers.length) return { status: OK, proof: servers.join(", ") };
          } catch {}
          if (config.mcp?.length) return { status: PARTIAL, proof: `déclarés dans kickoff.json mais pas dans .mcp.json : ${config.mcp.join(", ")}` };
          return { status: MISSING, proof: "aucun MCP de projet" };
        },
      },
      {
        id: "issues", label: "Issues + milestones GitHub", where: "GitHub", type: "recommandé",
        risk: "Plan qui reste sur le papier, avancement invisible",
        check: () => {
          if (OFFLINE) return fromCache("issues", { status: PARTIAL, proof: "non vérifié (hors ligne)" });
          if (!ghRepo.ok) return { status: MISSING, proof: "pas de repo GitHub" };
          const issues = run("gh", ["issue", "list", "--state", "all", "--limit", "500", "--json", "number"]);
          const milestones = run("gh", ["api", `repos/${ghRepo.out}/milestones?state=all`, "--jq", "length"]);
          const n = issues.ok ? JSON.parse(issues.out || "[]").length : 0;
          const m = milestones.ok ? Number(milestones.out) || 0 : 0;
          if (!n) return { status: MISSING, proof: "aucune issue" };
          return m ? { status: OK, proof: `${n} issues, ${m} milestones` } : { status: PARTIAL, proof: `${n} issues, aucun milestone` };
        },
      },
      {
        id: "preview", label: "Preview déployée", where: "docs/kickoff.json → previewUrl", type: "après J0",
        risk: "Problèmes de déploiement découverts à la fin",
        check: async () => {
          if (!config.previewUrl) return { status: MISSING, proof: "pas encore d'URL (attendue à J0)" };
          if (OFFLINE) return fromCache("preview", { status: PARTIAL, proof: `${config.previewUrl} (non vérifiée hors ligne)` });
          try {
            const res = await fetch(config.previewUrl, { redirect: "follow", signal: AbortSignal.timeout(10000) });
            return res.status < 400 ? { status: OK, proof: `${config.previewUrl} répond ${res.status}` } : { status: MISSING, proof: `${config.previewUrl} répond ${res.status}` };
          } catch (e) {
            return { status: MISSING, proof: `${config.previewUrl} injoignable (${e.name})` };
          }
        },
      },
    ];
    
    // Lignes sans objet : en mode express, et celles que l'utilisateur a écartées dans kickoff.json.
    const EXPRESS_NA = { revue: "mode express", estimation: "mode express", design: "mode express (tokens dans CLAUDE.md)", ci: "mode express", dependabot: "mode express", issues: "mode express" };
    const naReasons = { ...(mode === "express" ? EXPRESS_NA : {}), ...(config.na || {}) };
    
    const results = [];
    for (const row of rows) {
      // Une ligne bloquante ne peut être écartée que par le mode express, jamais par kickoff.json.
      const reason = naReasons[row.id];
      if (reason && (row.type !== "bloquant" || (mode === "express" && EXPRESS_NA[row.id]))) {
        results.push({ id: row.id, label: row.label, where: row.where, type: row.type, risk: row.risk, status: NA, proof: reason });
        continue;
      }
      let r;
      try {
        r = await row.check();
      } catch (e) {
        r = { status: PARTIAL, proof: `vérification impossible : ${e.message}` };
      }
      results.push({ id: row.id, label: row.label, where: row.where, type: row.type, risk: row.risk, ...r });
    }
    
    const counted = results.filter((r) => r.status !== NA && r.type !== "info");
    const okCount = counted.filter((r) => r.status === OK).length;
    const blockers = counted.filter((r) => r.type === "bloquant" && r.status !== OK);
    const reserves = counted.filter((r) => r.type === "recommandé" && r.status !== OK);
    const verdict = blockers.length ? `${MISSING} Pas prêt` : reserves.length ? `${PARTIAL} Prêt avec réserves` : `${OK} Prêt`;
    const regressions = results.filter((r) => previous.rows?.find((x) => x.id === r.id)?.status === OK && r.status !== OK && r.status !== NA);
    const ignoredNa = Object.keys(config.na || {}).filter((id) => rows.find((r) => r.id === id)?.type === "bloquant" && !(mode === "express" && EXPRESS_NA[id]));
    const now = new Date();
    const report = { project: config.project || "", mode, level: config.level || "", milestone: config.currentMilestone || "", checkedAt: now.toISOString(), offline: OFFLINE, score: `${okCount}/${counted.length}`, verdict, blockers: blockers.map((r) => r.id), regressions: regressions.map((r) => r.id), rows: results };
    
    if (args.has("--json")) {
      console.log(JSON.stringify(report, null, 2));
    } else if (args.has("--summary")) {
      const todo = [...blockers, ...reserves];
      console.log(`[Kickoff] ${report.project || "Projet"} · jalon en cours : ${report.milestone || "non défini"} · ${report.score} · ${verdict}`);
      for (const r of regressions) console.log(`  RÉGRESSION ${r.label} : ${r.proof}`);
      for (const r of todo.slice(0, 6)) console.log(`  ${r.status} ${r.label} (${r.type}) : ${r.proof}`);
      if (todo.length) console.log("  → /kickoff-status pour le tableau complet");
    } else {
      const esc = (s) => String(s).replace(/\|/g, "\\|");
      const lines = [
        `# Tableau de préparation${report.project ? ` — ${report.project}` : ""}`,
        "",
        `Mode : **${mode}**${report.level ? ` · Niveau : ${report.level}` : ""}${report.milestone ? ` · Jalon en cours : **${report.milestone}**` : ""} · Vérifié le ${now.toLocaleString("fr-FR")}${OFFLINE ? " (hors ligne)" : ""}`,
        "",
        `## Score : ${report.score} — ${verdict}`,
        "",
        "| # | Élément | Où | Statut | Preuve | Type | Risque si sauté |",
        "|---|---|---|---|---|---|---|",
        ...results.map((r, i) => `| ${i + 1} | ${esc(r.label)} | ${esc(r.where)} | ${r.status} | ${esc(r.proof)} | ${r.type} | ${esc(r.risk)} |`),
        "",
        `Légende : ${OK} fait · ${PARTIAL} partiel · ${MISSING} manquant · ${NA} sans objet (raison indiquée). Ce tableau est généré par \`scripts/kickoff-check.mjs\` : ne le modifiez pas à la main.`,
      ];
      if (config._invalid) lines.push("", `${PARTIAL} **docs/kickoff.json est invalide** : valeurs par défaut utilisées.`);
      if (ignoredNa.length) lines.push("", `${PARTIAL} Lignes bloquantes ne pouvant pas être écartées (sauf mode express) : ${ignoredNa.join(", ")}.`);
      if (regressions.length) lines.push("", "## Régressions depuis la dernière vérification", ...regressions.map((r) => `- ${r.label} : ${r.proof}`));
      const next = [...blockers, ...reserves];
      if (next.length) lines.push("", "## À faire ensuite", ...next.map((r) => `- [${r.type}] **${r.label}** : ${r.proof}`));
      const md = lines.join("\n") + "\n";
      console.log(md);
      mkdirSync(p("docs"), { recursive: true });
      writeFileSync(p("docs", "KICKOFF.md"), md);
      mkdirSync(p(".claude"), { recursive: true });
      writeFileSync(statePath, JSON.stringify({ checkedAt: report.checkedAt, rows: results.map(({ id, status, proof }) => ({ id, status, proof })) }, null, 2));
    }
    
    if (args.has("--strict") && blockers.length) process.exit(1);
    
  • references/avocat-du-diable.md2.9 KB6ab82479531dupstream ↗
    # Revue « avocat du diable » du PRD
    
    Objectif : trouver les trous du cahier des charges **avant** le code. Une relecture par un regard neuf trouve ce que l'auteur ne voit plus.
    
    ## Comment la lancer
    Lance un sous-agent (outil Agent, type `general-purpose`) **sans** l'historique de la conversation, pour qu'il ne partage pas tes angles morts. Donne-lui le prompt ci-dessous, en remplaçant les chemins. Il lit les fichiers et écrit `docs/REVIEW.md`. Il ne modifie rien d'autre.
    
    ```
    Tu es un relecteur exigeant et sceptique. Ton travail est de trouver ce qui manque ou ce qui
    cassera, pas de féliciter. Lis docs/PRD.md, puis docs/ARCHITECTURE.md et docs/DESIGN.md s'ils
    existent. Contexte : pack <pack>, pays <pays>, mode <mode>, niveau de l'utilisateur <niveau>.
    
    Vérifie au minimum :
    1. Ambiguïtés : chaque critère d'acceptation est-il testable par un automate ? Signale les mots
       flous (« rapide », « simple », « sécurisé », « etc. »).
    2. Cas limites : concurrence (deux actions simultanées), double soumission, données vides ou
       énormes, suppression d'un élément lié, fuseaux horaires, devises, réseau lent ou coupé,
       droits (qui peut voir ou modifier quoi, et que se passe-t-il pour un autre utilisateur ?).
    3. Contradictions entre stories, entre le PRD et l'architecture, ou avec le hors périmètre.
    4. Taille du MVP : est-il réalisable dans la contrainte de temps annoncée ? Qu'enlever en premier ?
    5. Sécurité et données personnelles : données sensibles, mineurs, paiements, secrets côté client,
       obligations légales du pays.
    6. Parcours oubliés : inscription, mot de passe oublié, suppression de compte, état vide du
       premier usage, erreurs, notifications, administration.
    7. Intégrité : chaque garantie (« jamais deux X ») est-elle imposée en base de données ?
    8. Mesure du succès : les indicateurs sont-ils mesurables avec ce qui est prévu ?
    
    Écris docs/REVIEW.md au format exact :
    # Revue du PRD — <date>
    ## Synthèse
    <3 lignes : niveau de risque global et les 3 points les plus importants>
    ## Points
    - [ ] [Bloquant] <problème> → <proposition concrète de correction> (réf. : US2 / section X)
    - [ ] [Important] ...
    - [ ] [Suggestion] ...
    
    « Bloquant » = le projet sera faux, illégal ou non sécurisé si on ne le corrige pas.
    Maximum 15 points, classés du plus grave au moins grave. Pas de point vague : chaque point
    cite l'endroit concerné et propose une correction.
    ```
    
    ## Après la revue
    1. Présente la synthèse et les points **Bloquant** à l'utilisateur, adaptés à son niveau.
    2. Pour chaque point accepté : corrige `docs/PRD.md` (ou l'architecture), puis coche le point dans `REVIEW.md` : `- [x] [Bloquant] … → corrigé : <ce qui a changé>`.
    3. Un point refusé par l'utilisateur est coché avec `→ refusé : <raison>`. C'est un choix assumé, et il ne bloque plus le tableau.
    4. Relance le script du tableau : la ligne « Revue » passe ✅ quand aucun `- [ ] [Bloquant]` ne reste.
    
  • references/estimation.md2.3 KB3b8dac8d0b87upstream ↗
    # Estimation des coûts et du temps → docs/ESTIMATION.md
    
    **Les prix changent souvent.** N'écris jamais un prix de mémoire. Pour chaque service retenu, consulte sa page de tarifs officielle (recherche web) et note la date et la source. Si tu ne peux pas vérifier, écris « à vérifier » plutôt qu'un chiffre.
    
    ## 1. Coût mensuel par palier
    Construis ce tableau avec les services de l'ADR de stack :
    
    | Service | Rôle | 0–100 utilisateurs | 100–1 000 | 1 000–10 000 | Ce qui fait changer de palier | Source (date) |
    |---|---|---|---|---|---|---|
    | Hébergement front | | | | | bande passante, fonctions | |
    | Base de données | | | | | taille, connexions, mise en pause des projets gratuits | |
    | Emails | | | | | nombre d'emails par mois | |
    | SMS / WhatsApp | | | | | coût par message × volume | |
    | Stockage de fichiers | | | | | Go stockés et téléchargés | |
    | Monitoring / analytics | | | | | événements par mois | |
    | Paiement | | | | | % par transaction (à intégrer dans le prix de vente) | |
    | Domaine | | | | | annuel | |
    | Comptes stores (mobile) | | | | | annuel ou unique | |
    | **Total** | | | | | | |
    
    Signale clairement :
    - les **pièges des offres gratuites** (projet mis en pause après inactivité, usage commercial interdit, limites strictes) ;
    - les coûts **variables** qui peuvent exploser (SMS, IA, bande passante vidéo) et un plafond ou une alerte de facturation à configurer ;
    - si le total dépasse le budget du profil : propose une alternative moins chère.
    
    ## 2. Temps
    Pour chaque jalon de `docs/PLAN.md`, donne une taille et le nombre estimé de sessions Claude Code :
    
    | Taille | Description | Sessions estimées |
    |---|---|---|
    | S | une story simple, 1–2 écrans, pas de nouvelle intégration | 1–2 |
    | M | plusieurs écrans ou une intégration (email, stockage) | 2–4 |
    | L | paiement, temps réel, hors ligne, import de données, rôles complexes | 4–8 |
    
    Ajoute une **marge de 30 %** pour les corrections et la mise en production. Pour un débutant, ajoute 30 % de plus pour l'apprentissage. Compare le total à l'échéance du PRD. Si ça ne tient pas, propose de déplacer des stories de « Must » vers « Should ».
    
    ## 3. Conclusion
    Termine par 3 lignes : coût mensuel au lancement, coût à 1 000 utilisateurs, et durée estimée jusqu'à la v1. Indique aussi si ça tient dans le budget et l'échéance.
    
  • references/issues-github.md2.1 KB26298e69cf15upstream ↗
    # Créer les milestones et les issues GitHub depuis le plan
    
    **Demande l'accord de l'utilisateur avant de créer quoi que ce soit** : c'est visible sur son compte GitHub. Montre d'abord la liste exacte (titres des milestones et des issues), puis crée-la après son « oui ».
    
    ## Pré-requis
    - Le repo existe sur GitHub (`gh repo view` répond).
    - `docs/PLAN.md` et `docs/PRD.md` sont validés.
    
    ## 1. Labels
    ```bash
    gh label create feature --color 1D76DB --description "User story du PRD" --force
    gh label create bug --color D73A4A --force
    gh label create chore --color C5DEF5 --description "Technique, outillage" --force
    gh label create bloquant --color B60205 --description "Bloque un jalon" --force
    ```
    
    ## 2. Un milestone par jalon
    ```bash
    gh api repos/{owner}/{repo}/milestones -f title="J0 — Squelette, CI, preview" -f description="Terminé quand : <critère du PLAN>"
    ```
    `{owner}/{repo}` est remplacé automatiquement par `gh` quand la commande est lancée dans le repo.
    
    ## 3. Une issue par story ou tâche
    Le corps reprend la story et ses critères d'acceptation **en cases à cocher**, avec un lien vers le PRD :
    ```bash
    gh issue create \
      --title "US4 — Réserver un créneau sans compte" \
      --label feature \
      --milestone "J4 — Réservation" \
      --body-file - <<'EOF'
    **Story** : En tant que client, je veux réserver un créneau sans compte.
    
    **Critères d'acceptation**
    - [ ] Choix prestation → créneau → nom + téléphone
    - [ ] Impossible de réserver un créneau passé ou déjà pris (vérifié côté serveur)
    - [ ] Deux réservations simultanées : une seule acceptée (test E2E)
    
    Réf. : docs/PRD.md#us4
    EOF
    ```
    Pour J0, crée des issues `chore` : squelette, scripts lint/typecheck/test/build, CI verte, preview déployée.
    
    ## 4. Vérifier
    Relance `node scripts/kickoff-check.mjs` : la ligne « Issues + milestones » doit passer ✅.
    
    ## Pendant le développement
    - Une branche par issue : `feat/us4-reservation`.
    - La PR contient `Closes #<numéro>` pour fermer l'issue automatiquement.
    - Quand tous les éléments d'un jalon sont fermés, mets à jour `currentMilestone` dans `docs/kickoff.json`.
    
  • references/packs/ecole.md2.5 KB683a46e7eb9bupstream ↗
    # Pack — Application scolaire (présence, notes, communication avec les parents)
    
    **Mode conseillé** : **complet**, car il y a des données de mineurs.
    
    ## Questions spécifiques
    - Quels acteurs : administration, enseignants, parents, élèves ? Les élèves se connectent-ils eux-mêmes ? À partir de quel âge ?
    - Quelles fonctions : présence, retards, notes, bulletins, emploi du temps, paiement des frais de scolarité, messages aux parents ?
    - Une ou plusieurs écoles (multi-établissements) ? Quels cycles et quelles classes ?
    - Comment l'enseignant fait-il l'appel : téléphone en classe, tablette, ordinateur ? Y a-t-il du réseau en classe ? Si non, il faut un **mode hors ligne** avec synchronisation.
    - Comment prévenir les parents : SMS, WhatsApp, email, application ? Lequel est réellement lu dans le pays ?
    - Quelles données importer (listes d'élèves depuis un tableur) ?
    - Qui valide une absence justifiée ?
    
    ## Stack par défaut
    Next.js ou une PWA (installable, hors ligne) + Supabase (RLS par établissement et par classe) · SMS ou WhatsApp via un fournisseur disponible dans le pays (`pays.md`) · export PDF des bulletins · si l'établissement utilise déjà Odoo : `odoo.md`.
    
    ## Pièges connus
    - **Cloisonnement** : un parent ne voit que ses enfants, un enseignant seulement ses classes. Écrire un test par rôle.
    - Pas d'informations sensibles dans les SMS (santé, sanctions), car ils sont lisibles par n'importe qui sur le téléphone.
    - Année scolaire : prévoir le passage d'une année à l'autre (archivage, changement de classe) dès le modèle de données.
    - Un import de fichier mal formaté crée des doublons d'élèves : valider avant l'import et prévoir une fusion.
    - L'appel doit prendre moins de 30 secondes par classe, sinon les enseignants ne l'utiliseront pas.
    
    ## Légal
    Données de mineurs : consentement des parents, minimisation, durée de conservation définie, accès journalisés. Formalités auprès de l'autorité de protection des données du pays (`pays.md`). Ces points sont **bloquants** dans la revue « avocat du diable ».
    
    ## Tableau
    `revue` et `estimation` sont bloquants (mode complet). Ajouter dans `docs/PRD.md` une section « Rôles et droits » sous forme de matrice rôle × action.
    
    ## Jalons types
    J0 squelette + preview · J1 auth + rôles + RLS testée · J2 import des élèves et des classes · J3 appel (hors ligne si nécessaire) · J4 notifications aux parents · J5 tableaux de bord et exports · J6 légal, sauvegardes, formation des utilisateurs.
    
  • references/packs/ecommerce.md2.1 KBfbb21ff90c5fupstream ↗
    # Pack — E-commerce
    
    **Mode conseillé** : complet (paiement, données clients, obligations légales).
    
    ## Questions spécifiques
    - Combien de produits ? Ont-ils des variantes (taille, couleur) ? Produits physiques, numériques ou services ?
    - Livraison : zones, transporteurs, frais, retrait en boutique ?
    - Faut-il gérer les stocks, et sont-ils synchronisés avec une boutique physique ou une caisse ?
    - Quels moyens de paiement les clients utilisent-ils réellement (carte, mobile money, paiement à la livraison) ? Voir `pays.md`.
    - Comment se font les retours et remboursements ?
    - Qui gère le catalogue au quotidien, et sait-il coder ?
    - Quel volume de commandes est attendu par mois ?
    
    ## Stack par défaut
    **Ne pas coder un panier et un tunnel de paiement à la main.**
    - Petit catalogue, gestion simple : **Shopify** (thème ou headless avec Hydrogen/Next.js).
    - Déjà sur WordPress, ou budget serré : **WooCommerce**.
    - Besoin très spécifique (marketplace, configurateur) : Medusa ou Saleor (open source headless), en mode complet obligatoire.
    - Afrique de l'Ouest : prévoir une passerelle mobile money compatible avec la plateforme choisie (`pays.md`).
    
    ## Pièges connus
    - Prix et stock calculés côté navigateur : ils doivent toujours être recalculés côté serveur.
    - Une commande créée avant la confirmation du paiement : utiliser les webhooks et des statuts clairs (en attente, payée, expédiée, remboursée).
    - Taxes (TVA) et factures légales oubliées.
    - Emails de commande en spam (SPF/DKIM).
    - Images produit non optimisées : pages lentes et conversion en baisse.
    
    ## Légal
    CGV obligatoires (droit de rétractation dans l'UE), mentions légales, confidentialité, cookies, factures conformes, médiation de la consommation (UE).
    
    ## Tableau
    `revue` et `estimation` sont bloquants (mode complet). `design` n'est jamais ➖ : la confiance visuelle fait vendre.
    
    ## Jalons types
    J0 boutique de base + preview · J1 catalogue et fiches produit · J2 panier et checkout (paiement en mode test) · J3 livraison et emails · J4 back-office et rôles · J5 légal, SEO produit, performance, passage en paiement réel.
    
  • references/packs/mobile.md2.3 KBdd4beef03678upstream ↗
    # Pack — Application mobile (avec ou sans version web)
    
    **Mode conseillé** : standard ; complet si paiement in-app ou données sensibles.
    
    ## Questions spécifiques
    - iOS, Android ou les deux ? Faut-il aussi une version web ?
    - L'app doit-elle fonctionner **hors connexion** ? Que se passe-t-il en cas de conflit lors de la synchronisation ?
    - Notifications push ? Pour quels événements ?
    - Accès à l'appareil : caméra, GPS, contacts, biométrie, NFC ?
    - Publication sur les stores : as-tu un compte développeur Apple (payant chaque année) et Google Play (frais unique) ?
    - Achats in-app ? Les stores prennent une commission et imposent leurs règles pour les biens numériques.
    - Quelle est la gamme de téléphones visée (anciens Android d'entrée de gamme ?)
    
    ## Stack par défaut
    **Expo** (React Native) + TypeScript + Expo Router · backend Supabase ou API partagée avec le web · EAS Build et EAS Submit pour les stores · notifications avec Expo Notifications · si hors ligne : stockage local (SQLite) + stratégie de synchronisation écrite dans un ADR.
    Alternative : une **PWA** si l'app n'a pas besoin des stores ni des API natives avancées. Moins cher, et une seule base de code avec le web.
    
    ## Pièges connus
    - La validation des stores prend du temps (souvent quelques jours) et peut refuser l'app : la prévoir dans le plan.
    - Pages de confidentialité et suppression de compte **dans l'app** exigées par les stores.
    - Les clés d'API dans l'app sont lisibles par n'importe qui : aucun secret côté client. Passer par le backend.
    - Tester sur un vrai téléphone d'entrée de gamme, pas seulement sur l'émulateur.
    - Mises à jour : prévoir les mises à jour OTA (EAS Update) et forcer une mise à jour si l'API change.
    
    ## Légal
    Politique de confidentialité (URL publique exigée par les stores), déclaration des données collectées (App Privacy, Data safety), CGU.
    
    ## Tableau
    `preview` = un build de test interne (TestFlight, test interne Google Play) ou un lien Expo, à mettre dans `previewUrl` s'il est accessible en ligne. Sinon ➖ avec la raison.
    
    ## Jalons types
    J0 squelette Expo + CI + build de test · J1 navigation, auth, design system · J2…Jn stories Must · J(n+1) notifications et hors ligne · dernier jalon fiches stores, captures, confidentialité, soumission.
    
  • references/packs/odoo.md3.2 KB9933336129a3upstream ↗
    # Pack — Module Odoo
    
    **Mode conseillé** : standard ; complet si le module touche à la comptabilité, la paie ou les paiements.
    
    ## Questions spécifiques
    - Quelle **version** d'Odoo (17, 18…) et quelle édition : Community ou Enterprise ? Où est-il hébergé (Odoo.sh, serveur, Docker local, WSL) ?
    - Quels modules standard le module **étend-il** (sale, stock, account, hr…) ? Un module OCA existant fait-il déjà le travail ?
    - Quels nouveaux modèles, champs, vues (formulaire, liste, kanban), menus et rapports PDF ?
    - Quels groupes d'utilisateurs et quels droits (lecture, écriture, création, suppression), et quelles règles d'enregistrement (record rules) ?
    - Y a-t-il des données à migrer depuis une version précédente du module ?
    - Faut-il des tâches planifiées (cron), des emails automatiques, une API externe ?
    
    ## Stack et structure
    Python + XML (+ OWL pour le JavaScript côté client), sans npm.
    ```
    mon_module/
      __init__.py
      __manifest__.py          version, dépendances, données chargées, licence
      models/                  modèles Python
      views/                   vues XML et menus
      security/
        ir.model.access.csv    droits par groupe (obligatoire pour chaque modèle)
        security.xml           groupes et règles d'enregistrement
      data/                    données par défaut, crons
      report/                  rapports QWeb
      tests/                   tests (TransactionCase)
      static/src/              OWL, SCSS
    ```
    Environnement local : Docker Compose (odoo + postgres) ou l'installation existante de l'utilisateur. Tests : `odoo-bin -d test_db -i mon_module --test-enable --stop-after-init`.
    Qualité : `pre-commit` avec ruff et pylint-odoo (configurations OCA).
    
    ## Adapter les phases génériques
    - Phase 0 : vérifier Python, Docker, l'accès à Odoo et la version. Gestionnaire de paquets : ➖ (pip/requirements seulement si dépendance Python externe).
    - Phase 4 : la CI lance pre-commit et les tests Odoo dans un conteneur. S'inspirer des workflows publics de l'OCA.
    - Phase 5 : le hook de formatage passe les `.py` à ruff s'il est installé.
    - `secrets` : mots de passe de base et clés d'API dans un fichier de configuration non commité (`odoo.conf` local) et un exemple `odoo.conf.example`.
    
    ## Pièges connus
    - Oublier `ir.model.access.csv` : le modèle est invisible ou inaccessible, avec un avertissement dans les logs.
    - Modifier une vue standard en la remplaçant au lieu d'utiliser l'héritage (`inherit_id` + xpath) : casse lors des mises à jour.
    - Champs calculés `store=True` sans `@api.depends` correct : valeurs fausses en silence.
    - Mettre la logique métier dans les vues ou le JavaScript au lieu des modèles.
    - Oublier d'incrémenter la version dans `__manifest__.py` et d'écrire les scripts de migration.
    
    ## Tableau
    `design` est souvent ➖ (raison : vues Odoo standard). `preview` correspond à une base de test (staging Odoo.sh ou serveur de test) : mettre son URL dans `previewUrl`.
    
    ## Jalons types
    J0 module vide installable + CI + base de test · J1 modèles + sécurité + tests · J2 vues et menus · J3 logique métier et automatisations · J4 rapports et emails · J5 migration des données, documentation utilisateur, déploiement.
    
  • references/packs/outil-interne.md1.9 KBdccc4dd9b723upstream ↗
    # Pack — Outil interne, dashboard, back-office
    
    **Mode conseillé** : standard ; express pour un outil personnel jetable.
    
    ## Questions spécifiques
    - Combien d'utilisateurs, et sont-ils tous dans la même entreprise ? Faut-il une connexion avec le compte de l'entreprise (Google Workspace, Microsoft 365) ?
    - D'où viennent les données : base existante, tableur, API d'un autre logiciel ? Lecture seule ou écriture ?
    - Quels rôles et quels droits (lecture, saisie, validation, admin) ?
    - Faut-il des exports (CSV, Excel, PDF) et des imports ?
    - Faut-il garder un historique des modifications (qui a changé quoi, quand) ?
    - Quel est l'appareil principal : ordinateur de bureau ou tablette sur le terrain ?
    
    ## Stack par défaut
    - Vite + React + TanStack Query + TanStack Table + shadcn/ui, avec une API légère (Hono) ou directement Supabase.
    - Si l'outil consiste surtout en formulaires et tableaux sur une base existante, évaluer un outil low-code (Retool, Appsmith, NocoDB) dans l'ADR avant de coder.
    - Si l'entreprise utilise déjà Odoo, voir `odoo.md` : un module est souvent préférable à une app séparée.
    
    ## Pièges connus
    - Accès direct à la base de production : utiliser un compte en lecture seule ou une réplique.
    - Tables de milliers de lignes chargées d'un coup : pagination et filtres côté serveur.
    - Pas de journal d'audit, alors que la direction le demandera plus tard.
    - Imports de fichiers sans validation : données corrompues.
    
    ## Légal
    Données des salariés ou des clients : informer les personnes, limiter les accès, journaliser. En UE, registre des traitements.
    
    ## Tableau
    `design` peut être ➖ (raison : composants shadcn/ui par défaut). `issues` est souvent ➖ pour un développeur solo.
    
    ## Jalons types
    J0 squelette + auth entreprise + preview · J1 connexion aux données (lecture) · J2 tableaux, filtres et exports · J3 saisie avec validation et audit · J4 rôles, finitions, déploiement interne.
    
  • references/packs/saas.md2.2 KB5a1e0ea3e4efupstream ↗
    # Pack — SaaS, application web avec comptes
    
    **Mode conseillé** : standard, ou **complet** s'il y a un paiement, des données sensibles ou un client final.
    
    ## Questions spécifiques
    - Est-ce une seule organisation par compte ou du **multi-tenant** (équipes, invitations, rôles par équipe) ?
    - Quel est le modèle économique : gratuit, freemium, abonnement, paiement à l'usage ? Y a-t-il un essai gratuit ?
    - Quelles limites par plan (nombre d'utilisateurs, de projets, de stockage) ?
    - Quels emails transactionnels (bienvenue, réinitialisation, facture, rappel) ?
    - Faut-il un back-office admin ? Qui y a accès ?
    - Que se passe-t-il quand un abonnement expire ou qu'un paiement échoue ?
    - Faut-il exporter les données de l'utilisateur ou supprimer son compte (RGPD) ?
    
    ## Stack par défaut
    Next.js (App Router) + TypeScript + Tailwind + shadcn/ui · Supabase (Postgres, Auth, Storage) ou Postgres + Drizzle + Better Auth · paiement Stripe ou un équivalent local (`pays.md`) · Resend · Sentry · PostHog ou Plausible · Vercel.
    
    ## Pièges connus
    - **Autorisation** : chaque requête doit vérifier que la ressource appartient à l'utilisateur ou à son équipe. Avec Supabase, RLS activée sur **toutes** les tables, plus un test qui vérifie qu'un autre compte ne voit rien.
    - **Webhooks de paiement** : vérifier la signature, rendre le traitement idempotent (un même événement peut arriver deux fois) et ne jamais faire confiance au retour du navigateur pour activer un abonnement.
    - **Concurrence** : garantir l'unicité et la non-collision en base (contraintes uniques, transactions), pas seulement dans l'interface.
    - **Emails** : configurer SPF, DKIM et DMARC sur le domaine, sinon les emails partent en spam.
    - **Suppression de compte** : prévoir dès le départ ce qui est supprimé et ce qui est anonymisé.
    
    ## Légal
    CGU, politique de confidentialité, CGV si payant, mentions légales, bannière cookies si analytics avec traceurs, registre des traitements (UE).
    
    ## Tableau
    Rien n'est ➖ en standard. En complet, `revue` et `estimation` deviennent bloquants.
    
    ## Jalons types
    J0 squelette + CI + preview · J1 auth + layout + RLS testée · J2…Jn une story Must par jalon · J(n+1) paiement + webhooks · dernier jalon finitions, légal, monitoring, domaine.
    
  • references/packs/vitrine.md2.2 KB6e60e82ac225upstream ↗
    # Pack — Site vitrine, landing page, blog, portfolio
    
    **Mode conseillé** : express (landing simple) ou standard (site multi-pages avec CMS).
    
    ## Questions spécifiques
    - Combien de pages ? Lesquelles (accueil, services, à propos, contact, blog…) ?
    - Qui modifiera le contenu après la livraison, et cette personne sait-elle coder ? Si non, il faut un CMS.
    - Quelle action principale attend-on du visiteur (appeler, remplir un formulaire, réserver, acheter) ?
    - Quels mots-clés et quelle zone géographique pour le SEO local ? Existe-t-il une fiche Google Business ?
    - Les textes et les photos sont-ils prêts ? Sinon, qui les fournit et quand ? C'est souvent le vrai goulot d'étranglement.
    - Le site a-t-il plusieurs langues ?
    
    ## Stack par défaut
    - **Astro** + Tailwind, contenu en Markdown/MDX. Hébergement statique (Cloudflare Pages, Netlify, Vercel) : 0 €.
    - CMS si un non-développeur édite : Decap CMS ou Keystatic (basés sur Git, gratuits), ou un CMS headless hébergé.
    - Formulaire de contact : un endpoint serverless + Resend, avec une protection anti-spam (Cloudflare Turnstile ou un champ piège).
    - Si l'utilisateur veut WordPress (habitude, client) : thème léger, peu de plugins, mises à jour automatiques.
    
    ## Pièges connus
    - Images trop lourdes : convertir en AVIF/WebP, indiquer les dimensions, lazy loading.
    - Formulaire sans protection anti-spam : spam dès la première semaine.
    - Balises SEO oubliées : title, meta description, Open Graph, `sitemap.xml`, `robots.txt`, données structurées (LocalBusiness).
    - Polices Google chargées depuis les serveurs de Google : problème RGPD en UE. Les auto-héberger.
    
    ## Légal
    Mentions légales, politique de confidentialité si un formulaire existe, bannière cookies seulement s'il y a des traceurs non essentiels. Plausible ou Umami permettent souvent de s'en passer : à vérifier selon le pays.
    
    ## Tableau
    - Souvent ➖ : `issues` (projet court), `estimation` (coût quasi nul).
    - Jamais ➖ : `secrets` (clé de l'outil d'email), `prd`.
    
    ## Jalons types
    J0 squelette + preview · J1 layout, navigation, design system · J2 pages de contenu · J3 formulaire + anti-spam · J4 SEO, performance (Lighthouse ≥ 95), accessibilité, pages légales, domaine.
    
  • references/pays.md3.0 KB9954339c7616upstream ↗
    # Adapter le projet au pays
    
    Les lois et les prestataires changent souvent. **Ce fichier donne des pistes, pas des certitudes.** Avant de figer un choix, vérifie sur le site officiel du prestataire ou de l'autorité, avec une recherche web, que l'information est encore à jour : disponibilité dans le pays, frais, conditions. Écris la source et la date dans l'ADR concerné.
    
    ## Ce qu'il faut déterminer pour chaque projet
    1. Le **droit des données personnelles** applicable et l'autorité de contrôle.
    2. Les **pages légales** obligatoires (mentions légales, confidentialité, CGU/CGV, cookies).
    3. Les **moyens de paiement** que les clients utilisent vraiment.
    4. Les **canaux de notification** efficaces (email, SMS, WhatsApp).
    5. Les **contraintes réseau** (connexion lente ou instable → app légère, mode hors ligne).
    6. La **devise**, le format des dates et des numéros de téléphone, et le fuseau horaire.
    
    ## Repères par zone
    
    | Zone | Données personnelles | Paiement (à vérifier) | Points d'attention |
    |---|---|---|---|
    | France, Belgique, UE | RGPD. Autorités : CNIL (FR), APD (BE). | Stripe, Mollie, PayPal | Consentement aux cookies avant tout traceur non essentiel. Mentions légales obligatoires (LCEN en France). CGV pour la vente en ligne. |
    | Suisse | nLPD | Stripe, TWINT | Proche du RGPD. Prévoir un hébergement en Suisse ou dans l'UE si le client l'exige. |
    | Canada / Québec | LPRPDE ; Loi 25 au Québec | Stripe, Square | Loi 25 : responsable de la protection des renseignements personnels, évaluation des facteurs relatifs à la vie privée. |
    | Maroc | Loi 09-08, CNDP | CMI, agrégateurs locaux | Déclaration ou autorisation auprès de la CNDP selon le traitement. |
    | Côte d'Ivoire | Loi n° 2013-450, ARTCI | Mobile money (Orange Money, MTN MoMo, Moov, Wave) via des agrégateurs comme CinetPay | Mobile money prioritaire sur la carte bancaire. Formalités auprès de l'ARTCI. Penser au réseau mobile lent. |
    | Sénégal | Loi n° 2008-12, CDP | Wave, Orange Money, agrégateurs comme PayDunya | Idem : mobile money et formalités auprès de la CDP. |
    | Autres pays d'Afrique de l'Ouest et centrale | Loi nationale + autorité locale (à rechercher) | Agrégateurs mobile money régionaux (CinetPay, FedaPay, PayDunya…), selon le pays | Vérifier la couverture pays par pays. |
    
    Stripe n'est pas disponible dans tous les pays : vérifie la liste officielle avant de le proposer.
    
    ## Règles qui valent partout
    - Ne collecte que les données nécessaires (minimisation). Indique pourquoi chaque donnée est demandée.
    - Permets la suppression des données sur demande.
    - Si l'app concerne des **mineurs** (école, jeux, réseaux) : consentement des parents et données réduites au strict minimum. Ce point devient une ligne « Exigences non fonctionnelles » du PRD et une question de l'avocat du diable.
    - Les données de santé, de paiement ou biométriques sont sensibles : le mode **complet** est obligatoire.
    - Stocke les dates en UTC et affiche-les dans le fuseau de l'utilisateur.
    
  • references/profil.md3.9 KB573f94b92d5bupstream ↗
    # Profil utilisateur et adaptation au niveau
    
    Le profil vit dans `~/.claude/kickoff-profile.md`. Il est partagé par tous les projets. Il sert à ne jamais reposer une question déjà répondue.
    
    ## Premier lancement : 6 questions (en 2 lots)
    Lot 1
    1. **Niveau** : débutant (je découvre, je veux qu'on m'explique), intermédiaire (je sais lire du code), expert (je veux aller vite et discuter technique).
    2. **Pays et langue** : pays où le produit sera exploité et langue de travail. Ça détermine le droit applicable et les moyens de paiement (voir `pays.md`).
    3. **Contexte** : seul, en équipe, ou pour un client (freelance/agence) ?
    
    Lot 2
    4. **Stacks préférées ou imposées** (ex. « Next.js + Supabase », « Laravel », « Odoo 17 »), ou « aucune, conseille-moi ».
    5. **Hébergeur habituel** (Vercel, Cloudflare, Netlify, VPS, o2switch, Odoo.sh…) et **budget mensuel** habituel.
    6. **Ce qui t'a fait perdre du temps** sur tes projets passés (optionnel). Ça part directement dans « Leçons ».
    
    ## Format du fichier
    ```markdown
    # Profil kickoff
    - Niveau : intermédiaire
    - Pays : France · Langue : français
    - Contexte : seul
    - Stacks préférées : Next.js + Supabase ; Odoo 17 pour les ERP
    - Hébergeur : Vercel · Budget habituel : 0–20 €/mois
    - Gestionnaire de paquets : bun
    - Outils installés (dernière vérif.) : git, node 24, bun, gh, docker
    
    ## Leçons (mises à jour par les rétrospectives)
    - Toujours poser la question des fuseaux horaires.
    - Supabase validé sur 2 projets ; éviter Firebase pour les données relationnelles.
    
    ## Historique
    | Date | Projet | Type | Mode | Stack | Issue |
    |---|---|---|---|---|---|
    ```
    
    ## Lancements suivants
    Affiche le profil en 3 lignes et demande seulement : « Toujours vrai ? ». Mets à jour le fichier si quelque chose change. Applique les **Leçons** : chaque leçon devient une question ou une vérification supplémentaire dans le projet.
    
    ## Adapter le comportement au niveau
    
    | | Débutant | Intermédiaire | Expert |
    |---|---|---|---|
    | Questions de la phase 1 | 8 essentielles. Le reste reçoit une valeur par défaut, notée en Hypothèses. | Les 12 | Les 12, plus les questions techniques du pack |
    | Choix de stack | Une seule recommandation, expliquée simplement | 2 options avec tes préférences en premier | Tableau comparatif et ADR détaillés |
    | Vocabulaire | Chaque terme technique est expliqué en une ligne au premier usage (voir glossaire) | Termes avancés seulement | Aucune explication |
    | Validation | Résumé « ce que ça veut dire pour toi » à chaque phase | Résumé des fichiers produits | Liste des fichiers, sans commentaire |
    | Commandes | Claude les lance après accord, l'utilisateur n'a rien à taper | Claude les lance | Claude les lance, l'utilisateur peut demander les commandes brutes |
    
    Les 8 questions essentielles pour un débutant sont les n°1, 2, 3, 4, 5, 7, 10 et 12 de la phase 1.
    
    ## Mini-glossaire (niveau débutant)
    - **PRD** : le cahier des charges, ce que l'app doit faire et pour qui.
    - **Critère d'acceptation** : une phrase testable qui dit quand une fonctionnalité est finie.
    - **Stack** : l'ensemble des technologies utilisées.
    - **ADR** : une fiche qui explique pourquoi on a choisi une technologie plutôt qu'une autre.
    - **Repo** : le dossier du projet avec tout son historique, stocké sur GitHub.
    - **CI** : un robot GitHub qui vérifie automatiquement le code à chaque modification.
    - **Variables d'environnement / secrets** : les mots de passe et clés d'API, gardés hors du code.
    - **RLS** : des règles dans la base de données qui empêchent un utilisateur de voir les données d'un autre.
    - **MCP** : des connecteurs qui donnent à Claude des outils en plus (documentation, navigateur, GitHub…).
    - **Preview** : une version en ligne du site, pour tester avant la vraie mise en production.
    - **Migration** : un fichier qui modifie la structure de la base de données de façon traçable.
    
  • references/reprise.md3.0 KBa6b4235072b9upstream ↗
    # Mode reprise : projet qui existe déjà
    
    À utiliser quand le dossier contient déjà du code : `package.json`, `__manifest__.py`, `composer.json`, `requirements.txt`, `pyproject.toml`, un dossier `src/` ou `app/`, ou un historique Git avec des commits.
    **Principe : auditer d'abord, demander ensuite.** On ne pose pas les 12 questions de départ, on reconstitue ce qui existe et on ne demande que ce que le code ne dit pas.
    
    ## 1. Audit en lecture seule (ne rien modifier)
    1. Lire `README`, `CLAUDE.md` et `docs/` s'ils existent.
    2. Identifier la stack : manifestes de dépendances, fichiers de configuration (next.config, astro.config, vite.config, docker-compose, `__manifest__.py`).
    3. Historique Git : `git log --oneline -30`, `git shortlog -sn`, branches, date du dernier commit.
    4. Scripts disponibles : lint, test, build. Les lancer et noter ce qui passe ou casse, sans rien réparer.
    5. Tests : combien, où, et passent-ils ?
    6. **Secrets** : chercher des clés dans le code et dans l'historique.
       ```bash
       git grep -nE "(sk_live|sk_test|AKIA[0-9A-Z]{16}|-----BEGIN .*PRIVATE KEY|api[_-]?key\s*[:=]\s*['\"][A-Za-z0-9]{16,})" $(git rev-list --all | head -200) -- . ':!*.lock' | head -20
       git log --all --diff-filter=A --name-only --format= | grep -E "(^|/)\.env($|\.)" | sort -u
       ```
       Si un secret a été commité : **le signaler en premier**. Il doit être révoqué et remplacé chez le fournisseur. Le supprimer de l'historique ne suffit pas.
    7. Lancer le script du tableau : `node scripts/kickoff-check.mjs`. Il montre tout de suite ce qui existe.
    
    ## 2. Rapport d'audit à l'utilisateur
    Format court :
    - **Ce qu'est le projet** (déduit du code) : 3 lignes.
    - **Stack détectée.**
    - **État de santé** : build, tests, lint, dépendances obsolètes ou vulnérables (`npm audit`, `bun audit` ou `pip-audit` s'ils sont disponibles).
    - **Risques urgents** : secrets, absence de sauvegarde, dépendances avec failles critiques.
    - **Le tableau de préparation** (sortie du script).
    
    ## 3. Rétro-documenter
    Génère seulement les fichiers manquants, à partir du code :
    - `docs/PRD.md` « reconstitué » : fonctionnalités observées dans le code (routes, écrans, modèles). Chaque story déduite est marquée **(à confirmer)**. Pose ensuite à l'utilisateur seulement les questions sans réponse dans le code : objectif, utilisateurs, ce qui reste à faire, hors périmètre.
    - `docs/ARCHITECTURE.md` : schéma réel, modèle de données lu dans les migrations ou les modèles, variables d'environnement trouvées dans le code.
    - `CLAUDE.md` : les conventions **observées** (nommage, structure, style), pas celles du modèle si elles diffèrent.
    - `.env.example` : depuis les variables réellement utilisées (le script du tableau les liste).
    
    ## 4. Ensuite
    Reprends les phases normales du skill, uniquement pour les lignes ❌ et ⚠️ du tableau. Le plan (`docs/PLAN.md`) commence par un jalon **J0 « remise à niveau »** : CI verte, secrets corrigés, tests de base sur les parcours critiques. Ensuite viennent les nouvelles fonctionnalités.
    
  • references/retrospective.md2.0 KB67f3f844bb9dupstream ↗
    # Rétrospective de fin de projet (ou de fin de jalon important)
    
    Déclencheurs : « rétro », « fin de projet », « on a livré », « bilan », ou la mise en production de la v1.
    Objectif : que le **prochain** projet profite de celui-ci, grâce au profil utilisateur.
    
    ## 1. Rassembler les faits (sans rien demander)
    - Lance `node scripts/kickoff-check.mjs` et lis `docs/KICKOFF.md`.
    - Compare `docs/PLAN.md` et `docs/ESTIMATION.md` à la réalité : jalons terminés, dates des commits (`git log --format="%ad %s" --date=short`), issues fermées et ouvertes.
    - Repère les correctifs : commits `fix:` et issues `bug`. Quelle partie du code a le plus cassé ?
    - Repère les stories ajoutées en cours de route, qui n'étaient pas dans le PRD.
    
    ## 2. Poser 4 questions à l'utilisateur
    1. Qu'est-ce qui a le mieux marché ?
    2. Qu'est-ce qui t'a fait perdre le plus de temps ?
    3. Quelle question aurait-il fallu poser au kickoff ?
    4. Garderais-tu la même stack pour un projet similaire ?
    
    ## 3. Écrire `docs/RETRO.md`
    - Estimé contre réel (temps, coût).
    - Ce qui a marché, ce qui a coincé, les causes.
    - 3 à 5 **leçons** formulées comme des règles actionnables (« Toujours… », « Ne jamais… », « Pour un projet X, prévoir… »).
    
    ## 4. Mettre à jour le profil `~/.claude/kickoff-profile.md`
    - Ajoute les leçons dans « Leçons » (sans doublon ; fusionne si une leçon existe déjà).
    - Ajoute une ligne dans « Historique » (date, projet, type, mode, stack, issue).
    - Mets à jour « Stacks préférées » si l'utilisateur valide ou rejette une technologie.
    Montre le diff du profil à l'utilisateur avant de l'enregistrer.
    
    ## 5. Améliorer le skill lui-même (optionnel)
    Si une leçon est valable pour **tout le monde** et pas seulement pour cet utilisateur (une question manquante dans un pack, un piège non listé), propose une modification précise du pack ou de `SKILL.md`. Après l'accord de l'utilisateur, ajoute aussi un scénario dans `evals/scenarios/` pour que le problème ne revienne pas.
    
  • .gitattributes19 B6313b56c5784upstream ↗
    * text=auto eol=lf
    
  • .gitignore51 B6df9c22ac623upstream ↗
    node_modules/
    .claude/kickoff-state.json
    .DS_Store
    
  • evals/check.test.mjs8.6 KBa51b8391009dupstream ↗
    // Tests du script du tableau de préparation. Lancer : node evals/check.test.mjs
    import test from "node:test";
    import assert from "node:assert/strict";
    import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, existsSync, copyFileSync } from "node:fs";
    import { spawnSync } from "node:child_process";
    import { join, dirname } from "node:path";
    import { tmpdir } from "node:os";
    import { fileURLToPath } from "node:url";
    
    const skill = join(dirname(fileURLToPath(import.meta.url)), "..");
    const checkScript = join(skill, "scripts", "check.mjs");
    
    const check = (dir, ...flags) => {
      const r = spawnSync(process.execPath, [checkScript, "--offline", ...flags], { cwd: dir, encoding: "utf8", env: { ...process.env, KICKOFF_ROOT: dir, CLAUDE_PROJECT_DIR: dir } });
      return { ...r, json: flags.includes("--json") ? JSON.parse(r.stdout) : null };
    };
    const row = (report, id) => report.rows.find((r) => r.id === id);
    const write = (dir, file, content) => {
      mkdirSync(dirname(join(dir, file)), { recursive: true });
      writeFileSync(join(dir, file), content);
    };
    const git = (dir, ...a) => spawnSync("git", a, { cwd: dir, encoding: "utf8" });
    
    const PRD = `# PRD
    ## Vision
    Une app de test assez longue pour dépasser le seuil minimal de contenu attendu par le script.
    ## User stories
    En tant que client, je veux réserver. Critères d'acceptation : créneau futur uniquement.
    En tant que coiffeuse, je veux voir mon agenda. Critères d'acceptation : vue jour.
    ## Hors périmètre
    Paiement.
    ## Exigences non fonctionnelles
    Mobile d'abord.
    ## Hypothèses
    Un seul fauteuil.
    `;
    
    // Projet « complet » minimal qui doit passer toutes les lignes bloquantes.
    const makeProject = (mode = "express") => {
      const dir = mkdtempSync(join(tmpdir(), "kickoff-"));
      git(dir, "init", "-q", "-b", "main");
      write(dir, "docs/PRD.md", PRD);
      write(dir, "docs/ARCHITECTURE.md", "# Archi\n```mermaid\nerDiagram\n```\n## Variables d'environnement\nAPI_KEY\n");
      write(dir, "docs/adr/0001-stack.md", "# ADR 1\n");
      write(dir, "docs/PLAN.md", "| J0 | squelette |\n| J1 | auth |\n");
      write(dir, "CLAUDE.md", "# Projet\n## Commandes\n- `bun run dev`\n## Definition of Done\n- [ ] tests\n");
      write(dir, ".env.example", "API_KEY=\n");
      write(dir, ".gitignore", "node_modules/\n.env\n.env.*\n!.env.example\n");
      write(dir, ".gitattributes", "* text=auto eol=lf\n");
      write(dir, ".claude/settings.json", readFileSync(join(skill, "templates", "settings.json"), "utf8").replace(/<pm>/g, "bun"));
      mkdirSync(join(dir, ".claude", "hooks"), { recursive: true });
      copyFileSync(join(skill, "templates", "hooks", "format.mjs"), join(dir, ".claude", "hooks", "format.mjs"));
      write(dir, "docs/kickoff.json", JSON.stringify({ project: "Test", mode, currentMilestone: "J0" }));
      return dir;
    };
    
    test("dossier vide : pas prêt, lignes bloquantes manquantes", () => {
      const dir = mkdtempSync(join(tmpdir(), "kickoff-empty-"));
      const { json } = check(dir, "--json");
      assert.match(json.verdict, /Pas prêt/);
      assert.equal(row(json, "prd").status, "❌");
      assert.equal(row(json, "secrets").status, "❌");
      assert.equal(row(json, "repo").status, "❌");
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("--strict sort en erreur quand le projet n'est pas prêt", () => {
      const dir = mkdtempSync(join(tmpdir(), "kickoff-strict-"));
      assert.equal(check(dir, "--json", "--strict").status, 1);
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("projet express complet : toutes les lignes bloquantes vertes, CI sans objet", () => {
      const dir = makeProject("express");
      const { json } = check(dir, "--json");
      for (const id of ["prd", "architecture", "plan", "claude_md", "secrets"]) assert.equal(row(json, id).status, "✅", `${id} : ${row(json, id).proof}`);
      assert.equal(row(json, "ci").status, "➖");
      assert.equal(row(json, "settings").status, "✅", row(json, "settings").proof);
      assert.doesNotMatch(json.verdict, /Pas prêt/);
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("mode standard : la CI absente bloque", () => {
      const dir = makeProject("standard");
      const { json } = check(dir, "--json");
      assert.equal(row(json, "ci").status, "❌");
      assert.match(json.verdict, /Pas prêt/);
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("variable utilisée dans le code mais absente de .env.example", () => {
      const dir = makeProject();
      write(dir, "src/pay.ts", "const k = process.env.STRIPE_SECRET_KEY;\nconst e = process.env.NODE_ENV;\n");
      const r = row(check(dir, "--json").json, "secrets");
      assert.equal(r.status, "⚠️");
      assert.match(r.proof, /STRIPE_SECRET_KEY/);
      assert.doesNotMatch(r.proof, /NODE_ENV/);
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("fichier .env suivi par Git : ❌ même s'il est dans .gitignore", () => {
      const dir = makeProject();
      write(dir, ".env", "API_KEY=secret\n");
      git(dir, "add", "-f", ".env");
      const r = row(check(dir, "--json").json, "secrets");
      assert.equal(r.status, "❌");
      assert.match(r.proof, /SECRET SUIVI/);
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("une ligne bloquante ne peut pas être écartée par kickoff.json", () => {
      const dir = makeProject("standard");
      rmSync(join(dir, "docs/PRD.md"));
      write(dir, "docs/kickoff.json", JSON.stringify({ mode: "standard", na: { prd: "pas envie", dependabot: "projet perso" } }));
      const { json } = check(dir, "--json");
      assert.equal(row(json, "prd").status, "❌");
      assert.equal(row(json, "dependabot").status, "➖");
      assert.equal(row(json, "dependabot").proof, "projet perso");
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("régression détectée après une vérification réussie", () => {
      const dir = makeProject();
      check(dir); // écrit docs/KICKOFF.md et l'état
      assert.ok(existsSync(join(dir, "docs/KICKOFF.md")));
      rmSync(join(dir, "docs/PLAN.md"));
      const { json } = check(dir, "--json");
      assert.deepEqual(json.regressions, ["plan"]);
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("revue : un point bloquant ouvert donne ⚠️, tout coché donne ✅", () => {
      const dir = makeProject("standard");
      write(dir, "docs/REVIEW.md", "# Revue\n- [ ] [Bloquant] fuseaux → stocker en UTC\n- [x] [Important] double clic → corrigé\n");
      assert.equal(row(check(dir, "--json").json, "revue").status, "⚠️");
      write(dir, "docs/REVIEW.md", "# Revue\n- [x] [Bloquant] fuseaux → corrigé : UTC\n");
      assert.equal(row(check(dir, "--json").json, "revue").status, "✅");
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("mode complet : la revue devient bloquante", () => {
      const dir = makeProject("complet");
      const { json } = check(dir, "--json");
      assert.equal(row(json, "revue").type, "bloquant");
      assert.ok(json.blockers.includes("revue"));
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("CLAUDE.md avec des placeholders non remplis : ⚠️", () => {
      const dir = makeProject();
      write(dir, "CLAUDE.md", "# <Nom du projet>\n- `<pm> run dev`\n## Definition of Done\n");
      const r = row(check(dir, "--json").json, "claude_md");
      assert.equal(r.status, "⚠️");
      assert.match(r.proof, /<pm>/);
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("settings qui bloque .env.example : ⚠️", () => {
      const dir = makeProject();
      write(dir, ".claude/settings.json", JSON.stringify({ permissions: { deny: ["Read(./.env.*)"] } }));
      assert.match(row(check(dir, "--json").json, "settings").proof, /\.env\.example bloqué/);
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("odoo : fichier d'exemple des secrets configurable", () => {
      const dir = makeProject();
      rmSync(join(dir, ".env.example"));
      write(dir, "odoo.conf.example", "[options]\nadmin_passwd =\n");
      write(dir, "docs/kickoff.json", JSON.stringify({ mode: "express", envExample: "odoo.conf.example" }));
      assert.equal(row(check(dir, "--json").json, "secrets").status, "✅");
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("--summary : résumé court, aucun fichier écrit", () => {
      const dir = makeProject("standard");
      const r = check(dir, "--summary");
      assert.match(r.stdout, /^\[Kickoff\] Test · jalon en cours : J0/);
      assert.ok(!existsSync(join(dir, "docs/KICKOFF.md")));
      rmSync(dir, { recursive: true, force: true });
    });
    
    test("hook de formatage : ne plante pas sans formateur ni sur un .py", () => {
      const dir = makeProject();
      for (const file of ["a.ts", "b.py", "c.png"]) {
        const r = spawnSync(process.execPath, [join(dir, ".claude/hooks/format.mjs")], { cwd: dir, input: JSON.stringify({ tool_input: { file_path: file } }), encoding: "utf8", env: { ...process.env, CLAUDE_PROJECT_DIR: dir } });
        assert.equal(r.status, 0, `${file} : ${r.stderr}`);
      }
      rmSync(dir, { recursive: true, force: true });
    });
    
  • evals/scenarios/01-coiffly-saas.md1.4 KBa024ed96834cupstream ↗
    # Scénario 1 — Coiffly (SaaS de réservation, niveau intermédiaire)
    
    ## Description donnée au départ
    « Je veux une app pour que les coiffeurs indépendants prennent leurs rendez-vous en ligne. »
    
    ## Réponses de l'utilisateur
    - Profil : intermédiaire · France · seul · préfère Next.js + Supabase · Vercel · budget 0–20 €/mois · bun installé, pas pnpm.
    - Persona : coiffeuse indépendante, 1 fauteuil ; les clients réservent sans compte depuis leur téléphone.
    - MVP : page publique par coiffeuse, prestations, horaires et absences, réservation, email de confirmation et annulation, agenda.
    - Hors périmètre : paiement, multi-employés, SMS, app native.
    - Échéance : 6 semaines.
    
    ## Attendus
    - Pack **saas**, mode **standard** (pas de paiement en v1).
    - Gestionnaire de paquets : **bun** (pnpm absent), aucun `<pm>` restant.
    - Le PRD ou la revue mentionne : deux réservations simultanées sur le même créneau, fuseau horaire, double clic, créneau passé, modification des horaires avec des RDV existants.
    - L'architecture impose la non-collision **en base** (contrainte d'exclusion ou équivalent).
    - La revue « avocat du diable » existe (`docs/REVIEW.md`).
    - `ESTIMATION.md` cite ses sources de prix ou écrit « à vérifier ».
    - Pages légales FR : mentions légales et politique de confidentialité.
    - Tableau : aucune ligne ➖ (standard), `profil` ✅.
    
  • evals/scenarios/02-boulangerie-vitrine.md1.2 KBc9c72828a58eupstream ↗
    # Scénario 2 — Boulangerie (site vitrine, niveau débutant, mode express)
    
    ## Description donnée au départ
    « Ma tante a une boulangerie, je veux lui faire un petit site rapidement pour qu'on la trouve sur Google. »
    
    ## Réponses de l'utilisateur
    - Profil : **débutant** · Belgique · seul · aucune stack préférée · pas d'hébergeur · budget 0 €.
    - 4 pages : accueil, produits, horaires et accès, contact. Sa tante veut modifier les horaires elle-même.
    - Les photos ne sont pas prêtes. Délai : un week-end.
    
    ## Attendus
    - Pack **vitrine**, mode **express**.
    - Au plus 8 questions générales (niveau débutant), avec les termes techniques expliqués.
    - Une seule recommandation de stack, expliquée simplement (Astro + un CMS simple pour les horaires).
    - Le PRD signale le risque « photos non prêtes ».
    - Formulaire de contact avec anti-spam ; SEO local (données structurées LocalBusiness) ; polices auto-hébergées (UE).
    - Belgique : RGPD, autorité APD, mentions légales.
    - Tableau : `revue`, `estimation`, `design`, `ci`, `dependabot` et `issues` en ➖ « mode express ». Les lignes bloquantes restantes (prd, architecture, plan, claude_md, secrets) sont ✅.
    
  • evals/scenarios/03-presence-ecole.md1.6 KB9e684874dab6upstream ↗
    # Scénario 3 — Présence école (données de mineurs, Côte d'Ivoire, niveau expert)
    
    ## Description donnée au départ
    « Une app pour que les enseignants fassent l'appel et que les parents soient prévenus des absences. »
    
    ## Réponses de l'utilisateur
    - Profil : **expert** · Côte d'Ivoire · pour un client (groupe de 3 écoles) · Next.js + Supabase ou Odoo · budget 30 €/mois.
    - Les enseignants font l'appel sur leur téléphone ; le réseau est mauvais dans certaines classes.
    - Les parents doivent être prévenus par SMS ou WhatsApp, et payer les frais de scolarité par mobile money plus tard (hors v1).
    - Rôles : direction, enseignant, parent. Les élèves ne se connectent pas.
    
    ## Attendus
    - Pack **ecole**, mode **complet** imposé (données de mineurs), même si l'utilisateur ne le demande pas.
    - Questions techniques avancées (niveau expert) et comparaison Next.js/Supabase contre module Odoo dans un ADR.
    - PRD : matrice rôles × actions ; mode hors ligne avec synchronisation ; passage d'une année scolaire à l'autre ; import des élèves sans doublons.
    - Revue « avocat du diable » **bloquante**, avec au moins : consentement des parents, aucune donnée sensible dans les SMS, cloisonnement parent/enseignant testé.
    - Pays : loi n° 2013-450 et ARTCI mentionnées ; le mobile money est noté pour plus tard, avec la disponibilité de l'agrégateur « à vérifier ».
    - Estimation **bloquante**, coût des SMS × volume signalé comme poste variable.
    - Tableau : `revue` et `estimation` de type bloquant ; aucune ligne ➖ sauf avec une raison acceptée.
    
  • LICENSE1.0 KB2f6ce8440a4bupstream ↗
    MIT License
    
    Copyright (c) 2026 mahelmouzoun07-afk
    
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
    
  • templates/CLAUDE.md1.9 KB438283a6c6dfupstream ↗
    # <Nom du projet>
    
    ## Contexte
    <Une phrase : quel problème, pour qui.> Détails : `docs/PRD.md`. Architecture : `docs/ARCHITECTURE.md`. Plan : `docs/PLAN.md`.
    
    ## Stack
    - <ex. Next.js 15 App Router, TypeScript strict, Tailwind v4, shadcn/ui>
    - <ex. Supabase (Postgres + Auth + Storage), Drizzle, Zod>
    - <ex. Vitest, Playwright, Biome>
    - Hébergement : <ex. Vercel> · Erreurs : Sentry
    
    ## Commandes
    - Dev : `<pm> run dev` (http://localhost:3000)
    - Vérification complète : `<pm> run lint && <pm> run typecheck && <pm> run test && <pm> run build`
    - E2E : `<pm> run test:e2e`
    - Migrations : `<commande>`
    
    ## Arborescence
    - `src/app/` routes · `src/components/ui/` composants de base · `src/features/<domaine>/` logique par domaine
    - `src/lib/` utilitaires · `src/server/` code serveur uniquement · `tests/` E2E
    
    ## Conventions
    - TypeScript strict, pas de `any`. Validation Zod à chaque frontière (formulaires, API, variables d'environnement).
    - Composants serveur par défaut, `"use client"` seulement si nécessaire.
    - Toujours utiliser les tokens du design system (`docs/DESIGN.md`), jamais de couleurs en dur.
    - Gérer les états chargement, vide et erreur sur chaque écran. Mobile d'abord.
    - Commits : Conventional Commits. Une branche et une PR par tâche.
    - Avant d'utiliser une librairie, consulter sa documentation via le MCP context7.
    
    ## Interdits
    - Ne jamais lire, afficher ou committer `.env*`. Les nouvelles variables vont dans `.env.example`.
    - Pas de nouvelle dépendance sans justification dans la PR.
    - Ne jamais désactiver un test ou une règle de lint pour « faire passer ».
    
    ## Definition of Done
    - [ ] Critères d'acceptation vérifiés (interface testée dans le navigateur via Playwright)
    - [ ] Tests ajoutés et vérification complète au vert
    - [ ] États chargement, vide et erreur, rendu mobile et accessibilité (labels, contraste, clavier)
    - [ ] Documentation mise à jour si l'architecture ou les variables d'environnement changent
    
  • templates/commands/kickoff-status.md1.0 KB497b3e4d044cupstream ↗
    ---
    description: Affiche le tableau de préparation du projet, vérifié en direct, avec les régressions et les prochaines actions
    allowed-tools: Bash(node scripts/kickoff-check.mjs:*), Read
    ---
    
    Lance `node scripts/kickoff-check.mjs` (en ligne, pour vérifier aussi la CI, les issues et la preview), puis :
    
    1. Affiche le tableau tel que le script l'a produit. Ne change aucun statut à la main.
    2. S'il y a des **régressions**, explique chacune en une phrase : ce qui a changé et la cause probable (lis les fichiers concernés ou `git log -5` si besoin).
    3. Propose les **3 prochaines actions** les plus utiles : les lignes bloquantes d'abord, puis les régressions, puis les lignes recommandées. Pour chacune, dis ce que tu ferais concrètement.
    4. Rappelle le jalon en cours (`currentMilestone` dans `docs/kickoff.json`). Si tous ses éléments semblent terminés, propose de passer au suivant et de mettre à jour ce champ.
    
    Adapte les explications au niveau indiqué dans `docs/kickoff.json` (`level`). Ne corrige rien sans l'accord de l'utilisateur.
    
  • templates/hooks/format.mjs1.5 KB3758de55eb2fupstream ↗
    // Hook PostToolUse : formate le fichier que Claude vient de modifier avec le formateur du projet.
    // Biome en priorité, sinon Prettier. Lance leur binaire JS via node (pas de shell : marche aussi sous Windows).
    // Les fichiers Python passent par ruff s'il est installé.
    import { existsSync } from "node:fs";
    import { spawnSync } from "node:child_process";
    import { join } from "node:path";
    
    let input = "";
    for await (const chunk of process.stdin) input += chunk;
    
    const file = JSON.parse(input || "{}").tool_input?.file_path;
    const root = process.env.CLAUDE_PROJECT_DIR || process.cwd();
    
    if (file?.endsWith(".py")) {
      const r = spawnSync("ruff", ["format", file], { cwd: root, encoding: "utf8" });
      if (r.error) console.error("format hook : ruff non installé, fichier Python non formaté.");
      else if (r.status !== 0) console.error(r.stderr || r.stdout);
      process.exit(0);
    }
    if (!file || !/\.(ts|tsx|js|jsx|mjs|cjs|json|jsonc|css|md)$/.test(file)) process.exit(0);
    
    const formatters = [
      { bin: join(root, "node_modules/@biomejs/biome/bin/biome"), args: ["format", "--write", file] },
      { bin: join(root, "node_modules/prettier/bin/prettier.cjs"), args: ["--write", file] },
    ];
    
    const formatter = formatters.find((f) => existsSync(f.bin));
    if (!formatter) {
      console.error("format hook : ni Biome ni Prettier installé, fichier non formaté.");
      process.exit(0);
    }
    
    const result = spawnSync(process.execPath, [formatter.bin, ...formatter.args], { cwd: root, encoding: "utf8" });
    if (result.status !== 0) console.error(result.stderr || result.stdout);
    process.exit(0);
    
  • templates/kickoff.json252 Bc374098c7f20upstream ↗
    {
      "project": "<Nom du projet>",
      "mode": "standard",
      "level": "intermédiaire",
      "pack": "saas",
      "country": "FR",
      "packageManager": "bun",
      "currentMilestone": "Cadrage",
      "previewUrl": "",
      "mcp": ["context7", "playwright"],
      "na": {}
    }
    
  • templates/settings.json1.0 KBe354d43e0ba4upstream ↗
    {
      "permissions": {
        "allow": [
          "Bash(<pm> run:*)",
          "Bash(<pm> test:*)",
          "Bash(node scripts/kickoff-check.mjs:*)",
          "Bash(git status:*)",
          "Bash(git diff:*)",
          "Bash(git log:*)",
          "Bash(gh issue list:*)",
          "Bash(gh pr view:*)"
        ],
        "deny": [
          "Read(./.env)",
          "Read(./.env.local)",
          "Read(./.env.*.local)",
          "Read(./.env.production)",
          "Read(./.env.development)",
          "Bash(git push --force:*)",
          "Bash(git push -f:*)",
          "Bash(rm -rf:*)"
        ]
      },
      "hooks": {
        "SessionStart": [
          {
            "hooks": [
              {
                "type": "command",
                "command": "node \"$CLAUDE_PROJECT_DIR/scripts/kickoff-check.mjs\" --offline --summary"
              }
            ]
          }
        ],
        "PostToolUse": [
          {
            "matcher": "Edit|Write|MultiEdit",
            "hooks": [
              {
                "type": "command",
                "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/format.mjs\""
              }
            ]
          }
        ]
      }
    }
    

Plain-text copy, no login: /skill-bundles/mahelmouzoun07-afk-project-kickoff.md

// REPO STATS

0 stars