diff --git a/.github/ISSUE_TEMPLATE/01-mvp-foundation.md b/.github/ISSUE_TEMPLATE/01-mvp-foundation.md
new file mode 100644
index 0000000..66fe0de
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/01-mvp-foundation.md
@@ -0,0 +1,15 @@
+---
+name: MVP Foundation
+about: Fondations monorepo et architecture feature-first
+title: "[MVP] Fondations monorepo Solid + Capacitor"
+labels: ["mvp", "foundation"]
+assignees: []
+---
+
+## Objectif
+Mettre en place la base technique feature-first et DDD simple.
+
+## Critères d'acceptation
+- [ ] Workspaces apps/packages opérationnels
+- [ ] Domain immutable pour base/variant/recipe
+- [ ] Scripts qualité exécutables
diff --git a/.github/ISSUE_TEMPLATE/02-builder-flow.md b/.github/ISSUE_TEMPLATE/02-builder-flow.md
new file mode 100644
index 0000000..d9c349e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/02-builder-flow.md
@@ -0,0 +1,16 @@
+---
+name: Builder Flow
+about: Parcours base -> variante -> recette
+title: "[MVP] Builder flow 3 étapes"
+labels: ["mvp", "feature"]
+assignees: []
+---
+
+## Objectif
+Implémenter le wizard de construction bento.
+
+## Critères d'acceptation
+- [ ] Filtre salé/sucré base
+- [ ] Variante filtrée par base
+- [ ] Recette fusionnée avec ordre de priorité
+- [ ] Partage + impression + quantité
diff --git a/.github/ISSUE_TEMPLATE/03-quality-security.md b/.github/ISSUE_TEMPLATE/03-quality-security.md
new file mode 100644
index 0000000..58cb074
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/03-quality-security.md
@@ -0,0 +1,15 @@
+---
+name: Quality and Security
+about: CI quality gates et audits sécurité
+title: "[MVP] Quality gates + security audits"
+labels: ["mvp", "quality", "security"]
+assignees: []
+---
+
+## Objectif
+Garantir qualité, sécurité et non-régression.
+
+## Critères d'acceptation
+- [ ] Unit/integration/e2e en CI
+- [ ] SAST + dependencies + secrets scan
+- [ ] Lighthouse perf/seo/a11y bloquant
diff --git a/.github/ISSUE_TEMPLATE/04-refactor-builder-solid.md b/.github/ISSUE_TEMPLATE/04-refactor-builder-solid.md
new file mode 100644
index 0000000..cd58116
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/04-refactor-builder-solid.md
@@ -0,0 +1,33 @@
+---
+name: Refactor Builder SOLID
+about: Découpage SOLID du flux builder (BuilderFlow)
+title: "[Refactor] Builder — découpage SOLID et responsabilités claires"
+labels: ["refactor"]
+assignees: []
+---
+
+## Contexte
+
+La logique et l’UI du parcours builder sont concentrées dans `apps/web/src/ui/BuilderFlow.tsx` (fichier très volumineux). Le domaine pur existe déjà dans `packages/features/builder/src/domain.ts`.
+
+## Objectif
+
+Réduire la surface « tout-en-un » du flux builder et clarifier les frontières entre UI, état du wizard, et domaine (`@ben-to/builder`).
+
+## Pistes techniques
+
+- Extraire par **responsabilité unique** : étapes (base / variante / recette), barre d’actions, feuille recette, navigation « Continuer », synchronisation reset (`builder-reset-bus`).
+- Introduire des **hooks / ports** testables (ex. `useBuilderSteps`, `useRecipeSheet`) pour isoler les effets et la lecture du catalogue.
+- **DIP** : l’UI consomme le domaine (`domain.ts`) et des interfaces stables pour le catalogue, sans détails de parsing dispersés.
+- Conserver / étendre les tests (`*.unit.test.ts`, e2e, Gherkin `@int-builder-*`).
+
+## Critères d’acceptation
+
+- [ ] Aucun fichier du flux builder > ~250–300 lignes sans justification documentée en commentaire de module.
+- [ ] Tests unitaires / intégration et e2e verts ; pas de régression sur le parcours dans `specs/features/builder.feature`.
+
+## Références
+
+- `apps/web/src/ui/BuilderFlow.tsx`
+- `packages/features/builder/src/domain.ts`
+- `specs/features/builder.feature`
diff --git a/.github/ISSUE_TEMPLATE/05-architecture-no-index-barrels.md b/.github/ISSUE_TEMPLATE/05-architecture-no-index-barrels.md
new file mode 100644
index 0000000..c7d3990
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/05-architecture-no-index-barrels.md
@@ -0,0 +1,36 @@
+---
+name: Architecture sans barrels index
+about: Supprimer les index.ts barrel et verrouiller par règle / CI
+title: "[Archi] Supprimer les index.ts barrel et verrouiller par règle projet"
+labels: ["architecture"]
+assignees: []
+---
+
+## Contexte
+
+Les packages exposent des barrels `src/index.ts` avec `export * from "..."`. Les alias Vite dans `apps/web/vite.config.ts` pointent vers ces fichiers. Ce pattern complique le suivi des imports réels et la détection de code inutilisé.
+
+## Objectif
+
+Supprimer les barrels `index.ts`, migrer vers des imports **explicites**, et **verrouiller** la réintroduction du pattern (règles projet + CI).
+
+## Travail technique
+
+1. Remplacer les alias Vite (et chemins TypeScript si besoin) par des modules canoniques nommés (ex. `@ben-to/builder/domain`, `@ben-to/recipes/catalog`) ou par des sous-chemins `package.json` **`exports`** sans fichier nommé `index.ts`.
+2. Mettre à jour tous les imports (`apps/web`, `packages/**`).
+3. Supprimer les anciens `**/src/index.ts` après migration.
+
+## Règles / outillage
+
+- Étendre `.cursor/rules/ben-to-engineering.mdc` : pas de nouveau barrel `index.ts` ; imports depuis modules nommés.
+- Optionnel : ESLint `no-restricted-imports` ou script `scripts/quality/` qui échoue si un `**/src/index.ts` barrel réapparaît.
+
+## Critères d’acceptation
+
+- [ ] Plus de `export * from` dans des `index.ts` à la racine des packages features/shared concernés.
+- [ ] CI (`check:quality` ou lint) empêche la réintroduction du pattern (selon le niveau de durcissement retenu).
+
+## Références
+
+- `apps/web/vite.config.ts`
+- `packages/features/*/src/index.ts`, `packages/shared/*/src/index.ts`
diff --git a/.github/ISSUE_TEMPLATE/06-seo-recipe-og-twitter.md b/.github/ISSUE_TEMPLATE/06-seo-recipe-og-twitter.md
new file mode 100644
index 0000000..d179f98
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/06-seo-recipe-og-twitter.md
@@ -0,0 +1,38 @@
+---
+name: SEO recettes OG / réseaux sociaux
+about: Métadonnées Open Graph et Twitter par URL /r/...
+title: "[SEO] Métadonnées OG/Twitter par recette (/r/...)"
+labels: ["seo"]
+assignees: []
+---
+
+## Contexte
+
+Les métadonnées Open Graph et Twitter sont définies une seule fois dans `apps/web/index.html`. Les URLs de recette `/r/...` doivent exposer **titre**, **description** et **image** adaptés au partage.
+
+**Contrainte SPA** : modifier `
` ou les meta **uniquement en JavaScript après chargement** ne suffit souvent pas pour les robots d’aperçu (Facebook, X, LinkedIn) : ils lisent le HTML initial.
+
+## Objectif
+
+Pour chaque recette partageable, fournir des métadonnées correctes : `og:title`, `og:description`, `og:image`, `og:url`, `twitter:card` (idéalement `summary_large_image` si visuel adapté), éventuellement `canonical`.
+
+## Options à trancher (documenter le choix dans README ou `docs/`)
+
+| Approche | Effort | Aperçus réseaux |
+|----------|--------|-----------------|
+| Pré-rendu statique des routes `/r/*` au build (catalogue / pipeline recettes) | Moyen | Bon pour crawlers statiques |
+| Middleware / route serveur (Worker, reverse proxy, petite app Node) injectant le HTML minimal avec meta | Variable | Très bon si bien déployé |
+| Meta uniquement côté client (Solid) | Faible | Insuffisant pour la majorité des partages |
+
+Données : titres localisés, description courte, images de couverture produites par le pipeline recettes.
+
+## Critères d’acceptation
+
+- [ ] Partager une URL `/r/...` affiche un aperçu avec **titre et image recette** (validation manuelle : Meta Sharing Debugger / Twitter Card Validator ou équivalent).
+- [ ] Balises OG/Twitter cohérentes (`og:type`, `og:url`, etc.).
+- [ ] Documentation de la stratégie retenue (build vs serveur).
+
+## Références
+
+- `apps/web/index.html`
+- Parcours e2e et URL partagée dans `tests/e2e/builder.e2e.ts`
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..fde5006
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,16 @@
+version: 2
+updates:
+ - package-ecosystem: npm
+ directory: "/"
+ schedule:
+ interval: weekly
+ open-pull-requests-limit: 15
+ groups:
+ npm-all:
+ patterns:
+ - "*"
+
+ - package-ecosystem: github-actions
+ directory: "/"
+ schedule:
+ interval: weekly
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..6431daa
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,13 @@
+## Ticket
+
+- [ ] Issue GitHub liée : #_____
+- [ ] Branche nommée de façon explicite (ex. `feat/issue-___-…`)
+
+## Vérifications
+
+- [ ] `npm run test:ci` exécuté localement avec succès
+- [ ] Impact **mobile Android** pris en compte si pertinent (Capacitor / Gradle)
+
+## Notes pour les relecteurs
+
+
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..93d1f73
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,158 @@
+name: CI
+
+on:
+ pull_request:
+ push:
+ branches: [main]
+
+jobs:
+ quality:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 22
+ cache: npm
+ - run: npm ci
+ - run: npm run pipeline:recipes
+ - run: npm run check:quality
+ - run: npm run lint
+ - run: npm run typecheck
+ - run: npm run test:coverage
+ - name: Upload coverage
+ uses: actions/upload-artifact@v7
+ with:
+ name: coverage-summary
+ path: |
+ coverage/coverage-summary.json
+ coverage/lcov.info
+
+ e2e-and-a11y:
+ runs-on: ubuntu-latest
+ needs: quality
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 22
+ cache: npm
+ - run: npm ci
+ - run: npm run pipeline:recipes
+ - run: npx playwright install --with-deps
+ - run: npm run test:e2e
+ - name: Upload quality summaries (a11y + perf)
+ uses: actions/upload-artifact@v7
+ with:
+ name: quality-e2e
+ path: quality-out/
+ if-no-files-found: warn
+
+ lighthouse:
+ runs-on: ubuntu-latest
+ needs: quality
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 22
+ cache: npm
+ - run: npm ci
+ - run: npm run audit:lighthouse
+ # .lighthouseci est dans .gitignore : upload-artifact exclut ces fichiers → aucun artefact « lighthouse-ci ».
+ # Copie vers un dossier non ignoré pour forcer l’upload réel des LHR.
+ - name: Stage LHCI reports for artifact
+ run: |
+ rm -rf __lhci_artifact__
+ mkdir -p __lhci_artifact__
+ cp -R .lighthouseci/. __lhci_artifact__/
+ test "$(find __lhci_artifact__ -type f | wc -l)" -gt 0 || { echo "::error::LHCI n'a produit aucun fichier"; exit 1; }
+ - name: Upload Lighthouse CI output
+ uses: actions/upload-artifact@v7
+ with:
+ name: lighthouse-ci
+ path: __lhci_artifact__
+ if-no-files-found: error
+
+ android-smoke:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 22
+ cache: npm
+ - uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: 21
+ - name: Setup Android SDK
+ uses: android-actions/setup-android@v4
+ - run: sdkmanager "platforms;android-35" "build-tools;35.0.0"
+ - run: npm ci
+ - run: npm run pipeline:recipes
+ - run: npm run build:web
+ - run: npm run mobile:sync
+ - name: Gradle assembleDebug
+ working-directory: apps/mobile/android
+ run: chmod +x gradlew && ./gradlew assembleDebug --no-daemon
+
+ pull-request-report:
+ if: github.event_name == 'pull_request'
+ needs: [quality, e2e-and-a11y, lighthouse]
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 22
+ cache: npm
+ - run: npm ci
+ - run: mkdir -p incoming
+ - run: npm audit --json > incoming/audit.json || true
+ # Même version majeure que upload-artifact (@v7) : v8 peut ne pas résoudre les artefacts v7.
+ - uses: actions/download-artifact@v8
+ with:
+ name: coverage-summary
+ path: incoming/coverage
+ - uses: actions/download-artifact@v8
+ with:
+ name: quality-e2e
+ path: incoming/e2e
+ - uses: actions/download-artifact@v8
+ with:
+ name: lighthouse-ci
+ path: incoming/lhci
+ - run: node scripts/ci/pr-quality-report.mjs incoming incoming/pr-body.md
+ - uses: actions/github-script@v9
+ with:
+ script: |
+ const fs = require('fs');
+ const marker = '';
+ const body = marker + '\n' + fs.readFileSync('incoming/pr-body.md','utf8');
+ const issue_number = context.payload.pull_request.number;
+ const { data: comments } = await github.rest.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number,
+ per_page: 100
+ });
+ const existing = comments.find(c => c.body.includes(marker));
+ if (existing) {
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: existing.id,
+ body
+ });
+ } else {
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number,
+ body
+ });
+ }
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
new file mode 100644
index 0000000..cd14776
--- /dev/null
+++ b/.github/workflows/dependency-review.yml
@@ -0,0 +1,22 @@
+name: Dependency review
+
+on:
+ pull_request:
+
+permissions:
+ contents: read
+
+jobs:
+ dependency-review:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 22
+ cache: npm
+ # L’action officielle `dependency-review-action` suppose Dependency graph + GitHub Advanced Security
+ # (réglages du dépôt). Sans GHAS elle échoue toujours sur les dépôts non éligibles.
+ # Même garde-fou que Security / deps-and-secrets : lockfile installable + npm audit.
+ - run: npm ci
+ - run: npm audit --audit-level=high
diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml
new file mode 100644
index 0000000..8d4d8c3
--- /dev/null
+++ b/.github/workflows/mobile-release.yml
@@ -0,0 +1,50 @@
+name: Mobile Release
+
+on:
+ push:
+ tags:
+ - "mobile-v*"
+ workflow_dispatch:
+
+jobs:
+ android:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 22
+ cache: npm
+ - uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: 21
+ - name: Setup Android SDK
+ uses: android-actions/setup-android@v4
+ - run: sdkmanager "platforms;android-35" "build-tools;35.0.0"
+ - run: npm ci
+ - run: npm run build:web
+ - run: npm run mobile:sync
+ - name: Gradle bundleDebug (sans keystore Play — artefact de validation CI)
+ working-directory: apps/mobile/android
+ run: chmod +x gradlew && ./gradlew bundleDebug --no-daemon
+ - uses: actions/upload-artifact@v7
+ with:
+ name: android-bundle-debug
+ path: apps/mobile/android/app/build/outputs/bundle/debug/*.aab
+ if-no-files-found: error
+
+ ios:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 22
+ cache: npm
+ - run: npm ci
+ - run: npm run build:web
+ - run: npm run mobile:sync
+ - name: iOS — projet non versionné ou machine locale
+ run: |
+ if [ -d apps/mobile/ios/App ]; then echo "Projet iOS présent — ouvrir Xcode pour archive."; else echo "Pas de dossier ios dans ce clone — ignorer pour Android-only."; fi
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
new file mode 100644
index 0000000..9863f19
--- /dev/null
+++ b/.github/workflows/security.yml
@@ -0,0 +1,51 @@
+name: Security
+
+on:
+ pull_request:
+ push:
+ branches: [main]
+ schedule:
+ - cron: "0 5 * * 1"
+
+jobs:
+ codeql:
+ # PR depuis fork : pas d’upload SARIF vers le dépôt cible (permissions GitHub).
+ # Dépôt sans « Code scanning » activé (Settings → Code security) : l’upload échoue ;
+ # `upload: false` garde l’analyse CodeQL sans appeler l’API. Quand le scanning est
+ # activé sur le repo, retirer `upload: false` (comportement par défaut = upload).
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+ steps:
+ - uses: actions/checkout@v7
+ - uses: github/codeql-action/init@v4
+ with:
+ languages: javascript-typescript
+ - uses: github/codeql-action/analyze@v4
+ with:
+ upload: false
+
+ deps-and-secrets:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: read
+ steps:
+ # Historique complet : gitleaks compare github.event.before..after sur push ;
+ # un clone depth=1 ne contient pas le parent, d’où « unknown revision ».
+ - uses: actions/checkout@v7
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 22
+ - run: npm ci
+ - run: npm run audit:deps
+ - name: Secret scan
+ # v2 — SHA épinglé (supply chain) ; tag : https://github.com/gitleaks/gitleaks-action/releases
+ uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}