# Resync du clone specification.website + tool MCP `search_spec_topics` + checks headers

**Date :** 2026-07-07 · **Suite de :** ELEVA-93 / ELEVA-98 (re-sync périodique prévu par le design du 2026-06-11). Demande : « faire toute la partie pas faite pour le MCP » — copie interne à jour + recherche pour les agents.

## Contexte

Le clone interne (`src/content/spec/**`, `checklist-data.ts`) date du 2026-06-11 avec 135 topics. L'upstream compte désormais 156 topics et publie 33 changements depuis début juin (ajouts, réécritures — dont HSTS sans `preload` —, downgrades vers `optional` de view-transitions / invoker-commands / visibility-aware-rendering, 2 suppressions jamais présentes chez nous). Le serveur MCP privé n'offre pas de recherche : un agent qui ne connaît pas les slugs doit paginer `list_spec_topics`.

## Décisions

### 1. Pipeline de resync — `scripts/sync-spec-content.ts` (`bun run spec:sync`)
- **Source de vérité de l'index** : `https://specification.website/checklist.md` (une requête HTTP, CC BY 4.0) — lignes `- [ ] [Titre](URL) — Status` + description wrappée indentée, groupées par `## Catégorie` dont les headings correspondent exactement aux `label` de `CATEGORY_META`.
- **`scripts/spec-source/*.md` conservé** comme intermédiaire lisible/diffable ; `checklist-data.ts` toujours auto-généré.
- **Logique pure dans `src/lib/spec/checklist-sync.ts`** (couverte par `test:all`, qui ne découvre que `src/**`) : parse du format spec-source (extrait de `generate-checklist.ts`, refactor sans changement de comportement), sérialisation spec-source, rendu de `checklist-data.ts`, `parseUpstreamChecklist`, `diffChecklists`. Le script ne garde que l'I/O (fetch, fs, rapport, exit codes).
- Déroulé : fetch checklist.md → parse → diff vs `CHECKLIST` (rapport added/removed/statusChanged/textChanged) → réécrit spec-source (garde d'idempotence : re-parse ≡) → régénère checklist-data.ts → fetch des `.md` topics (pool 5, timeout 10 s, retry ×1, garde frontmatter) avec created/updated/unchanged → suppression des fichiers locaux hors index (dossiers des 10 catégories connues uniquement) → récap, exit 1 si échec. `--dry-run` disponible.
- Heading de catégorie inconnu → throw : une nouvelle catégorie upstream est une décision humaine (CATEGORY_META, UI, types).
- Les nouveaux topics sans check tombent en « manual » dans l'audit (comportement existant de `run.ts`) — score inchangé.

### 2. Tool MCP `search_spec_topics` (parité avec le `search` upstream)
- `src/lib/mcp/spec-search.ts` : index en mémoire lazy (module-level) construit depuis `CHECKLIST` + `getSpecPage` (summary frontmatter + corps), champs pré-lowercasés.
- Tokenisation : lowercase, split whitespace + ponctuation sauf `/ _ . -`, tokens < 2 chars ignorés (documenté comme l'upstream).
- Scoring déterministe par token (substring) : title +8, slug +6, summary +4, body +1 ; tri score desc puis ordre checklist ; `limit` clampé 1–25, défaut 5 ; jusqu'à 2 extraits (~90 chars) du corps.
- Retour : id, titre, statut, catégorie, résumé, chemin interne `/flight-checker/spec/<id>`, URL canonique, extraits.
- Auth/quota : rien de spécifique — couverts au chokepoint route (`resolveMcpOrg` + `assertMcpQuota`), pas de `requireOrgId` (précédent `list_spec_topics`, contenu statique identique pour toutes les orgs).

### 3. Checks d'audit « faciles » (headers de la home uniquement, `SiteSnapshot` inchangé)
- `security/cross-origin-isolation` : COOP+COEP+CORP → pass ; partiel → warn (nomme les manquants) ; aucun → warn.
- `security/reporting-endpoints` : header présent → pass ; `report-to` legacy seul → warn ; absent → warn.
- `security/trusted-types` : CSP contenant `require-trusted-types-for` → pass ; CSP sans → warn ; pas de CSP → warn.
- `performance/server-timing` : présent → pass ; absent → **na** (topic `optional`, absence non pénalisée — miroir http3/theme-color).
- `security/mixed-content` et `performance/conditional-requests` existent déjà — rien à faire.

## Hors périmètre (explicitement)
MCP public-limité/anonyme et rate-limiting, manifestes `.well-known` (server card, agent skills), tools `get_checklist`/`get_categories`/`get_changes`, `run_site_audit` (mutation, v1.1), cron/CI de resync périodique (le script est rejouable à la main ; cadence mensuelle suggérée par l'upstream).

## Tests
- `checklist-sync.test.ts` : round-trip spec-source, parse upstream (wrap multi-lignes, mapping "Well-Known URIs"→`well-known` / "Internationalisation"→`i18n`, statuts capitalisés, heading inconnu → throw, ordre préservé), diff.
- `spec-search.test.ts` : top hit CSP, slug exact, token 1 char ignoré, 0 hit → `[]`, clamp limit, déterminisme, extraits.
- `run.test.ts` : `GOOD_HEADERS` complété (sinon « good snapshot scores 100 » casse), 4 nouveaux ids en pass, edge cases warn/na.
- `checklist-data.test.ts` : comptes réels post-sync ; `route.test.ts` : liste exacte des tools + `search_spec_topics`.

## Notes
- Les scores des sites déjà audités baisseront légèrement au prochain audit (nouveaux warns typiques) — attendu.
- Les `site_audits` historiques restent valides (mapping par itemId sur la CHECKLIST courante) ; l'état coché localStorage ignore les ids disparus.
