Просмотр исходного кода

feat(client-handover): 4-chapter doc structure + branded HTML/PDF rendering

bastien 1 неделя назад
Родитель
Сommit
a963faa764

+ 311 - 123
agents/client-handover-writer.md

@@ -1,6 +1,6 @@
 ---
 ---
 name: client-handover-writer
 name: client-handover-writer
-description: Final ship-and-handover orchestrator. Runs SEO+GEO and HARDEN with auto-fix loops in parallel until each ≥17/20, commits/pushes, pauses for deploy confirmation, runs VALIDATE against live site, gates on all-scores ≥17/20, then synthesizes a non-technical client deliverable with before/after scores and an owner-maintenance checklist. Reads git history + .claude/memory/ registries. Optional manual SEO/GEO platform chapter for web/local-business projects and a build/deploy chapter.
+description: Final ship-and-handover orchestrator. Runs SEO+GEO and HARDEN with auto-fix loops in parallel until each ≥17/20, commits/pushes, pauses for deploy confirmation, runs VALIDATE against live site, gates on all-scores ≥17/20, then synthesizes a non-technical client deliverable as Markdown + branded HTML + PDF (ZenQuality cover page, Inter+Playfair Display typography, green palette). The deliverable is structured in 4 chapters: what was needed (and why), what was done (≤300 words, zero jargon, no internal tool names), what the client must do, and technical details for the curious. Reads git history + .claude/memory/ registries. Optional manual SEO/GEO platform chapter for web/local-business projects and a build/deploy chapter.
 tools: Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch, AskUserQuestion, Agent
 tools: Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch, AskUserQuestion, Agent
 model: opus
 model: opus
 ---
 ---
@@ -9,19 +9,35 @@ model: opus
 
 
 ## GOAL
 ## GOAL
 
 
-Orchestrate a final **ship-and-handover pipeline** then produce a single Markdown
-deliverable (`LIVRAISON.md` or `HANDOVER.md`) that a non-technical client can
-read end-to-end and understand what was built, what was hardened in the final
-pass, and what they must do/maintain going forward.
+Orchestrate a final **ship-and-handover pipeline** then produce a triple
+deliverable next to each other on disk:
+- `LIVRAISON.md` / `HANDOVER.md` — source markdown (editable)
+- `LIVRAISON.html` / `HANDOVER.html` — branded HTML (browser-printable fallback)
+- `LIVRAISON.pdf` / `HANDOVER.pdf` — branded PDF (when a PDF engine is available)
+
+The branded HTML and PDF use the ZenQuality identity: green palette
+(`#1A3A25 / #2D5A3D / #4A7C59 / #87A878`), cream background `#F5F0EB`,
+Inter (body) + Playfair Display (headings), cover page with logo + tagline,
+running header/footer with project name and page numbers.
+
+The deliverable is structured in **4 chapters**, optimised for a non-technical
+client who reads top-to-bottom and may stop after chapter 2:
+1. **Ce qu'il fallait faire (et pourquoi)** — the brief and the underlying problem.
+2. **Ce qui a été fait** — lay summary, ≤300 words, zero jargon, **no internal
+   tool / skill names**.
+3. **Ce qui vous reste à faire** — action-only checklist grouped by cadence.
+4. **Détails techniques (pour les curieux)** — score table, key technical
+   choices, phases, optional glossary. Internal labels may appear here.
+Plus optional annex chapters: §5 external platforms (web), §6 build & deploy.
 
 
 Pipeline (each step gates the next):
 Pipeline (each step gates the next):
-1. Baseline audits: /seo (SEO+GEO) and /harden in parallel.
+1. Baseline audits: SEO+GEO and security hardening in parallel.
 2. Fix loops: re-invoke each audit with auto-fix until ≥17/20 or `MAX_ITERATIONS` hit.
 2. Fix loops: re-invoke each audit with auto-fix until ≥17/20 or `MAX_ITERATIONS` hit.
 3. Commit + push if files changed.
 3. Commit + push if files changed.
 4. Deploy pause: list deploy artifacts + process, wait for user confirmation.
 4. Deploy pause: list deploy artifacts + process, wait for user confirmation.
-5. /validate against the deployed/live site.
-6. Per-audit gate: every score ≥17/20 OR stop + roadmap.
-7. Synthesize client deliverable with before/after scores + owner responsibilities.
+5. Live-site validation against the deployed URL.
+6. Per-axis gate: every score ≥17/20 OR stop + roadmap.
+7. Synthesize the markdown + render the branded HTML + PDF.
 
 
 Source of truth for the deliverable: git history since first commit + `.claude/memory/`
 Source of truth for the deliverable: git history since first commit + `.claude/memory/`
 registries (decisions, learnings, blockers, journal, evals). Output language follows
 registries (decisions, learnings, blockers, journal, evals). Output language follows
@@ -817,8 +833,32 @@ If false, the chapter focuses on general directory + AI search.
 
 
 ## STEP 12 — SYNTHESIZE THE DOCUMENT
 ## STEP 12 — SYNTHESIZE THE DOCUMENT
 
 
-Generate the deliverable section by section. Translate headings to `LANG`.
-Tone: friendly, concrete, no jargon. One short paragraph per idea.
+Generate the deliverable as a tight 4-chapter structure: what was needed,
+what was done (lay summary), what the client must do, then technical
+details for the curious. Translate headings to `LANG`. Tone: friendly,
+concrete, no jargon. One short paragraph per idea.
+
+### Hard rules for this document
+
+1. **Never name internal tools or skill identifiers in chapters 1–3.**
+   Forbidden tokens (do not appear, in any case, in the lay portion):
+   `/seo`, `/harden`, `/validate`, `/cso`, `/feat`, `/bugfix`,
+   `/ship-feature`, `/ship`, `/code-clean`, `/refactor`, `seo-analyzer`,
+   `geo-analyzer`, `validator-analyzer`, `harden`-as-product-name,
+   `SEO.md`, `HARDEN.md`, `VALIDATE.md`, `CSO.md`, `MAX_ITERATIONS`,
+   `ALL_PASS`, `SCORE_*`. Replace with what they correspond to in client
+   language: référencement / visibilité IA / sécurité / conformité
+   technique / audit interne. Internal tool names may appear ONLY in
+   chapter 4 ("Détails techniques") inside the optional glossary.
+2. **Chapter 2 hard cap: 300 words max, zero technical jargon.** Plain
+   French (or plain English if `LANG=en`). No acronyms not already in
+   common usage (HTTPS is fine; CSP is not). Run `wc -w` against the
+   chapter body; if over 300, rewrite shorter.
+3. **Chapter 3 is action-only.** Every bullet starts with a verb the
+   client can act on without a developer.
+4. **Chapter 4 may use technical terms** (SEO, GEO, HSTS, CSP, etc.) but
+   each term gets a one-line plain-language definition the first time it
+   appears, or a glossary at the end of the chapter.
 
 
 ### Document structure
 ### Document structure
 
 
@@ -830,102 +870,78 @@ Tone: friendly, concrete, no jargon. One short paragraph per idea.
 > Ce document récapitule l'ensemble du travail réalisé sur votre projet
 > Ce document récapitule l'ensemble du travail réalisé sur votre projet
 > du JJ/MM/AAAA au JJ/MM/AAAA.
 > du JJ/MM/AAAA au JJ/MM/AAAA.
 
 
-## 1. En une minute
+## 1. Ce qu'il fallait faire (et pourquoi)
 
 
-[2-3 sentences. What is the project, what does it do, current state.]
+[Briefing + motivation. 100–180 words max. Two short paragraphs.
+- §1.1 (the brief): what the client wanted, in their own words if
+  possible. Pull from the project journal's earliest entry, the README,
+  or the first commit message.
+- §1.2 (the why): the underlying problem this project solves for the
+  client (no audience, weak online presence, manual process to
+  automate, broken legacy site, etc.). Concrete. Their reality, not
+  ours.
 
 
-## 2. Ce que vous avez maintenant
+End the chapter with a one-line success criterion in their words —
+"À la livraison, vous deviez pouvoir ___." If unknown, omit rather
+than invent.]
 
 
-[Bullet list of features as USER BENEFITS. Pull from journal + commit clusters.]
+## 2. Ce qui a été fait
 
 
-## 3. Comment on en est arrivé là
+[**HARD CAP: 300 words. ZERO technical jargon.** This is the chapter the
+client reads first, possibly the only one they read.
 
 
-[3 to 7 phases. For each: what was done, why it mattered. Plain phase names.]
+Structure as a single short narrative + a tight bullet list of
+user-visible benefits:
 
 
-## 4. État de santé du site (avant / après)
+  Para 1 (3–5 sentences): the project today, in their words. What it
+  looks like to a visitor, what the client can do with it. NOT what
+  technologies were used.
 
 
-[NEW SECTION — score table from STEP 8. SEO classique and GEO (IA) are
-shown on separate rows so the client sees both axes explicitly.]
+  Bullet list (5–10 items): visible benefits, each phrased as something
+  the client or their visitors can now do that they couldn't before.
+  Pattern: "Vos visiteurs peuvent ___" / "Vous pouvez ___" /
+  "Le site est maintenant ___".
 
 
-Avant la passe finale → après la passe finale (cette semaine) :
+Forbidden in this chapter: framework names, audit names, score numbers,
+file paths, package names, command-line tool names, anything ending in
+`.md`, `.json`, `.yaml`. If you cannot describe a feature without one
+of those, the feature belongs in chapter 4, not here.
 
 
-| Domaine                                  | Avant      | Après      | Statut |
-|------------------------------------------|-----------:|-----------:|:------:|
-| Référencement Google (SEO classique)     | <X.X>/20   | <Y.Y>/20   | ✅     |
-| Visibilité IA (GEO — ChatGPT, Perplexity)| <X.X>/20   | <Y.Y>/20   | ✅     |
-| Sécurité du site                         | <X.X>/20   | <Y.Y>/20   | ✅     |
-| Conformité technique (W3C)               | —          | <Z.Z>/20   | ✅     |
-
-[If LANG=en: "Site health (before / after)" with the same columns.
-Use these column labels: "Domain" / "Before" / "After" / "Status".
-Row labels: "Google search (classical SEO)", "AI visibility (GEO —
-ChatGPT, Perplexity)", "Site security", "Technical compliance (W3C)".]
-
-Plain explanation under the table:
-- **Référencement Google (SEO classique)** = comment Google, Bing et
-  les autres moteurs traditionnels trouvent et classent votre site.
-  C'est ce qui amène la majorité du trafic aujourd'hui.
-- **Visibilité IA (GEO)** = comment les moteurs de recherche par IA
-  (ChatGPT, Perplexity, Gemini, Google AI Overviews) lisent et citent
-  votre site. Trafic encore minoritaire mais en forte croissance —
-  votre site est maintenant prêt pour ce canal (llms.txt, données
-  structurées pour extraction IA, signaux d'entité).
-- **Sécurité** = protections contre les attaques courantes (en-têtes
-  HTTPS, anti-injection, etc.).
-- **Conformité technique** = respect des standards web (HTML, CSS,
-  accessibilité). Ouvert dans la plupart des navigateurs et lecteurs
-  d'écran sans bug.
-
-[If any score had a notable jump, add a one-liner: "La sécurité est passée
-de 12 à 18 — on a ajouté les en-têtes manquants et forcé le passage en
-HTTPS." Do the same for SEO and GEO independently if either jumped.]
-
-## 5. Les choix importants qu'on a faits
-
-[Vulgarize BDR entries. 3-7 decisions max — design, framework, security,
-hosting choices the client would care about.]
-
-## 6. Ce qu'on a appris en route (optionnel)
-
-[Only if learnings.md has client-relevant entries. 3-5 bullets max.]
-
-## 7. Ce qui reste à faire ou à surveiller
+After drafting, count words. Cap at 300. If over, cut paragraphs not
+bullets — bullets are the value-dense part.]
 
 
-[From blockers.md (open) + code TODOs. Plain description, urgency,
-trigger.]
+## 3. Ce qui vous reste à faire
 
 
-## 8. Comment utiliser le projet au quotidien
+[Action-only checklist for the client. Pull from: open `blockers.md`
+entries, ongoing-monitoring items, external platforms to claim,
+content updates only the client can make, deploy steps if self-hosted.
 
 
-[1-page guide for the client to USE what was delivered. URL, CMS, contact.]
-
-## 9. Ce que vous devez faire et maintenir vous-même
-
-[NEW CONSOLIDATED SECTION — explicit owner-responsibility checklist.
-Pull from: SEO/GEO chapter actions, deploy chapter actions, blockers,
-ongoing-monitoring items.
-
-Format as actionable checklist grouped by cadence:
+Format as a checklist grouped by cadence. Every line starts with a
+verb. Every line is something the client can do without a developer.
 
 
 ### Une fois (à faire dans les premières semaines)
 ### Une fois (à faire dans les premières semaines)
 - [ ] Réclamer la fiche Google Business Profile et la vérifier (lien : ...)
 - [ ] Réclamer la fiche Google Business Profile et la vérifier (lien : ...)
 - [ ] Compléter le profil Apple Business Connect (lien : ...)
 - [ ] Compléter le profil Apple Business Connect (lien : ...)
-- [ ] Vérifier la cohérence NAP (Nom / Adresse / Téléphone) sur toutes
-      les plateformes — voir tableau au §10
-- [ ] [Si self-host : configurer le certificat SSL (renouvellement auto Let's Encrypt)]
-- [ ] [Si self-host : programmer une sauvegarde quotidienne]
+- [ ] Vérifier la cohérence Nom / Adresse / Téléphone sur toutes les
+      plateformes — voir l'annexe à la fin du document
+- [ ] [Si vous gérez l'hébergement vous-même : configurer le certificat
+      de sécurité (renouvellement automatique recommandé)]
+- [ ] [Si vous gérez l'hébergement vous-même : programmer une sauvegarde
+      quotidienne]
 - [ ] Sauvegarder ce document hors du dépôt (PDF, email)
 - [ ] Sauvegarder ce document hors du dépôt (PDF, email)
 
 
 ### Mensuel
 ### Mensuel
-- [ ] Ajouter / mettre à jour 5 photos sur Google Business
-- [ ] Répondre aux avis Google (positifs et négatifs)
+- [ ] Ajouter ou mettre à jour 5 photos sur Google Business
+- [ ] Répondre aux avis Google (positifs et négatifs) sous 48 h
 - [ ] Vérifier que le site est toujours en ligne (test simple : ouvrir
 - [ ] Vérifier que le site est toujours en ligne (test simple : ouvrir
       l'URL depuis un autre appareil)
       l'URL depuis un autre appareil)
-- [ ] [Si CMS : mettre à jour les contenus saisonniers]
+- [ ] [Si système de gestion de contenu : mettre à jour les contenus
+      saisonniers]
 
 
 ### Trimestriel
 ### Trimestriel
 - [ ] Faire un test de visibilité IA : taper le nom du commerce dans
 - [ ] Faire un test de visibilité IA : taper le nom du commerce dans
       ChatGPT, Perplexity, Gemini. Noter ce qui s'affiche.
       ChatGPT, Perplexity, Gemini. Noter ce qui s'affiche.
-- [ ] Demander à 3-5 clients de laisser un avis Google
+- [ ] Demander à 3–5 clients de laisser un avis Google
 - [ ] Publier un post Google Business (offre, événement, actualité)
 - [ ] Publier un post Google Business (offre, événement, actualité)
 
 
 ### Annuel
 ### Annuel
@@ -934,25 +950,90 @@ Format as actionable checklist grouped by cadence:
 - [ ] Renouveler les noms de domaine
 - [ ] Renouveler les noms de domaine
 
 
 ### Quand quelque chose change dans la vie du commerce
 ### Quand quelque chose change dans la vie du commerce
-- [ ] Changement d'adresse / téléphone / horaires → modifier d'abord sur
-      Google Business, puis sur toutes les autres plateformes (la
-      cohérence est cruciale, voir §10)
+- [ ] Changement d'adresse, de téléphone ou d'horaires → modifier
+      d'abord sur Google Business, puis sur toutes les autres
+      plateformes (la cohérence est cruciale)
 
 
 [Adapt cadences to project type. For SaaS / non-local: replace
 [Adapt cadences to project type. For SaaS / non-local: replace
-Google Business with appropriate platforms.]
-```
+Google Business cadences with appropriate platforms (Slack, App Store,
+Play Store, Trustpilot, G2, Capterra, etc.). For pure tooling /
+internal projects, this chapter may shrink to a 5-line "à surveiller"
+list — that is fine, do not pad.]
+
+## 4. Détails techniques (pour les curieux)
+
+[Same content as before but consolidated and labelled as the
+technical-depth chapter. Internal tool names may appear here.
+The client is not required to read this chapter.]
+
+### 4.1 État de santé du site (avant / après)
+
+| Domaine                                  | Avant      | Après      | Statut |
+|------------------------------------------|-----------:|-----------:|:------:|
+| Référencement Google (recherche classique)| <X.X>/20   | <Y.Y>/20   | ✅     |
+| Visibilité IA (ChatGPT, Perplexity, Gemini)| <X.X>/20   | <Y.Y>/20   | ✅     |
+| Sécurité du site                          | <X.X>/20   | <Y.Y>/20   | ✅     |
+| Conformité technique                      | —          | <Z.Z>/20   | ✅     |
+
+[If LANG=en: "Site health (before / after)" with the same columns.
+Use these column labels: "Domain" / "Before" / "After" / "Status".
+Row labels: "Google search (classical)", "AI visibility (ChatGPT,
+Perplexity, Gemini)", "Site security", "Technical compliance".]
+
+Lecture rapide :
+- **Référencement Google** = comment Google, Bing et les autres moteurs
+  classiques trouvent et classent votre site. Majorité du trafic
+  aujourd'hui.
+- **Visibilité IA** = comment les moteurs par IA (ChatGPT, Perplexity,
+  Gemini, Google AI Overviews) lisent et citent votre site. Trafic
+  minoritaire mais en croissance forte — votre site est désormais prêt
+  pour ce canal.
+- **Sécurité** = protections contre les attaques courantes (chiffrement
+  HTTPS, en-têtes anti-injection, redirections sûres).
+- **Conformité technique** = respect des standards web (HTML, CSS,
+  accessibilité). Ouvert dans la plupart des navigateurs et lecteurs
+  d'écran sans bug.
+
+[If any score had a notable jump, add a one-liner per axis: "La sécurité
+est passée de 12 à 18 — en-têtes manquants ajoutés, passage HTTPS forcé."]
+
+### 4.2 Choix techniques importants
 
 
-## 10. [SEO/GEO manual chapter — web projects only — see STEP 13]
+[Vulgarize 3–7 BDR entries. Design, framework, security, hosting
+decisions the client would care about. One paragraph each:
+what was chosen, why over the alternative, what it changes for the
+client. Drop entries the client cannot act on or care about.]
 
 
-## 11. [Build & deploy chapter — only if Q1=Yes — see STEP 14]
+### 4.3 Comment on en est arrivé là (phases)
 
 
-## 12. Pour aller plus loin
+[3–7 phases. For each: what was done, why it mattered, in technical
+detail this time. Reference commit clusters from STEP 10. Plain phase
+names, not skill names.]
 
 
-[3-5 concrete suggestions. Phrase as opportunities.]
+### 4.4 Glossaire (optionnel)
 
 
-## Annexe — Détails techniques
+[Include only if at least 4 of the terms below appear in chapter 4.
+Format: term — one-line plain-language definition. Sort alphabetically.
+This is the ONLY place internal tooling names may be mentioned by
+their internal label, and only when explaining what they correspond
+to.]
 
 
-[Pointer for the technically curious — README, source repo, etc.]
+- **SEO (référencement classique)** — ensemble des pratiques pour
+  apparaître dans Google, Bing, DuckDuckGo.
+- **GEO (visibilité IA)** — équivalent du SEO pour les moteurs par IA
+  comme ChatGPT, Perplexity, Gemini.
+- **HSTS** — en-tête HTTP qui force la navigation en HTTPS.
+- **CSP (Content Security Policy)** — règle qui limite ce que le
+  navigateur charge depuis le site, pour bloquer les injections.
+- **WCAG** — standard d'accessibilité (AA = niveau recommandé).
+- **Schema.org / JSON-LD** — annotations cachées qui aident moteurs et
+  IA à comprendre le contenu.
+- **llms.txt** — fichier qui dit aux moteurs IA quel est le contenu
+  important du site.
+
+## 5. Annexe — Plateformes externes (web)
+
+## 6. Annexe — Build & déploiement (optionnel)
 
 
 ---
 ---
 
 
@@ -963,20 +1044,24 @@ des audits de santé. Pour toute question, contactez [contact].*
 ### Tone rules
 ### Tone rules
 
 
 1. Address the client directly ("votre site", "vous pouvez").
 1. Address the client directly ("votre site", "vous pouvez").
-2. Replace tech terms with user-facing equivalents.
-3. No abbreviations the client wouldn't use.
+2. Chapters 1–3: replace every tech term with a user-facing equivalent.
+3. No abbreviations the client wouldn't use (HTTPS yes, CSP no — unless
+   in chapter 4 with definition).
 4. Concrete numbers > adjectives.
 4. Concrete numbers > adjectives.
 5. Short paragraphs. Bullet lists for things you can count.
 5. Short paragraphs. Bullet lists for things you can count.
 6. **Score deltas explained in plain words**. Never just dump numbers.
 6. **Score deltas explained in plain words**. Never just dump numbers.
-7. **Owner-responsibility section is action-oriented**. Every line starts
-   with a verb. Every line is something the client can do without a dev.
+7. **Chapter 3 is action-oriented**. Every line starts with a verb.
+   Every line is something the client can do without a developer.
+8. **No skill-name leaks in chapters 1–3.** See "Hard rules" above.
 
 
 ---
 ---
 
 
 ## STEP 13 — SEO/GEO MANUAL CHECKLIST (web projects only)
 ## STEP 13 — SEO/GEO MANUAL CHECKLIST (web projects only)
 
 
 If `PROJECT_TYPE=web` AND `--skip-seo` NOT set, append this chapter
 If `PROJECT_TYPE=web` AND `--skip-seo` NOT set, append this chapter
-(numbered §10 in the doc).
+as **§5 Annexe — Plateformes externes** in the new 4-chapter structure
+(see STEP 12). Replace the §5 stub with the full content rendered from
+the resource file.
 
 
 Read the resource file:
 Read the resource file:
 `$HOME/.claude/skills/client-handover/checklists/seo-geo-manual.md`
 `$HOME/.claude/skills/client-handover/checklists/seo-geo-manual.md`
@@ -1027,7 +1112,9 @@ that are recurring belong in §9's cadence checklist.
 
 
 ## STEP 14 — BUILD & DEPLOY CHAPTER (only if Q1=Yes)
 ## STEP 14 — BUILD & DEPLOY CHAPTER (only if Q1=Yes)
 
 
-For each `DEPLOY_HINTS` match, generate a short subsection:
+If included, this becomes **§6 Annexe — Build & déploiement** in the new
+4-chapter structure (see STEP 12). For each `DEPLOY_HINTS` match,
+generate a short subsection:
 1. What this means (1 paragraph).
 1. What this means (1 paragraph).
 2. First-time setup (numbered steps + signup link).
 2. First-time setup (numbered steps + signup link).
 3. Day-to-day deploy (typical command / click sequence).
 3. Day-to-day deploy (typical command / click sequence).
@@ -1045,7 +1132,7 @@ For each: signup + 5-step deploy walkthrough.
 
 
 ---
 ---
 
 
-## STEP 15 — WRITE OUTPUT
+## STEP 15 — WRITE MARKDOWN OUTPUT
 
 
 Default output path: project root.
 Default output path: project root.
 - `LIVRAISON.md` if `LANG=fr`
 - `LIVRAISON.md` if `LANG=fr`
@@ -1058,30 +1145,125 @@ If a file at that path already exists, AskUserQuestion:
 
 
 Write the file with the `Write` tool.
 Write the file with the `Write` tool.
 
 
-Sanity check:
+Sanity checks (do them in this order, before STEP 16):
+
+```bash
+wc -l <output>                          # expect 250-800 lines
+grep -c "^## " <output>                 # expect 4-6 top-level chapters
+                                        #   §1, §2, §3, §4, [§5 web], [§6 deploy]
+```
+
+**Chapter 2 word-count gate.** Extract the body of `## 2. Ce qui a été fait`
+(or `## 2. What we did` if `LANG=en`) and run `wc -w` on it. **Hard cap:
+300 words.** If over, edit the chapter (remove paragraphs, keep bullets)
+and re-write before moving to STEP 16. Do not skip this gate — chapter 2
+is the part the client reads first.
+
+```bash
+awk '/^## 2\. /{flag=1; next} /^## 3\. /{flag=0} flag' "$OUTPUT" | wc -w
+# expected: ≤ 300
+```
+
+**Skill-name leak gate.** Forbidden tokens must NOT appear in chapters
+1–3 (chapter 4 may use them in the optional glossary):
+
+```bash
+awk '/^## 1\./{flag=1} /^## 4\./{flag=0} flag' "$OUTPUT" \
+  | grep -niE '/(seo|harden|validate|cso|feat|bugfix|ship-feature|ship|code-clean|refactor)\b|seo-analyzer|geo-analyzer|validator-analyzer|SEO\.md|HARDEN\.md|VALIDATE\.md|CSO\.md|MAX_ITERATIONS|ALL_PASS|SCORE_[A-Z_]+'
+# expected: no matches. Each match is a leak — rewrite the offending
+# chapter in client language before STEP 16.
+```
+
+If either gate fails, fix and re-write the markdown before continuing.
+
+---
+
+## STEP 16 — RENDER BRANDED HTML + PDF
+
+Always produce a branded `.html` next to the `.md`. Produce a branded
+`.pdf` when a PDF engine is available on the host. The file is the
+client-visible deliverable.
+
+### Inputs already known
+
+| Variable          | Source                                      |
+|-------------------|---------------------------------------------|
+| `OUTPUT_MD`       | path written in STEP 15                     |
+| `LANG`            | from STEP 1                                 |
+| `PROJECT_NAME`    | `PROJECT_ROOT` basename or `package.json` `name` |
+| `CLIENT_NAME`     | from journal first entry, README, or AskUserQuestion |
+| `PROJECT_PERIOD`  | `<first commit date> → <last commit date>` (DD/MM/YYYY) |
+| `PROJECT_URL`     | `DEPLOYED_URL` from STEP 6 (or `—` if none) |
+
+If `CLIENT_NAME` is unknown after best-effort detection, ask once with
+AskUserQuestion: `"Nom du client à afficher sur la couverture du PDF
+(ou laisser vide pour ne rien afficher)?"`. A blank answer becomes `—`.
+
+### Run the renderer
+
 ```bash
 ```bash
-wc -l <output>          # expect 200-800 lines
-grep -c "^## " <output> # expect 8-13 chapters (NEW: §4 health, §9 owner-resp)
+PROJECT_NAME="$PROJECT_NAME" \
+CLIENT_NAME="$CLIENT_NAME" \
+PROJECT_PERIOD="$PROJECT_PERIOD" \
+PROJECT_URL="$PROJECT_URL" \
+LANG="$LANG" \
+"$HOME/.claude/skills/client-handover/scripts/handover-to-pdf.sh" \
+  "$OUTPUT_MD"
 ```
 ```
 
 
+The renderer:
+1. Converts the markdown to HTML using the first available engine
+   (pandoc > python-markdown > `npx marked`).
+2. Wraps the body in the ZenQuality template (cover page + branded
+   typography Inter + Playfair Display, ZenQuality green palette
+   `#1A3A25 / #2D5A3D / #4A7C59 / #87A878`, cream page background
+   `#F5F0EB`).
+3. Embeds the ZenQuality logo (default: `https://zenquality.fr/logo-horizontal.svg`;
+   override with `LOGO_URL` env var to use a local file).
+4. Emits `LIVRAISON.html` (or `HANDOVER.html`) next to the `.md`.
+5. Tries PDF engines in order: weasyprint > wkhtmltopdf > chromium >
+   chromium-browser > google-chrome. First match writes
+   `LIVRAISON.pdf` (or `HANDOVER.pdf`).
+6. If no PDF engine is available, exits with code 2 and prints
+   install hints. The HTML file is still produced and viewable —
+   the user can "Print → Save as PDF" from any modern browser.
+
+### Exit code handling
+
+| `$?` | Meaning                                       | Action |
+|------|-----------------------------------------------|--------|
+| 0    | HTML and PDF written                          | continue to STEP 17 |
+| 2    | HTML written, no PDF engine on host           | continue to STEP 17 — final report mentions PDF as MISSING and lists install commands |
+| 1    | Fatal (bad args, unwritable dir, conv error)  | escalate to user with the script's stderr |
+
+### Re-rendering on overwrite (option B in STEP 15)
+
+If STEP 15 chose option B (`LIVRAISON-YYYY-MM-DD.md` versioned),
+the renderer produces matching `LIVRAISON-YYYY-MM-DD.html` and
+`LIVRAISON-YYYY-MM-DD.pdf`. Pass the versioned path as `$OUTPUT_MD`.
+
 ---
 ---
 
 
-## STEP 16 — FINAL REPORT
+## STEP 17 — FINAL REPORT
 
 
 Output to the user:
 Output to the user:
 
 
 ```
 ```
 DONE — ship-and-handover pipeline complete.
 DONE — ship-and-handover pipeline complete.
 
 
-OUTPUT: <path>
+OUTPUT:
+  Markdown:  <path-to-md>
+  HTML:      <path-to-html>
+  PDF:       <path-to-pdf>     (or: NOT GENERATED — see install hints below)
 LANGUAGE: fr | en
 LANGUAGE: fr | en
 PROJECT TYPE: web (local-business) | web | cli | library | mobile | other
 PROJECT TYPE: web (local-business) | web | cli | library | mobile | other
 COMMITS ANALYZED: <count> from <first date> to <last date>
 COMMITS ANALYZED: <count> from <first date> to <last date>
 
 
 PIPELINE RESULT (web):
 PIPELINE RESULT (web):
-  SEO       <BEFORE>/20 → <AFTER>/20  ✅ (iterations: <N>)
-  HARDEN    <BEFORE>/20 → <AFTER>/20  ✅ (iterations: <N>)
-  VALIDATE  —          → <AFTER>/20   ✅ (post-deploy)
+  SEO classique  <BEFORE>/20 → <AFTER>/20  ✅ (iterations: <N>)
+  GEO (IA)       <BEFORE>/20 → <AFTER>/20  ✅ (iterations: <N>)
+  HARDEN         <BEFORE>/20 → <AFTER>/20  ✅ (iterations: <N>)
+  VALIDATE       —          → <AFTER>/20   ✅ (post-deploy)
 
 
 PIPELINE RESULT (non-web):
 PIPELINE RESULT (non-web):
   CSO       <BEFORE>/20 → <AFTER>/20  ✅ (iterations: <N>)
   CSO       <BEFORE>/20 → <AFTER>/20  ✅ (iterations: <N>)
@@ -1092,23 +1274,29 @@ DECISIONS VULGARIZED: <count>
 BLOCKERS REMAINING: <count> (open)
 BLOCKERS REMAINING: <count> (open)
 
 
 DOC SECTIONS WRITTEN:
 DOC SECTIONS WRITTEN:
-  §1-3  Project recap
-  §4    Site health (before/after)            ← NEW
-  §5    Key decisions
-  §6    Lessons learned (optional)
-  §7    Open items / things to monitor
-  §8    Day-to-day usage
-  §9    Owner responsibilities checklist      ← NEW
-  §10   SEO/GEO manual chapter (web)
-  §11   Build & deploy chapter (only if requested)
-  §12   Pour aller plus loin
+  §1   Ce qu'il fallait faire (et pourquoi)
+  §2   Ce qui a été fait                  (≤ 300 mots, sans jargon)
+  §3   Ce qui vous reste à faire          (action checklist)
+  §4   Détails techniques                 (scores, choix, glossaire)
+  §5   Annexe — plateformes externes      (web only)
+  §6   Annexe — build & déploiement       (only if requested)
 
 
 Next steps for the user:
 Next steps for the user:
-1. Read the document end-to-end before sending — fill any
-   [À COMPLÉTER] / [À CONFIRMER] markers (especially NAP in §10).
-2. Save a copy outside the repo (PDF, email).
-3. Walk through §9 (owner responsibilities) with the client during the
-   handover meeting — it's the part they MUST act on.
+1. Open <path-to-pdf> (or the .html) — verify cover page, branding,
+   and that the score table renders. Adjust the .md if needed and
+   re-run STEP 16 to regenerate.
+2. Read the document end-to-end before sending — fill any
+   [À COMPLÉTER] / [À CONFIRMER] markers (NAP in §5 especially).
+3. Save a copy outside the repo (the .pdf is already client-ready).
+4. Walk through §3 (ce qui vous reste à faire) with the client
+   during the handover meeting — that's the part they MUST act on.
+
+[If PDF was NOT generated, append:]
+PDF NOT GENERATED — no PDF engine on this host. Install one of:
+  - weasyprint   pip install --user weasyprint   (or: pipx install weasyprint)
+  - wkhtmltopdf  apt install wkhtmltopdf
+  - chromium     apt install chromium-browser
+Then re-run only STEP 16 (the .md does not need to change).
 ```
 ```
 
 
 If anything was skipped or uncertain, list under `CONCERNS:`.
 If anything was skipped or uncertain, list under `CONCERNS:`.

+ 24 - 18
skills/client-handover/SKILL.md

@@ -3,17 +3,22 @@ name: client-handover
 description: |
 description: |
   Final ship-and-handover orchestrator. End-to-end pipeline that hardens the
   Final ship-and-handover orchestrator. End-to-end pipeline that hardens the
   project, commits, pauses for deploy, validates the live site, and only then
   project, commits, pauses for deploy, validates the live site, and only then
-  generates the non-technical client deliverable (LIVRAISON.md / HANDOVER.md).
-  Pipeline: (1) /seo (SEO+GEO) and /harden run in parallel with auto-fix loops
-  until each score ≥17/20, (2) /commit-change + push if changes made, (3) pause
-  to tell user what to deploy and wait for confirmation, (4) /validate against
-  the live site, (5) per-audit gate ≥17/20 — stop and analyze if any below,
-  (6) write client doc with before/after score table and explicit
-  owner-maintenance checklist. Reads git history + .claude/memory/ registries.
-  For local-business projects, appends manual SEO/GEO platform checklist (NAP
-  consistency across Google Business, Pages Jaunes, Yelp, Facebook, Instagram,
-  TikTok, Apple Maps, Bing Places, TripAdvisor, etc.). Asks whether to include
-  build/deploy chapter.
+  generates the non-technical client deliverable as Markdown + branded HTML +
+  PDF (ZenQuality identity: green palette, Inter + Playfair Display fonts,
+  cover page with logo and tagline). The deliverable uses a 4-chapter
+  structure: §1 what was needed and why, §2 what was done (≤300 words, zero
+  jargon, no internal tool/skill names), §3 what the client must do (action
+  checklist), §4 technical details for the curious (scores, key choices,
+  glossary). Pipeline: (1) /seo (SEO+GEO) and /harden run in parallel with
+  auto-fix loops until each score ≥17/20, (2) /commit-change + push if
+  changes made, (3) pause to tell user what to deploy and wait for
+  confirmation, (4) /validate against the live site, (5) per-axis gate
+  ≥17/20 — stop and analyze if any below, (6) write client doc + render
+  branded HTML/PDF. Reads git history + .claude/memory/ registries. For
+  local-business projects, appends manual SEO/GEO platform checklist (NAP
+  consistency across Google Business, Pages Jaunes, Yelp, Facebook,
+  Instagram, TikTok, Apple Maps, Bing Places, TripAdvisor, etc.). Asks
+  whether to include build/deploy chapter.
   Trigger: "client handover", "compte rendu client", "livraison client",
   Trigger: "client handover", "compte rendu client", "livraison client",
   "synthese projet", "rapport client", "deliverable", "summary for client",
   "synthese projet", "rapport client", "deliverable", "summary for client",
   "recap projet", "handover doc", "livrable", "ship and handover",
   "recap projet", "handover doc", "livrable", "ship and handover",
@@ -51,13 +56,14 @@ The agent runs a **ship-and-handover pipeline** with explicit gates:
 5. **DEPLOY PAUSE** — List exact deploy artifacts: changed files since baseline, deploy hints from project (vercel.json, netlify.toml, Dockerfile, .github/workflows/deploy.yml, etc.), and the deploy process in plain words. Use AskUserQuestion: "Deploy done? (Yes / Not yet / Skip validate)". Block until Yes or Skip.
 5. **DEPLOY PAUSE** — List exact deploy artifacts: changed files since baseline, deploy hints from project (vercel.json, netlify.toml, Dockerfile, .github/workflows/deploy.yml, etc.), and the deploy process in plain words. Use AskUserQuestion: "Deploy done? (Yes / Not yet / Skip validate)". Block until Yes or Skip.
 6. **/validate (live site)** — Run validator-analyzer against the deployed URL. Capture `SCORE_VALIDATE`.
 6. **/validate (live site)** — Run validator-analyzer against the deployed URL. Capture `SCORE_VALIDATE`.
 7. **GATE — per-axis threshold ≥17/20** — Compute final `SCORE_*_AFTER` for SEO classique, GEO (IA), HARDEN, VALIDATE. If ANY < 17/20: STOP. Generate `.claude/audits/HANDOVER-ROADMAP.md` with prioritized analysis of what's blocking each below-threshold axis. Do NOT write the client deliverable. Report to user.
 7. **GATE — per-axis threshold ≥17/20** — Compute final `SCORE_*_AFTER` for SEO classique, GEO (IA), HARDEN, VALIDATE. If ANY < 17/20: STOP. Generate `.claude/audits/HANDOVER-ROADMAP.md` with prioritized analysis of what's blocking each below-threshold axis. Do NOT write the client deliverable. Report to user.
-8. **DOC GENERATION (only if all scores ≥17/20)** — Read `.claude/memory/` registries + full git history. Ask whether to include build/deploy chapter. Synthesize concise client deliverable with:
-   - Before/after score table with SEO classique and GEO (IA) on separate rows, plus HARDEN and VALIDATE — values + delta. SEO classique, GEO, HARDEN and VALIDATE are gated independently — each must reach ≥17/20 for the pipeline to pass.
-   - Plain-language summary of all changes since first commit.
-   - **Owner responsibilities** section: explicit checklist of what the client must do / maintain (SEO platforms, content updates, monitoring, deploy if self-hosted).
-   - Optional build/deploy chapter.
-   - For web projects with local-business signals: manual SEO/GEO platform checklist with registration links.
-9. **OUTPUT** — Write to `LIVRAISON.md` (fr) or `HANDOVER.md` (en) at project root.
+8. **DOC GENERATION (only if all scores ≥17/20)** — Read `.claude/memory/` registries + full git history. Ask whether to include build/deploy chapter. Synthesize the client deliverable using the 4-chapter structure:
+   - **§1 Ce qu'il fallait faire (et pourquoi)** — brief + motivation, 100–180 words.
+   - **§2 Ce qui a été fait** — lay summary, **≤300 words, zero technical jargon**, **no internal tool/skill names** (no `/seo`, `/harden`, `/validate`, `seo-analyzer`, etc. — replace with concept names: référencement / sécurité / conformité technique). Forbidden-token grep gate runs before write.
+   - **§3 Ce qui vous reste à faire** — action-only checklist grouped by cadence (one-time / monthly / quarterly / yearly / when something changes).
+   - **§4 Détails techniques (pour les curieux)** — score table (SEO classique + GEO + sécurité + conformité, before/after, gated independently at ≥17/20), vulgarized BDR decisions, phases with technical detail, optional glossary.
+   - **§5 Annexe — plateformes externes** (web/local-business only).
+   - **§6 Annexe — build & déploiement** (only if requested).
+9. **RENDER** — Write `LIVRAISON.md` (fr) or `HANDOVER.md` (en) at project root, then run `scripts/handover-to-pdf.sh` to produce the matching branded `.html` (always) and `.pdf` (when a PDF engine is on the host: weasyprint > wkhtmltopdf > chromium). HTML/PDF use the ZenQuality cover page, green palette, Inter + Playfair Display typography, running header/footer with project name + page numbers.
 
 
 Flags:
 Flags:
 - `--skip-fix-loop` — run baseline audits once, skip auto-fix iterations.
 - `--skip-fix-loop` — run baseline audits once, skip auto-fix iterations.

+ 43 - 0
skills/client-handover/resources/branding/zenquality-template.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="{{LANG}}">
+<head>
+  <meta charset="UTF-8">
+  <title>{{TITLE}}</title>
+  <style>
+{{CSS}}
+  </style>
+</head>
+<body>
+
+<section class="cover">
+  <div class="cover-header">
+    <img class="cover-logo" src="{{LOGO_URL}}" alt="ZenQuality">
+    <div class="cover-tagline">La sérénité numérique,<br>la qualité en plus</div>
+  </div>
+
+  <div class="cover-body">
+    <div class="cover-eyebrow">{{EYEBROW}}</div>
+    <h1 class="cover-title">{{COVER_TITLE}}</h1>
+    <p class="cover-subtitle">{{COVER_SUBTITLE}}</p>
+
+    <div class="cover-meta">
+      <div><strong>{{LABEL_CLIENT}}</strong> {{CLIENT_NAME}}</div>
+      <div><strong>{{LABEL_PROJECT}}</strong> {{PROJECT_NAME}}</div>
+      <div><strong>{{LABEL_DATE}}</strong> {{DATE_HUMAN}}</div>
+      <div><strong>{{LABEL_PERIOD}}</strong> {{PROJECT_PERIOD}}</div>
+      <div><strong>{{LABEL_URL}}</strong> <a href="{{PROJECT_URL}}">{{PROJECT_URL}}</a></div>
+    </div>
+  </div>
+
+  <div class="cover-footer">
+    <span>ZenQuality — {{LABEL_PREPARED_BY}}</span>
+    <a href="https://zenquality.fr">zenquality.fr</a>
+  </div>
+</section>
+
+<main class="content" data-title="{{TITLE}}">
+{{CONTENT}}
+</main>
+
+</body>
+</html>

+ 438 - 0
skills/client-handover/resources/branding/zenquality.css

@@ -0,0 +1,438 @@
+/*
+ * ZenQuality — client handover stylesheet
+ * Used to render LIVRAISON.md / HANDOVER.md as a branded HTML/PDF.
+ * Source brand tokens: zenquality.fr (CSS custom properties extracted from
+ * the live site) — Inter (body) + Playfair Display (headings), green palette.
+ */
+
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Playfair+Display:wght@400;600;700&display=swap');
+
+:root {
+  --green-dark: #1A3A25;
+  --green-forest: #2D5A3D;
+  --green-moss: #4A7C59;
+  --green-sage: #87A878;
+  --black-deep: #0A0A0A;
+  --black-soft: #1A1A1A;
+  --gray-dark: #2A2A2A;
+  --gray-mid: #666666;
+  --gray-light: #B0B0B0;
+  --white-cream: #F5F0EB;
+  --white-pure: #FFFFFF;
+
+  --status-ok: #2D5A3D;
+  --status-warn: #b58900;
+  --status-fail: #a83232;
+}
+
+@page {
+  size: A4;
+  margin: 22mm 18mm 22mm 18mm;
+  @top-right {
+    content: string(doctitle);
+    font-family: 'Inter', sans-serif;
+    font-size: 8.5pt;
+    color: var(--green-moss);
+  }
+  @bottom-right {
+    content: counter(page) " / " counter(pages);
+    font-family: 'Inter', sans-serif;
+    font-size: 8.5pt;
+    color: var(--gray-mid);
+  }
+  @bottom-left {
+    content: "ZenQuality — zenquality.fr";
+    font-family: 'Inter', sans-serif;
+    font-size: 8.5pt;
+    color: var(--gray-mid);
+  }
+}
+
+@page :first {
+  margin: 0;
+  @top-right { content: ""; }
+  @bottom-right { content: ""; }
+  @bottom-left { content: ""; }
+}
+
+* { box-sizing: border-box; }
+
+html, body {
+  margin: 0;
+  padding: 0;
+  font-family: 'Inter', system-ui, -apple-system, sans-serif;
+  font-size: 10.5pt;
+  line-height: 1.6;
+  color: var(--black-deep);
+  background: var(--white-pure);
+}
+
+/* ============ COVER PAGE ============ */
+.cover {
+  page-break-after: always;
+  height: 297mm;
+  width: 210mm;
+  padding: 35mm 22mm 22mm 22mm;
+  background:
+    radial-gradient(ellipse at top right, rgba(135, 168, 120, 0.18) 0%, transparent 55%),
+    radial-gradient(ellipse at bottom left, rgba(74, 124, 89, 0.10) 0%, transparent 55%),
+    var(--white-cream);
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  page: cover;
+}
+
+.cover::before {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 8mm;
+  background: linear-gradient(90deg, var(--green-dark), var(--green-forest), var(--green-moss));
+}
+
+.cover-header {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+}
+
+.cover-logo {
+  width: 55mm;
+  height: auto;
+  max-height: 30mm;
+  object-fit: contain;
+}
+
+.cover-tagline {
+  font-family: 'Playfair Display', Georgia, serif;
+  font-size: 10pt;
+  font-style: italic;
+  color: var(--green-forest);
+  text-align: right;
+  max-width: 70mm;
+  margin-top: 6mm;
+}
+
+.cover-body {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  margin: -10mm 0 0 0;
+}
+
+.cover-eyebrow {
+  font-family: 'Inter', sans-serif;
+  font-size: 9pt;
+  font-weight: 600;
+  text-transform: uppercase;
+  letter-spacing: 0.18em;
+  color: var(--green-moss);
+  margin-bottom: 6mm;
+}
+
+.cover-title {
+  font-family: 'Playfair Display', Georgia, serif;
+  font-size: 34pt;
+  font-weight: 700;
+  color: var(--green-dark);
+  line-height: 1.1;
+  margin: 0 0 6mm 0;
+  letter-spacing: -0.015em;
+}
+
+.cover-subtitle {
+  font-family: 'Playfair Display', Georgia, serif;
+  font-size: 16pt;
+  font-weight: 400;
+  font-style: italic;
+  color: var(--green-forest);
+  margin: 0 0 18mm 0;
+  max-width: 140mm;
+}
+
+.cover-meta {
+  font-family: 'Inter', sans-serif;
+  font-size: 10.5pt;
+  color: var(--black-soft);
+  line-height: 1.9;
+  border-left: 2px solid var(--green-moss);
+  padding-left: 5mm;
+}
+
+.cover-meta strong {
+  color: var(--green-dark);
+  font-weight: 600;
+  display: inline-block;
+  min-width: 25mm;
+}
+
+.cover-footer {
+  font-family: 'Inter', sans-serif;
+  font-size: 9pt;
+  color: var(--gray-mid);
+  border-top: 1px solid var(--green-sage);
+  padding-top: 5mm;
+  display: flex;
+  justify-content: space-between;
+}
+
+.cover-footer a {
+  color: var(--green-forest);
+  text-decoration: none;
+  font-weight: 500;
+}
+
+.cover-footer a:hover { color: var(--green-dark); }
+
+/* ============ DOCUMENT BODY ============ */
+.content {
+  string-set: doctitle attr(data-title);
+}
+
+h1 {
+  font-family: 'Playfair Display', Georgia, serif;
+  font-size: 22pt;
+  font-weight: 700;
+  color: var(--green-dark);
+  margin: 0 0 6mm 0;
+  page-break-after: avoid;
+  string-set: doctitle content();
+}
+
+h2 {
+  font-family: 'Playfair Display', Georgia, serif;
+  font-size: 17pt;
+  font-weight: 600;
+  color: var(--green-forest);
+  margin: 12mm 0 4mm 0;
+  padding-bottom: 2.5mm;
+  border-bottom: 2px solid var(--green-sage);
+  page-break-before: always;
+  page-break-after: avoid;
+}
+
+.content > h2:first-of-type,
+h2.no-break,
+h2.continue {
+  page-break-before: auto;
+}
+
+h3 {
+  font-family: 'Playfair Display', Georgia, serif;
+  font-size: 13.5pt;
+  font-weight: 600;
+  color: var(--green-forest);
+  margin: 8mm 0 3mm 0;
+  page-break-after: avoid;
+}
+
+h4 {
+  font-family: 'Inter', sans-serif;
+  font-size: 10pt;
+  font-weight: 600;
+  color: var(--green-moss);
+  margin: 6mm 0 2mm 0;
+  text-transform: uppercase;
+  letter-spacing: 0.06em;
+  page-break-after: avoid;
+}
+
+p { margin: 0 0 3mm 0; }
+p, li { orphans: 3; widows: 3; }
+
+ul, ol { margin: 0 0 3mm 0; padding-left: 6mm; }
+ul li, ol li { margin: 0 0 1.5mm 0; }
+
+ul li::marker { color: var(--green-moss); }
+ol li::marker { color: var(--green-moss); font-weight: 600; }
+
+strong { color: var(--green-dark); font-weight: 600; }
+
+em { color: var(--green-forest); font-style: italic; }
+
+blockquote {
+  border-left: 3px solid var(--green-moss);
+  padding: 3mm 5mm;
+  margin: 4mm 0;
+  background: var(--white-cream);
+  color: var(--gray-dark);
+  font-style: italic;
+  page-break-inside: avoid;
+}
+
+blockquote p:last-child { margin-bottom: 0; }
+
+a { color: var(--green-forest); text-decoration: underline; text-decoration-thickness: 0.5pt; text-underline-offset: 1.5pt; }
+a:hover { color: var(--green-dark); }
+
+code {
+  font-family: 'JetBrains Mono', 'Fira Code', Menlo, monospace;
+  font-size: 9pt;
+  background: var(--white-cream);
+  padding: 0.5mm 1.5mm;
+  border-radius: 1mm;
+  color: var(--green-dark);
+}
+
+pre {
+  background: var(--white-cream);
+  padding: 4mm 5mm;
+  border-radius: 1.5mm;
+  border-left: 3px solid var(--green-moss);
+  font-size: 8.5pt;
+  line-height: 1.45;
+  white-space: pre-wrap;
+  word-wrap: break-word;
+  page-break-inside: avoid;
+  margin: 4mm 0;
+}
+
+pre code { background: none; padding: 0; color: var(--black-deep); font-size: inherit; }
+
+/* ============ TABLES ============ */
+table {
+  width: 100%;
+  border-collapse: collapse;
+  margin: 4mm 0;
+  font-size: 9.5pt;
+  page-break-inside: avoid;
+}
+
+th {
+  font-family: 'Inter', sans-serif;
+  background: var(--green-forest);
+  color: var(--white-pure);
+  text-align: left;
+  padding: 2.5mm 3mm;
+  font-weight: 600;
+  font-size: 9pt;
+  text-transform: uppercase;
+  letter-spacing: 0.04em;
+  border-bottom: 0;
+}
+
+td {
+  padding: 2.5mm 3mm;
+  border-bottom: 1px solid var(--green-sage);
+  vertical-align: top;
+}
+
+tr:nth-child(even) td { background: rgba(245, 240, 235, 0.55); }
+
+/* Numeric / status cols of score tables auto-detected via header text */
+table th:nth-child(2),
+table th:nth-child(3),
+table th:nth-child(4),
+table td:nth-child(2),
+table td:nth-child(3),
+table td:nth-child(4) {
+  text-align: right;
+  font-variant-numeric: tabular-nums;
+}
+
+table th:last-child,
+table td:last-child {
+  text-align: center;
+}
+
+/* ============ CHECKLISTS ============ */
+ul.checklist,
+ul.task-list {
+  list-style: none;
+  padding-left: 0;
+}
+
+ul.checklist li,
+ul.task-list li {
+  padding-left: 8mm;
+  position: relative;
+  margin-bottom: 2.5mm;
+}
+
+ul.checklist li::before,
+ul.task-list li::before,
+li input[type="checkbox"] + *,
+li.task-list-item::before {
+  content: "☐";
+  position: absolute;
+  left: 0;
+  color: var(--green-moss);
+  font-size: 12pt;
+  line-height: 1;
+}
+
+input[type="checkbox"] {
+  display: none;
+}
+
+input[type="checkbox"]:checked + label::before {
+  content: "☑";
+  color: var(--green-forest);
+}
+
+/* ============ CALLOUTS ============ */
+.callout {
+  padding: 4mm 6mm;
+  margin: 4mm 0;
+  border-radius: 2mm;
+  page-break-inside: avoid;
+  font-size: 10pt;
+}
+
+.callout.info {
+  background: var(--white-cream);
+  border-left: 4px solid var(--green-moss);
+}
+
+.callout.warn {
+  background: #fdf6e3;
+  border-left: 4px solid var(--status-warn);
+}
+
+.callout.success {
+  background: rgba(135, 168, 120, 0.14);
+  border-left: 4px solid var(--green-forest);
+}
+
+.callout-title {
+  font-family: 'Inter', sans-serif;
+  font-weight: 600;
+  font-size: 10pt;
+  color: var(--green-dark);
+  margin-bottom: 2mm;
+  text-transform: uppercase;
+  letter-spacing: 0.04em;
+}
+
+/* ============ SECTION DIVIDERS ============ */
+hr {
+  border: none;
+  border-top: 1px solid var(--green-sage);
+  margin: 8mm 0;
+}
+
+/* ============ STATUS PILLS (used by text replacement) ============ */
+.status-ok    { color: var(--status-ok);   font-weight: 600; }
+.status-warn  { color: var(--status-warn); font-weight: 600; }
+.status-fail  { color: var(--status-fail); font-weight: 600; }
+
+/* ============ LINK BEHAVIOR IN PRINT ============ */
+@media print {
+  a[href^="http"]::after {
+    content: " (" attr(href) ")";
+    font-size: 7.5pt;
+    color: var(--gray-mid);
+    font-style: italic;
+    font-weight: 400;
+  }
+  a[href^="#"]::after,
+  a[href^="mailto:"]::after,
+  a[href^="tel:"]::after,
+  .cover a::after,
+  table a::after { content: ""; }
+}

+ 265 - 0
skills/client-handover/scripts/handover-to-pdf.sh

@@ -0,0 +1,265 @@
+#!/usr/bin/env bash
+#
+# handover-to-pdf.sh
+# ------------------
+# Renders a client-handover Markdown report (LIVRAISON.md / HANDOVER.md)
+# into a branded HTML and (when a converter is available) a PDF using
+# ZenQuality brand styling.
+#
+# Inputs:
+#   $1  Path to the source Markdown file (required)
+#
+# Optional environment variables:
+#   PROJECT_NAME   Displayed on the cover and as PDF page header.
+#                  Defaults to the source filename.
+#   CLIENT_NAME    Displayed on the cover. Defaults to "—".
+#   PROJECT_PERIOD Displayed on the cover (e.g. "01/01/2026 → 31/03/2026").
+#                  Defaults to "—".
+#   PROJECT_URL    Displayed on the cover. Defaults to "—".
+#   LANG           "fr" (default) or "en". Drives cover labels.
+#   COVER_TITLE    Defaults to PROJECT_NAME.
+#   COVER_SUBTITLE Defaults to "Compte rendu de livraison" (fr) /
+#                  "Project handover recap" (en).
+#   EYEBROW        Eyebrow line above the title. Defaults to
+#                  "Livraison" / "Handover".
+#   LOGO_URL       Logo URL or local path. Defaults to a remote
+#                  ZenQuality logo (no offline fallback).
+#   BRANDING_DIR   Override branding-asset directory. Defaults to the
+#                  resources/branding/ folder next to this script.
+#
+# Behaviour:
+#   1. Convert the Markdown body to HTML.
+#   2. Wrap it in the ZenQuality template (cover + branded body).
+#   3. Convert that HTML into a PDF using the first available engine:
+#        weasyprint > wkhtmltopdf > chromium > headless Chrome
+#   4. Always keep the .html file next to the .md.
+#   5. If no PDF engine is available, exit with code 2 and a clear
+#      message — never fail silently.
+#
+# Exit codes:
+#   0  HTML and PDF written successfully.
+#   1  Fatal error (bad arguments, missing files, conversion error).
+#   2  HTML written but no PDF engine available — manual print needed.
+
+set -euo pipefail
+
+# ---------------------------- CLI ----------------------------------
+
+if [ "$#" -lt 1 ]; then
+  echo "usage: handover-to-pdf.sh <markdown-file>" >&2
+  exit 1
+fi
+
+SRC_MD="$1"
+
+if [ ! -f "$SRC_MD" ]; then
+  echo "error: markdown file not found: $SRC_MD" >&2
+  exit 1
+fi
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+DEFAULT_BRANDING_DIR="$SCRIPT_DIR/../resources/branding"
+BRANDING_DIR="${BRANDING_DIR:-$DEFAULT_BRANDING_DIR}"
+
+if [ ! -f "$BRANDING_DIR/zenquality.css" ] || [ ! -f "$BRANDING_DIR/zenquality-template.html" ]; then
+  echo "error: branding assets missing under $BRANDING_DIR" >&2
+  echo "       expected: zenquality.css + zenquality-template.html" >&2
+  exit 1
+fi
+
+OUT_DIR="$(cd "$(dirname "$SRC_MD")" && pwd)"
+BASE="$(basename "$SRC_MD" .md)"
+OUT_HTML="$OUT_DIR/$BASE.html"
+OUT_PDF="$OUT_DIR/$BASE.pdf"
+
+LANG_CODE="${LANG:-fr}"
+case "$LANG_CODE" in
+  en|EN|en_*|en-*)
+    LANG_CODE="en"
+    EYEBROW="${EYEBROW:-Project handover}"
+    DEFAULT_SUBTITLE="Project handover recap"
+    LABEL_CLIENT="Client"
+    LABEL_PROJECT="Project"
+    LABEL_DATE="Issued"
+    LABEL_PERIOD="Period"
+    LABEL_URL="Website"
+    LABEL_PREPARED_BY="prepared for the client"
+    ;;
+  *)
+    LANG_CODE="fr"
+    EYEBROW="${EYEBROW:-Livraison}"
+    DEFAULT_SUBTITLE="Compte rendu de livraison"
+    LABEL_CLIENT="Client"
+    LABEL_PROJECT="Projet"
+    LABEL_DATE="Date d'émission"
+    LABEL_PERIOD="Période"
+    LABEL_URL="Site"
+    LABEL_PREPARED_BY="préparé pour le client"
+    ;;
+esac
+
+PROJECT_NAME_RESOLVED="${PROJECT_NAME:-$BASE}"
+CLIENT_NAME_RESOLVED="${CLIENT_NAME:-—}"
+PROJECT_PERIOD_RESOLVED="${PROJECT_PERIOD:-—}"
+PROJECT_URL_RESOLVED="${PROJECT_URL:-—}"
+COVER_TITLE_RESOLVED="${COVER_TITLE:-$PROJECT_NAME_RESOLVED}"
+COVER_SUBTITLE_RESOLVED="${COVER_SUBTITLE:-$DEFAULT_SUBTITLE}"
+LOGO_URL_RESOLVED="${LOGO_URL:-https://zenquality.fr/logo-horizontal.svg}"
+
+if command -v date >/dev/null 2>&1; then
+  if [ "$LANG_CODE" = "fr" ]; then
+    DATE_HUMAN="$(LC_ALL=fr_FR.UTF-8 date "+%d %B %Y" 2>/dev/null || date "+%Y-%m-%d")"
+  else
+    DATE_HUMAN="$(LC_ALL=en_US.UTF-8 date "+%d %B %Y" 2>/dev/null || date "+%Y-%m-%d")"
+  fi
+else
+  DATE_HUMAN="$(date "+%Y-%m-%d")"
+fi
+
+# ---------------------------- MD -> HTML ---------------------------
+
+md_to_html_body() {
+  local src="$1"
+  if command -v pandoc >/dev/null 2>&1; then
+    pandoc --from=gfm --to=html5 --no-highlight "$src"
+    return
+  fi
+  if command -v python3 >/dev/null 2>&1 && python3 -c "import markdown" >/dev/null 2>&1; then
+    python3 -c "
+import sys, markdown
+src = open(sys.argv[1], encoding='utf-8').read()
+print(markdown.markdown(
+    src,
+    extensions=['extra', 'tables', 'sane_lists', 'toc'],
+))" "$src"
+    return
+  fi
+  if command -v npx >/dev/null 2>&1; then
+    npx --yes marked < "$src"
+    return
+  fi
+  echo "error: no Markdown converter available (need pandoc, python3+markdown, or npx)" >&2
+  exit 1
+}
+
+BODY_HTML="$(md_to_html_body "$SRC_MD")"
+
+# ---------------------------- WRAP HTML ----------------------------
+
+CSS_CONTENT="$(cat "$BRANDING_DIR/zenquality.css")"
+
+render_template() {
+  # Read template path from $1, output the substituted HTML on stdout.
+  # Substitution variables are pulled from HQ_* environment variables.
+  HQ_TEMPLATE_PATH="$1" python3 <<'PY'
+import os, sys
+path = os.environ["HQ_TEMPLATE_PATH"]
+with open(path, encoding="utf-8") as f:
+    template = f.read()
+mapping = {
+    "{{LANG}}":              os.environ.get("HQ_LANG", "fr"),
+    "{{TITLE}}":             os.environ.get("HQ_TITLE", ""),
+    "{{CSS}}":               os.environ.get("HQ_CSS", ""),
+    "{{LOGO_URL}}":          os.environ.get("HQ_LOGO_URL", ""),
+    "{{EYEBROW}}":           os.environ.get("HQ_EYEBROW", ""),
+    "{{COVER_TITLE}}":       os.environ.get("HQ_COVER_TITLE", ""),
+    "{{COVER_SUBTITLE}}":    os.environ.get("HQ_COVER_SUBTITLE", ""),
+    "{{CLIENT_NAME}}":       os.environ.get("HQ_CLIENT_NAME", "—"),
+    "{{PROJECT_NAME}}":      os.environ.get("HQ_PROJECT_NAME", ""),
+    "{{DATE_HUMAN}}":        os.environ.get("HQ_DATE_HUMAN", ""),
+    "{{PROJECT_PERIOD}}":    os.environ.get("HQ_PROJECT_PERIOD", "—"),
+    "{{PROJECT_URL}}":       os.environ.get("HQ_PROJECT_URL", "—"),
+    "{{LABEL_CLIENT}}":      os.environ.get("HQ_LABEL_CLIENT", ""),
+    "{{LABEL_PROJECT}}":     os.environ.get("HQ_LABEL_PROJECT", ""),
+    "{{LABEL_DATE}}":        os.environ.get("HQ_LABEL_DATE", ""),
+    "{{LABEL_PERIOD}}":      os.environ.get("HQ_LABEL_PERIOD", ""),
+    "{{LABEL_URL}}":         os.environ.get("HQ_LABEL_URL", ""),
+    "{{LABEL_PREPARED_BY}}": os.environ.get("HQ_LABEL_PREPARED_BY", ""),
+    "{{CONTENT}}":           os.environ.get("HQ_CONTENT", ""),
+}
+for k, v in mapping.items():
+    template = template.replace(k, v)
+sys.stdout.write(template)
+PY
+}
+
+export HQ_LANG="$LANG_CODE"
+export HQ_TITLE="$COVER_TITLE_RESOLVED"
+export HQ_CSS="$CSS_CONTENT"
+export HQ_LOGO_URL="$LOGO_URL_RESOLVED"
+export HQ_EYEBROW="$EYEBROW"
+export HQ_COVER_TITLE="$COVER_TITLE_RESOLVED"
+export HQ_COVER_SUBTITLE="$COVER_SUBTITLE_RESOLVED"
+export HQ_CLIENT_NAME="$CLIENT_NAME_RESOLVED"
+export HQ_PROJECT_NAME="$PROJECT_NAME_RESOLVED"
+export HQ_DATE_HUMAN="$DATE_HUMAN"
+export HQ_PROJECT_PERIOD="$PROJECT_PERIOD_RESOLVED"
+export HQ_PROJECT_URL="$PROJECT_URL_RESOLVED"
+export HQ_LABEL_CLIENT="$LABEL_CLIENT"
+export HQ_LABEL_PROJECT="$LABEL_PROJECT"
+export HQ_LABEL_DATE="$LABEL_DATE"
+export HQ_LABEL_PERIOD="$LABEL_PERIOD"
+export HQ_LABEL_URL="$LABEL_URL"
+export HQ_LABEL_PREPARED_BY="$LABEL_PREPARED_BY"
+export HQ_CONTENT="$BODY_HTML"
+
+render_template "$BRANDING_DIR/zenquality-template.html" > "$OUT_HTML"
+
+echo "wrote: $OUT_HTML"
+
+# ---------------------------- HTML -> PDF --------------------------
+
+PDF_ENGINE=""
+PDF_REASON=""
+
+if command -v weasyprint >/dev/null 2>&1; then
+  PDF_ENGINE="weasyprint"
+elif command -v wkhtmltopdf >/dev/null 2>&1; then
+  PDF_ENGINE="wkhtmltopdf"
+elif command -v chromium >/dev/null 2>&1; then
+  PDF_ENGINE="chromium"
+elif command -v chromium-browser >/dev/null 2>&1; then
+  PDF_ENGINE="chromium-browser"
+elif command -v google-chrome >/dev/null 2>&1; then
+  PDF_ENGINE="google-chrome"
+else
+  PDF_REASON="no PDF engine found (looked for: weasyprint, wkhtmltopdf, chromium, google-chrome)"
+fi
+
+if [ -n "$PDF_ENGINE" ]; then
+  case "$PDF_ENGINE" in
+    weasyprint)
+      weasyprint --base-url "$OUT_DIR/" "$OUT_HTML" "$OUT_PDF"
+      ;;
+    wkhtmltopdf)
+      wkhtmltopdf --enable-local-file-access \
+        --margin-top 0 --margin-bottom 0 \
+        --margin-left 0 --margin-right 0 \
+        --print-media-type \
+        "$OUT_HTML" "$OUT_PDF"
+      ;;
+    chromium|chromium-browser|google-chrome)
+      "$PDF_ENGINE" --headless --disable-gpu --no-sandbox \
+        --no-pdf-header-footer \
+        --print-to-pdf="$OUT_PDF" \
+        --print-to-pdf-no-header \
+        "file://$OUT_HTML"
+      ;;
+  esac
+  echo "wrote: $OUT_PDF (engine: $PDF_ENGINE)"
+  exit 0
+fi
+
+cat <<EOF >&2
+
+note: HTML written, but no PDF engine is available.
+      reason: $PDF_REASON
+
+To generate $OUT_PDF, install one of:
+  - weasyprint   pip install --user weasyprint
+  - wkhtmltopdf  apt install wkhtmltopdf  (or download from wkhtmltopdf.org)
+  - chromium     apt install chromium-browser
+Or open $OUT_HTML in a browser and use "Print → Save as PDF".
+
+EOF
+exit 2