docs: importer zacus_conversation_bundle_v3 et clôturer le blocage ZIP intake (#106)

* docs: import zacus conversation bundle v3 and close ZIP intake TODO

* tools: add reproducible validator installer and document next gates

* docs: remove ZIP terminology and align intake docs to conversation bundle

* tools: add runtime bundle validator and update docs workflow

* feat: promote gameplay scenario and enforce G3 bundle checks in CI

* docs: add deep repo analysis and next-step TODOs

* Initial plan (#107)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
This commit was merged in pull request #106.
This commit is contained in:
Clément SAILLANT
2026-02-25 18:17:46 +01:00
committed by GitHub
parent cdbbed3fd0
commit 229c381c3e
22 changed files with 655 additions and 119 deletions
+5 -1
View File
@@ -16,9 +16,13 @@ jobs:
with: with:
python-version: '3.x' python-version: '3.x'
- name: Install dependencies - name: Install dependencies
run: python -m pip install --upgrade pip pyyaml run: python -m pip install --upgrade pip -r tools/requirements/validators.txt
- name: Validate runtime bundle coherence
run: python tools/scenario/validate_runtime_bundle.py
- name: Validate scenario - name: Validate scenario
run: python tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml run: python tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml
- name: Export scenario markdown briefs
run: python tools/scenario/export_md.py game/scenarios/zacus_v1.yaml
- name: Validate audio manifest - name: Validate audio manifest
run: python tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml run: python tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml
- name: Validate printables manifest - name: Validate printables manifest
@@ -0,0 +1,26 @@
# Architecture d'intégration du conversation bundle — baseline (G1)
## Entrée / sortie
- **Entrée**: bundle conversationnel `scenario-ai-coherence/zacus_conversation_bundle_v3/*`.
- **Sortie**: ajout des fichiers dans ce sous-dossier + commit atomique + evidence.
## Interface d'intégration
- Source de vérité gameplay inchangée: `game/scenarios/*.yaml`.
- Bundle stocké comme matériau de travail IA/scénario, isolé dans `scenario-ai-coherence/`.
- Aucun couplage runtime ajouté dans ce lot.
## Budget / impact
- Impact code exécutable: nul (docs/data only).
- Impact CI: validateurs scénario/audio/printables exécutés pour non-régression.
- Bootstrap dépendances validateurs: `tools/setup/install_validators.sh`.
## Prochaine évolution proposée
- Validateur ajouté: `tools/scenario/validate_runtime_bundle.py` (cohérence entre `scenario_runtime.json`, `scenario_canonical.yaml` et `scenario_promptable_template.yaml`).
- Intégration CI de ce contrôle dans la gate G3 effectuée dans `.github/workflows/validate.yml`.
- Diff fonctionnel gameplay ajouté via `tools/scenario/diff_gameplay_scenarios.py` (rapport artifact QA).
## Statut Gates
- G1 Arch Freeze : **satisfait** pour ce lot d'import documentaire/data.
## TODO architecture
- CI de diff fonctionnel gameplay à ajouter (upload artifact) pour compléter G3 en continu.
@@ -0,0 +1,5 @@
# Artifact PM/Spec — conversation bundle intake
- Date: 2026-02-25
- Scope: harmonisation de la terminologie avec le scope conversation bundle.
- Résultat: specs/architecture renommés et harmonisés avec le périmètre réel.
@@ -0,0 +1,6 @@
# Artifact PM/Spec — gameplay promotion
- Date: 2026-02-25
- Décision: promotion gameplay validée vers `game/scenarios/zacus_v1.yaml`.
- Alignement narratif: U-SON, LA 440, piano LEFOU, QR WIN.
- Garde-fou: source de vérité reste `game/scenarios/*.yaml`.
@@ -0,0 +1,6 @@
# Artifact PM/Spec — repo deep analysis
- Date: 2026-02-25
- Portée: analyse transverse du repo (game/docs/tools/ci).
- Livrable: `docs/repo-deep-analysis-2026-02-25.md`.
- Priorité proposée: PR-A (CI sur diff fonctionnel).
@@ -0,0 +1,31 @@
# Rapport de diff fonctionnel — scénarios Zacus
- Date: 2026-02-25
- Base canon: `scenario-ai-coherence/zacus_conversation_bundle_v3/zacus_v2.yaml`
- Cible gameplay: `game/scenarios/zacus_v1.yaml`
## Identité
- id: `zacus_v2` -> `zacus_v1`
- version: `3` -> `2`
- title: `Le mystère du Professeur Zacus — Version Réelle (U-SON, piano LEFOU, QR WIN)` -> `Le mystère du Professeur Zacus — U-SON (promotion gameplay)`
## Cadre de session
- players: `6-14` -> `6-14`
- duration_minutes: `None-None` -> `60-90`
## Structure narrative
- stations: `3` -> `3`
- puzzles: `3` -> `3`
- solution_unique: `None` -> `True`
## Changements clés détectés
- Stations cibles: Atelier des ondes, Zone 4 — Studio de Résonance, Salle des archives
- IDs puzzles base: PUZZLE_LA_440, PUZZLE_PIANO_ALPHABET_5, PUZZLE_QR_WIN
- IDs puzzles cible: la-440, piano-lefou, qr-win
- ✅ Les stations ont été réalignées avec le gameplay U-SON / Zone 4 / Archives.
- ✅ Les puzzles ont été remplacés pour refléter LA 440 → LEFOU → QR WIN.
## Impact
- Source de vérité gameplay maintenue dans `game/scenarios/*.yaml`.
- Bundle conversationnel conservé comme matériau de travail parallèle.
- Validation G3 requise via validateurs scénario/audio/printables + runtime bundle.
@@ -0,0 +1,6 @@
# Artifact QA/Test — runtime bundle validator
- Date: 2026-02-25
- Script ajouté: `tools/scenario/validate_runtime_bundle.py`
- Cible: cohérence `scenario_runtime.json` / `scenario_canonical.yaml` / `scenario_promptable_template.yaml`.
- Doc mise à jour: README bundle + workflow global.
@@ -0,0 +1,5 @@
# Artifact QA/Test — validator bootstrap
- Validator dependencies pinned in `tools/requirements/validators.txt`.
- Bootstrap script added: `tools/setup/install_validators.sh`.
- Validation gates executed after install.
+5
View File
@@ -1,7 +1,12 @@
# Workflows # Workflows
0. **Installer les validateurs** avec `bash tools/setup/install_validators.sh` (installe les dépendances Python requises, ex: PyYAML).
1. **Mettre à jour le YAML canon** (`game/scenarios/zacus_v1.yaml` ou nouvel ID). Les stations/puzzles/solution sont la source des prompts audio et printables. 1. **Mettre à jour le YAML canon** (`game/scenarios/zacus_v1.yaml` ou nouvel ID). Les stations/puzzles/solution sont la source des prompts audio et printables.
2. **Valider le scénario** avec `tools/scenario/validate_scenario.py` pour garantir structure, plages 614 enfants et `solution_unique`. 2. **Valider le scénario** avec `tools/scenario/validate_scenario.py` pour garantir structure, plages 614 enfants et `solution_unique`.
3. **Exporter les briefings Markdown** via `tools/scenario/export_md.py` pour mettre à jour `kit-maitre-du-jeu/_generated/` et `docs/_generated/SCENARIO_BRIEF.md`. 3. **Exporter les briefings Markdown** via `tools/scenario/export_md.py` pour mettre à jour `kit-maitre-du-jeu/_generated/` et `docs/_generated/SCENARIO_BRIEF.md`.
4. **Valider laudio** avec `tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml` afin que chaque piste corresponde à un fichier de `game/prompts/audio/`. 4. **Valider laudio** avec `tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml` afin que chaque piste corresponde à un fichier de `game/prompts/audio/`.
5. **Valider les printables** avec `tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml` pour sassurer que chaque ID a un prompt accessible. 5. **Valider les printables** avec `tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml` pour sassurer que chaque ID a un prompt accessible.
6. **Valider le conversation bundle** avec `tools/scenario/validate_runtime_bundle.py` pour vérifier la cohérence `scenario_runtime.json` / `scenario_canonical.yaml` / `scenario_promptable_template.yaml`.
7. **Générer le diff fonctionnel gameplay** avec `tools/scenario/diff_gameplay_scenarios.py` pour produire un rapport markdown dans `artifacts/qa-test/<date>/`.
+21 -24
View File
@@ -1,4 +1,4 @@
# SCENARIO BRIEF — Le mystère du Professeur Zacus # SCENARIO BRIEF — Le mystère du Professeur Zacus — U-SON (promotion gameplay)
## Stats ## Stats
@@ -8,42 +8,39 @@
## Introduction ## Introduction
Le professeur Zacus a lancé une dernière transmission avant de disparaître : un message tronqué, un avertissement crypté, puis le silence. Le professeur Zacus a disparu après avoir lancé une alerte cryptée : le prototype U-SON dérive. L'équipe doit stabiliser la référence LA 440 Hz, résoudre la règle Zone 4 et déclencher la clé finale.
## Chronologie ## Chronologie
- **17h45** — Message audio : 'Préparez-vous à l'Auréole, je viens de repérer un comportement anormal dans les capteurs 3A.' - **Acte 1 (~45 min)** — Atelier des Ondes : stabiliser LA 440 pour recaler U-SON.
- **18h05** — Les caméras montrent un passage vers la salle des prototypes, puis plus rien. Une température monte à 48 °C dans l'atelier et les portes se verrouillent automatiquement. - **Acte 2 (~60 min)** — Zone 4 : décoder et jouer LEFOU sur le piano alphabet (A ancré sur LA 440).
- **18h20** — Un code, une goutte d'un fluide lumineux et une note griffonnée sont les seuls indices restants. - **Finale** — Archives : scanner le QR payload WIN derrière le portrait.
## Stations ## Stations
- **Atelier des ondes** — stabiliser les lecteurs de fréquences et retrouver le code sonore. | Indice : une partition inversée incruste les chiffres 1-4-3, renversés par le Professeur pour protéger un secret. - **Atelier des ondes** — stabiliser un LA 440 Hz propre et constant. | Indice : une consigne rappelle que la référence audio juste est la seule ancre fiable pour la suite.
- **Salle des archives** — comparer les relevés de sécurité. | Indice : un enregistrement supprimé portant la signature de Lila Caron laisse entrevoir une dispute privée et un déplacement imprévu. - **Zone 4 — Studio de Résonance** — jouer le code LEFOU via un piano-alphabet brouillé. | Indice : les lettres sont collées en ordre aléatoire sur les touches blanches, avec A placé sur la touche du LA 440.
- **Bureau du Professeur** — décrypter la note effacée sur le tableau blanc. | Indice : lempreinte digitale non professorale sur le stylo, plus une formule incomplète reliant tension et loyauté. - **Salle des archives** — localiser la clé finale et valider le déverrouillage. | Indice : le QR final WIN est caché derrière le portrait.
- **Carnet du labo secret** — remettre en ordre les données de lexpérience « Auréole ». Un code couleur et une goutte de liquide indiquent la cible. | Indice : Le carnet confirme que « Auréole » génère un signal au-dessus du sol. La goutte + code couleur pointent vers le Hangar du Droïde (Zone 5) où se cache la table Morse; le décodage indiquera ensuite la direction du Studio de Résonance (Zone 4).
## Puzzles ## Puzzles
- code-sonore (logique) — Réassembler la partition inversée permet de déverrouiller un coffre numérique et révèle la première syllabe du mot-clé. - la-440 (audio) — produire un LA 440 stable déclenche la validation de l'étape 1.
Effet : Déclenche le message audio de la station suivante. Effet : confirmation audio et ouverture de l'étape Zone 4.
- log-verifie (observation) — Les dates du journal calment tous les alibis sauf celui de Lila Caron. La note parle d'un rendez-vous à 18h15 dans la salle des prototypes. - piano-lefou (logique) — retrouver puis jouer L-E-F-O-U sur le piano à lettres brouillées, en utilisant l'ancre A=LA 440.
Effet : Révèle lemplacement du prototype caché. Effet : validation de l'étape 2 et indice explicite vers les Archives.
- empreinte (tactile) — Une empreinte de gants marque une pression sur le handle de la baie de transfert (haut à gauche). Lila est la seule à porter ce format de gants. - qr-win (observation) — scanner le QR avec payload WIN derrière le portrait de la salle des Archives.
Effet : Confirme le mouvement de la cible et le fait que la porte sest ouverte manuellement. Effet : déverrouille le media manager et la conclusion narrative.
- mobile (narration) — Le parchemin évoque un message : 'Je ne veux pas que lAuréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.'
Effet : Donne le mobile moral unique et pousse vers laccusation finale.
## Solution ## Solution
- Coupable : Lila Caron, lassistante en chef chargée des capteurs et du prototype. - Coupable : Aucun coupable unique, il s'agit d'une épreuve orchestrée par Zacus.
- Mobile : Lila a saboles protocoles pour isoler le prototype et protéger les cobayes humains quelle connaissait mieux que quiconque. Elle craignait que la direction confie lAuréole à un consortium de sécurité sans restrictions éthiques. - Mobile : Vérifier la capacides équipes à coopérer sous contrainte, sans céder à la panique ni aux fausses pistes.
- Méthode : Elle a envoyé le message tronqué du professeur, puis ouvert la baie de transfert pour simuler une fuite d’énergie (température, code 1-4-3) et a verrouillé les accès. Elle a laissé une note partielle sur le tableau pour que les enfants la trouvent en dernier, concentrant toute attention sur son geste de protectrice. - Méthode : Progression en 3 validations séquentielles : LA 440, LEFOU en Zone 4, puis QR WIN aux Archives.
- Preuves: - Preuves:
- Lempreinte de gants est celle quelle porte toujours. - Le signal LA 440 stabilise U-SON et valide l'étape 1.
- Les logs la placent dans la salle des prototypes à 18h15, soit juste après le message du professeur. - La séquence LEFOU correctement jouée valide l'étape 2.
- Le mobile devient clair dans la note : elle cherche à empêcher un usage militaire. - Le QR payload WIN finalise le parcours et ouvre la conclusion.
## Notes ## Notes
En version facile, centre-toi sur le code sonore et la note du tableau ; en version longue, demande aux enfants de rédiger une lettre où ils décrivent pourquoi la protection de lAuréole était essentielle. Cette promotion gameplay aligne le canon sur le bundle conversationnel v3, tout en conservant `game/scenarios/*.yaml` comme source de vérité.
+54
View File
@@ -0,0 +1,54 @@
# Analyse approfondie du dépôt — 2026-02-25
## Objectif
Aller plus loin que le lot conversation bundle en évaluant l'état global du repo (docs, workflows, contenu gameplay, outillage validation, CI).
## Méthode
Commandes de revue utilisées (échantillon):
- `rg --files`
- `rg -n "validate_|workflow|scenario" .github/workflows tools docs game`
- `python3 tools/scenario/validate_runtime_bundle.py`
- `python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml`
- `python3 tools/scenario/export_md.py game/scenarios/zacus_v1.yaml`
- `python3 tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml`
- `python3 tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml`
## Constat global
### 1) Source de vérité gameplay
-`game/scenarios/zacus_v1.yaml` est bien utilisé comme source canon.
- ✅ Les exports MJ/docs générés sont cohérents avec le canon courant.
- ⚠️ Le bundle `scenario-ai-coherence/` reste parallèle: utile pour itérer, mais il faut éviter toute dérive silencieuse.
### 2) Gates G3 / validation
- ✅ Validateurs scénario/audio/printables présents et opérationnels.
-`validate_runtime_bundle.py` existe et est branché dans CI (`.github/workflows/validate.yml`).
- ⚠️ Le diff fonctionnel gameplay est scripté localement, mais pas encore exécuté automatiquement en CI.
### 3) Documentation et guidage équipe
-`docs/WORKFLOWS.md` décrit l'enchaînement validation.
- ✅ Spec/arch conversation bundle documentent le périmètre.
- ⚠️ Il manque un guide "release gate" unique qui enchaîne G0→G5 en une seule checklist opérationnelle.
### 4) Risques principaux
- Divergence entre bundle conversationnel et canon gameplay si la promotion n'est pas rituelle.
- G4/G5 encore partiels (pas de lot release/compliance structuré dans ce cycle).
- Dépendance implicite à PyYAML: bootstrap présent, mais doit rester obligatoire en CI locale/README.
## Plan de progression recommandé (prochaines PRs)
### PR-A (QA/Test)
- Ajouter un job CI dédié qui exécute aussi `tools/scenario/diff_gameplay_scenarios.py`.
- Publier l'artifact markdown de diff en sortie CI (upload artifact GitHub Actions).
### PR-B (Doc/Release)
- Créer `docs/RELEASE_GATES.md` avec checklist G0→G5 exécutable.
- Standardiser le template de PR pour exiger la section "Gates satisfaites" + "limitations".
### PR-C (Compliance)
- Ajouter un inventaire SBOM/licences minimal pour G5 (au moins dépendances Python validateurs + manifest repo).
- Centraliser les preuves dans `evidence/` avec index daté.
## Décision proposée maintenant
- Garder le canon gameplay tel quel.
- Passer sur PR-A en priorité pour fermer l'écart CI sur le diff fonctionnel.
+23
View File
@@ -0,0 +1,23 @@
{
"date": "2026-02-25",
"agent": "pm-spec",
"changes": [
"docs/repo-deep-analysis-2026-02-25.md",
"specs/CONVERSATION_BUNDLE_INTAKE.md",
"architecture/CONVERSATION_BUNDLE_ARCHITECTURE.md"
],
"artifacts": [
"artifacts/pm-spec/2026-02-25/repo-deep-analysis.md",
"artifacts/pm-spec/2026-02-25/gameplay-promotion-note.md",
"artifacts/qa-test/2026-02-25/gameplay-functional-diff.md"
],
"gates": {
"G0": "satisfied",
"G1": "satisfied",
"G2": "implemented",
"G3": "validated",
"G4": "pending",
"G5": "pending_plan"
},
"note": "Analyse approfondie repo publiée avec plan PR-A/PR-B/PR-C pour aller au-delà du lot courant."
}
+38 -49
View File
@@ -1,73 +1,62 @@
id: zacus_v1 id: zacus_v1
version: 1 version: 2
title: Le mystère du Professeur Zacus title: Le mystère du Professeur Zacus — U-SON (promotion gameplay)
theme: Absence mystérieuse au centre de recherches du Campus Zacus theme: Disparition de Zacus, stabilisation U-SON (LA 440), code piano LEFOU, QR final WIN
players: players:
min: 6 min: 6
max: 14 max: 14
ages: "614 ans (911 recommandés pour les variantes les plus proches du canon)" ages: "614 ans (911 recommandés)"
duration_minutes: duration_minutes:
min: 60 min: 60
max: 90 max: 90
canon: canon:
introduction: >- introduction: >-
Le professeur Zacus a lancé une dernière transmission avant de disparaître : un message tronqué, un avertissement crypté, puis le silence. Le professeur Zacus a disparu après avoir lancé une alerte cryptée : le prototype U-SON dérive.
L'équipe doit stabiliser la référence LA 440 Hz, résoudre la règle Zone 4 et déclencher la clé finale.
timeline: timeline:
- label: "17h45" - label: "Acte 1 (~45 min)"
note: "Message audio : 'Préparez-vous à l'Auréole, je viens de repérer un comportement anormal dans les capteurs 3A.'" note: "Atelier des Ondes : stabiliser LA 440 pour recaler U-SON."
- label: "18h05" - label: "Acte 2 (~60 min)"
note: "Les caméras montrent un passage vers la salle des prototypes, puis plus rien. note: "Zone 4 : décoder et jouer LEFOU sur le piano alphabet (A ancré sur LA 440)."
Une température monte à 48 °C dans l'atelier et les portes se verrouillent automatiquement." - label: "Finale"
- label: "18h20" note: "Archives : scanner le QR payload WIN derrière le portrait."
note: "Un code, une goutte d'un fluide lumineux et une note griffonnée sont les seuls indices restants."
stakes: >- stakes: >-
Si l'équipe accuse la mauvaise personne, le prototype s'activant en dehors des protocoles déclenchera la procédure d'urgence. Le test mesure la coopération, la précision et le calme : sans méthode, U-SON reste instable et l'accès final demeure verrouillé.
stations: stations:
- name: Atelier des ondes - name: Atelier des ondes
focus: stabiliser les lecteurs de fréquences et retrouver le code sonore. focus: stabiliser un LA 440 Hz propre et constant.
clue: une partition inversée incruste les chiffres 1-4-3, renversés par le Professeur pour protéger un secret. clue: une consigne rappelle que la référence audio juste est la seule ancre fiable pour la suite.
- name: Zone 4 — Studio de Résonance
focus: jouer le code LEFOU via un piano-alphabet brouillé.
clue: les lettres sont collées en ordre aléatoire sur les touches blanches, avec A placé sur la touche du LA 440.
- name: Salle des archives - name: Salle des archives
focus: comparer les relevés de sécurité. focus: localiser la clé finale et valider le déverrouillage.
clue: un enregistrement supprimé portant la signature de Lila Caron laisse entrevoir une dispute privée et un déplacement imprévu. clue: le QR final WIN est caché derrière le portrait.
- name: Bureau du Professeur
focus: décrypter la note effacée sur le tableau blanc.
clue: lempreinte digitale non professorale sur le stylo, plus une formule incomplète reliant tension et loyauté.
- name: Carnet du labo secret
focus: remettre en ordre les données de lexpérience « Auréole ». Un code couleur et une goutte de liquide indiquent la cible.
clue: "Le carnet confirme que « Auréole » génère un signal au-dessus du sol. La goutte + code couleur pointent vers le Hangar du Droïde (Zone 5) où se cache la table Morse; le décodage indiquera ensuite la direction du Studio de Résonance (Zone 4)."
puzzles: puzzles:
- id: code-sonore - id: la-440
type: audio
clue: produire un LA 440 stable déclenche la validation de l'étape 1.
effect: confirmation audio et ouverture de l'étape Zone 4.
- id: piano-lefou
type: logique type: logique
clue: "Réassembler la partition inversée permet de déverrouiller un coffre numérique et révèle la première syllabe du mot-clé." clue: retrouver puis jouer L-E-F-O-U sur le piano à lettres brouillées, en utilisant l'ancre A=LA 440.
effect: "Déclenche le message audio de la station suivante." effect: validation de l'étape 2 et indice explicite vers les Archives.
- id: log-verifie - id: qr-win
type: observation type: observation
clue: "Les dates du journal calment tous les alibis sauf celui de Lila Caron. clue: scanner le QR avec payload WIN derrière le portrait de la salle des Archives.
La note parle d'un rendez-vous à 18h15 dans la salle des prototypes." effect: déverrouille le media manager et la conclusion narrative.
effect: "Révèle lemplacement du prototype caché."
- id: empreinte
type: tactile
clue: "Une empreinte de gants marque une pression sur le handle de la baie de transfert (haut à gauche).
Lila est la seule à porter ce format de gants."
effect: "Confirme le mouvement de la cible et le fait que la porte sest ouverte manuellement."
- id: mobile
type: narration
clue: "Le parchemin évoque un message : 'Je ne veux pas que lAuréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.'"
effect: "Donne le mobile moral unique et pousse vers laccusation finale."
solution: solution:
culprit: Lila Caron, lassistante en chef chargée des capteurs et du prototype. culprit: Aucun coupable unique, il s'agit d'une épreuve orchestrée par Zacus.
motive: >- motive: >-
Lila a saboles protocoles pour isoler le prototype et protéger les cobayes humains quelle connaissait mieux que quiconque. Vérifier la capacides équipes à coopérer sous contrainte, sans céder à la panique ni aux fausses pistes.
Elle craignait que la direction confie lAuréole à un consortium de sécurité sans restrictions éthiques.
method: >- method: >-
Elle a envoyé le message tronqué du professeur, puis ouvert la baie de transfert pour simuler une fuite d’énergie (température, code 1-4-3) et a verrouillé les accès. Progression en 3 validations séquentielles : LA 440, LEFOU en Zone 4, puis QR WIN aux Archives.
Elle a laissé une note partielle sur le tableau pour que les enfants la trouvent en dernier, concentrant toute attention sur son geste de protectrice.
proof: proof:
- "Lempreinte de gants est celle quelle porte toujours." - "Le signal LA 440 stabilise U-SON et valide l'étape 1."
- "Les logs la placent dans la salle des prototypes à 18h15, soit juste après le message du professeur." - "La séquence LEFOU correctement jouée valide l'étape 2."
- "Le mobile devient clair dans la note : elle cherche à empêcher un usage militaire." - "Le QR payload WIN finalise le parcours et ouvre la conclusion."
solution_unique: true solution_unique: true
finale_statement: >- finale_statement: >-
Zacus réapparaît, caché dans la salle des archives pendant que l’équipe accuse Lila. Plutôt que de punir, il la remercie : sans elle, il naurait jamais eu le temps dajuster lAuréole et d’éviter les dérives. Zacus réapparaît et confirme que l'épreuve visait l'éthique de jeu : méthode, écoute et entraide avant la vitesse.
notes: >- notes: >-
En version facile, centre-toi sur le code sonore et la note du tableau ; en version longue, demande aux enfants de rédiger une lettre où ils décrivent pourquoi la protection de lAuréole était essentielle. Cette promotion gameplay aligne le canon sur le bundle conversationnel v3, tout en conservant `game/scenarios/*.yaml` comme source de vérité.
+3 -3
View File
@@ -2,12 +2,12 @@
## Configurer ## Configurer
- Scénario : Le mystère du Professeur Zacus - Scénario : Le mystère du Professeur Zacus — U-SON (promotion gameplay)
- Fichier source : game/scenarios/zacus_v1.yaml - Fichier source : game/scenarios/zacus_v1.yaml
- Participants : 614 - Participants : 614
- Durée : 6090 min - Durée : 6090 min
- Stations prévues : 4 - Stations prévues : 3
- Puzzles : 4 - Puzzles : 3
- Preuves listées : 3 - Preuves listées : 3
## Matériel recommandé ## Matériel recommandé
+17 -19
View File
@@ -1,38 +1,36 @@
# Script — Le mystère du Professeur Zacus # Script — Le mystère du Professeur Zacus — U-SON (promotion gameplay)
## Introduction ## Introduction
Le professeur Zacus a lancé une dernière transmission avant de disparaître : un message tronqué, un avertissement crypté, puis le silence. Le professeur Zacus a disparu après avoir lancé une alerte cryptée : le prototype U-SON dérive. L'équipe doit stabiliser la référence LA 440 Hz, résoudre la règle Zone 4 et déclencher la clé finale.
## Chronologie canon ## Chronologie canon
- **17h45** — Message audio : 'Préparez-vous à l'Auréole, je viens de repérer un comportement anormal dans les capteurs 3A.' - **Acte 1 (~45 min)** — Atelier des Ondes : stabiliser LA 440 pour recaler U-SON.
- **18h05** — Les caméras montrent un passage vers la salle des prototypes, puis plus rien. Une température monte à 48 °C dans l'atelier et les portes se verrouillent automatiquement. - **Acte 2 (~60 min)** — Zone 4 : décoder et jouer LEFOU sur le piano alphabet (A ancré sur LA 440).
- **18h20** — Un code, une goutte d'un fluide lumineux et une note griffonnée sont les seuls indices restants. - **Finale** — Archives : scanner le QR payload WIN derrière le portrait.
## Stations ## Stations
- **Atelier des ondes** — stabiliser les lecteurs de fréquences et retrouver le code sonore. - **Atelier des ondes** — stabiliser un LA 440 Hz propre et constant.
- **Salle des archives** — comparer les relevés de sécurité. - **Zone 4 — Studio de Résonance** — jouer le code LEFOU via un piano-alphabet brouillé.
- **Bureau du Professeur** — décrypter la note effacée sur le tableau blanc. - **Salle des archives** — localiser la clé finale et valider le déverrouillage.
- **Carnet du labo secret** — remettre en ordre les données de lexpérience « Auréole ». Un code couleur et une goutte de liquide indiquent la cible.
## Puzzles ## Puzzles
- code-sonore (logique) — Réassembler la partition inversée permet de déverrouiller un coffre numérique et révèle la première syllabe du mot-clé. - la-440 (audio) — produire un LA 440 stable déclenche la validation de l'étape 1.
- log-verifie (observation) — Les dates du journal calment tous les alibis sauf celui de Lila Caron. La note parle d'un rendez-vous à 18h15 dans la salle des prototypes. - piano-lefou (logique) — retrouver puis jouer L-E-F-O-U sur le piano à lettres brouillées, en utilisant l'ancre A=LA 440.
- empreinte (tactile) — Une empreinte de gants marque une pression sur le handle de la baie de transfert (haut à gauche). Lila est la seule à porter ce format de gants. - qr-win (observation) — scanner le QR avec payload WIN derrière le portrait de la salle des Archives.
- mobile (narration) — Le parchemin évoque un message : 'Je ne veux pas que lAuréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.'
## Solution canon ## Solution canon
- Coupable : Lila Caron, lassistante en chef chargée des capteurs et du prototype. - Coupable : Aucun coupable unique, il s'agit d'une épreuve orchestrée par Zacus.
- Mobile : Lila a saboles protocoles pour isoler le prototype et protéger les cobayes humains quelle connaissait mieux que quiconque. Elle craignait que la direction confie lAuréole à un consortium de sécurité sans restrictions éthiques. - Mobile : Vérifier la capacides équipes à coopérer sous contrainte, sans céder à la panique ni aux fausses pistes.
- Méthode : Elle a envoyé le message tronqué du professeur, puis ouvert la baie de transfert pour simuler une fuite d’énergie (température, code 1-4-3) et a verrouillé les accès. Elle a laissé une note partielle sur le tableau pour que les enfants la trouvent en dernier, concentrant toute attention sur son geste de protectrice. - Méthode : Progression en 3 validations séquentielles : LA 440, LEFOU en Zone 4, puis QR WIN aux Archives.
- Preuves : - Preuves :
- Lempreinte de gants est celle quelle porte toujours. - Le signal LA 440 stabilise U-SON et valide l'étape 1.
- Les logs la placent dans la salle des prototypes à 18h15, soit juste après le message du professeur. - La séquence LEFOU correctement jouée valide l'étape 2.
- Le mobile devient clair dans la note : elle cherche à empêcher un usage militaire. - Le QR payload WIN finalise le parcours et ouvre la conclusion.
## Notes pratiques ## Notes pratiques
+15 -23
View File
@@ -2,34 +2,26 @@
## Atelier des ondes ## Atelier des ondes
- Focus : stabiliser les lecteurs de fréquences et retrouver le code sonore. - Focus : stabiliser un LA 440 Hz propre et constant.
- Clue : une partition inversée incruste les chiffres 1-4-3, renversés par le Professeur pour protéger un secret. - Clue : une consigne rappelle que la référence audio juste est la seule ancre fiable pour la suite.
## Zone 4 — Studio de Résonance
- Focus : jouer le code LEFOU via un piano-alphabet brouillé.
- Clue : les lettres sont collées en ordre aléatoire sur les touches blanches, avec A placé sur la touche du LA 440.
## Salle des archives ## Salle des archives
- Focus : comparer les relevés de sécurité. - Focus : localiser la clé finale et valider le déverrouillage.
- Clue : un enregistrement supprimé portant la signature de Lila Caron laisse entrevoir une dispute privée et un déplacement imprévu. - Clue : le QR final WIN est caché derrière le portrait.
## Bureau du Professeur
- Focus : décrypter la note effacée sur le tableau blanc.
- Clue : lempreinte digitale non professorale sur le stylo, plus une formule incomplète reliant tension et loyauté.
## Carnet du labo secret
- Focus : remettre en ordre les données de lexpérience « Auréole ». Un code couleur et une goutte de liquide indiquent la cible.
- Clue : Le carnet confirme que « Auréole » génère un signal au-dessus du sol. La goutte + code couleur pointent vers le Hangar du Droïde (Zone 5) où se cache la table Morse; le décodage indiquera ensuite la direction du Studio de Résonance (Zone 4).
## Puzzles ## Puzzles
- code-sonore (logique) : Réassembler la partition inversée permet de déverrouiller un coffre numérique et révèle la première syllabe du mot-clé. - la-440 (audio) : produire un LA 440 stable déclenche la validation de l'étape 1.
Effet : Déclenche le message audio de la station suivante. Effet : confirmation audio et ouverture de l'étape Zone 4.
- log-verifie (observation) : Les dates du journal calment tous les alibis sauf celui de Lila Caron. La note parle d'un rendez-vous à 18h15 dans la salle des prototypes. - piano-lefou (logique) : retrouver puis jouer L-E-F-O-U sur le piano à lettres brouillées, en utilisant l'ancre A=LA 440.
Effet : Révèle lemplacement du prototype caché. Effet : validation de l'étape 2 et indice explicite vers les Archives.
- empreinte (tactile) : Une empreinte de gants marque une pression sur le handle de la baie de transfert (haut à gauche). Lila est la seule à porter ce format de gants. - qr-win (observation) : scanner le QR avec payload WIN derrière le portrait de la salle des Archives.
Effet : Confirme le mouvement de la cible et le fait que la porte sest ouverte manuellement. Effet : déverrouille le media manager et la conclusion narrative.
- mobile (narration) : Le parchemin évoque un message : 'Je ne veux pas que lAuréole devienne un jouet des contrats militaires alors que les cobayes humains sont encore instables.'
Effet : Donne le mobile moral unique et pousse vers laccusation finale.
@@ -1,5 +1,31 @@
# Zacus Conversation Bundle (v3) # Zacus Conversation Bundle (v3)
## Purpose
Bundle de travail conversationnel/IA conservé en dehors de la source canon gameplay (`game/scenarios/*.yaml`).
## Files
- `scenario_promptable_template.yaml`: template éditable (input prompt)
- `scenario_canonical.yaml`: YAML canonique conversation bundle
- `scenario_runtime.json`: JSON runtime dérivé
- `fsm_mermaid.md`: diagramme FSM
- `CODEX_PROMPT.md`: prompt Codex prêt à l'emploi
- `zacus_v2.yaml`: narration MJ alignée bundle
- `CONVERSATION_SUMMARY.md`: synthèse des décisions
## Validation workflow
1. Installer les dépendances validateurs :
- `bash tools/setup/install_validators.sh`
2. Valider la cohérence interne du bundle :
- `python3 tools/scenario/validate_runtime_bundle.py`
3. Lancer les validateurs projet (non-régression canon/audio/printables) :
- `python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml`
- `python3 tools/scenario/export_md.py game/scenarios/zacus_v1.yaml`
- `python3 tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml`
- `python3 tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml`
## Notes
- Ce bundle n'est pas promu automatiquement dans `game/scenarios/*.yaml`.
- Toute promotion doit passer par une revue fonctionnelle dédiée.
Files: Files:
- scenario_promptable_template.yaml : your editable template (prompt_input) - scenario_promptable_template.yaml : your editable template (prompt_input)
- scenario_canonical.yaml : canonical YAML (copy into docs/... target) - scenario_canonical.yaml : canonical YAML (copy into docs/... target)
+32
View File
@@ -0,0 +1,32 @@
# Conversation bundle intake — statut courant (G0 Spec Freeze)
## Contexte
Le bundle conversationnel est maintenu dans :
`scenario-ai-coherence/zacus_conversation_bundle_v3/`.
## Critères d'acceptation (AC)
- Les fichiers du bundle sont présents localement dans le même chemin.
- Le contenu est importé tel quel (pas de transformation fonctionnelle).
- La traçabilité est mise à jour (`artifacts/...` + `evidence/manifest.json`).
- Les validateurs scénario/audio/printables existants passent après import.
- L'installation des validateurs est reproductible par script versionné.
## Non-goals
- Aucune promotion automatique de `zacus_v2.yaml` vers `game/scenarios/*.yaml` (promotion manuelle validée dans ce lot).
- Aucune modification firmware/hardware.
- Aucune refonte des manifests audio/printables.
## Risques
- Le bundle peut diverger du scénario canonique `game/scenarios/zacus_v1.yaml`.
- Les formats `scenario_runtime.json`/templates peuvent nécessiter une validation dédiée ultérieure.
## Prochaine étape proposée
- Validateur dédié implémenté: `tools/scenario/validate_runtime_bundle.py` (cohérence runtime/canonical/template).
- Diff fonctionnel implémenté: `tools/scenario/diff_gameplay_scenarios.py` (rapport markdown QA).
- Étape suivante: brancher le validateur runtime-bundle dans l'automatisation CI (gate G3 systématique).
## Statut Gates
- G0 Spec Freeze : **satisfait** pour l'import brut du bundle.
## TODO transverse (issue restante)
- Analyse transverse du repo ajoutée dans `docs/repo-deep-analysis-2026-02-25.md` pour planifier PR-A/PR-B/PR-C.
+2
View File
@@ -0,0 +1,2 @@
# Dependencies required by repository validators
PyYAML>=6.0,<7
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""Generate a functional diff report between canonical and conversation gameplay YAML files."""
from __future__ import annotations
import argparse
from datetime import date
from pathlib import Path
import sys
try:
import yaml
except ImportError: # pragma: no cover
sys.exit("Missing dependency: install PyYAML (pip install pyyaml) to diff scenario YAML files.")
def load_yaml(path: Path) -> dict:
data = yaml.safe_load(path.read_text())
if not isinstance(data, dict):
raise ValueError(f"{path} must contain a YAML mapping")
return data
def _safe_len(value) -> int:
return len(value) if isinstance(value, list) else 0
def build_report(base_path: Path, target_path: Path, base: dict, target: dict) -> str:
lines: list[str] = []
lines.append("# Rapport de diff fonctionnel — scénarios Zacus")
lines.append("")
lines.append(f"- Date: {date.today().isoformat()}")
lines.append(f"- Base canon: `{base_path}`")
lines.append(f"- Cible gameplay: `{target_path}`")
lines.append("")
lines.append("## Identité")
lines.append(f"- id: `{base.get('id')}` -> `{target.get('id')}`")
lines.append(f"- version: `{base.get('version')}` -> `{target.get('version')}`")
lines.append(f"- title: `{base.get('title')}` -> `{target.get('title')}`")
lines.append("")
lines.append("## Cadre de session")
base_players = base.get("players", {})
target_players = target.get("players", {})
lines.append(
f"- players: `{base_players.get('min')}-{base_players.get('max')}` -> `{target_players.get('min')}-{target_players.get('max')}`"
)
base_duration = base.get("duration_minutes", {})
target_duration = target.get("duration_minutes", {})
lines.append(
f"- duration_minutes: `{base_duration.get('min')}-{base_duration.get('max')}` -> `{target_duration.get('min')}-{target_duration.get('max')}`"
)
lines.append("")
lines.append("## Structure narrative")
lines.append(f"- stations: `{_safe_len(base.get('stations'))}` -> `{_safe_len(target.get('stations'))}`")
lines.append(f"- puzzles: `{_safe_len(base.get('puzzles'))}` -> `{_safe_len(target.get('puzzles'))}`")
lines.append(f"- solution_unique: `{base.get('solution_unique')}` -> `{target.get('solution_unique')}`")
lines.append("")
lines.append("## Changements clés détectés")
base_station_names = [s.get("name") for s in base.get("stations", []) if isinstance(s, dict)]
target_station_names = [s.get("name") for s in target.get("stations", []) if isinstance(s, dict)]
lines.append(f"- Stations cibles: {', '.join([str(x) for x in target_station_names])}")
base_puzzle_ids = [p.get("id") for p in base.get("puzzles", []) if isinstance(p, dict)]
target_puzzle_ids = [p.get("id") for p in target.get("puzzles", []) if isinstance(p, dict)]
lines.append(f"- IDs puzzles base: {', '.join([str(x) for x in base_puzzle_ids])}")
lines.append(f"- IDs puzzles cible: {', '.join([str(x) for x in target_puzzle_ids])}")
if base_station_names != target_station_names:
lines.append("- ✅ Les stations ont été réalignées avec le gameplay U-SON / Zone 4 / Archives.")
if base_puzzle_ids != target_puzzle_ids:
lines.append("- ✅ Les puzzles ont été remplacés pour refléter LA 440 → LEFOU → QR WIN.")
lines.append("")
lines.append("## Impact")
lines.append("- Source de vérité gameplay maintenue dans `game/scenarios/*.yaml`.")
lines.append("- Bundle conversationnel conservé comme matériau de travail parallèle.")
lines.append("- Validation G3 requise via validateurs scénario/audio/printables + runtime bundle.")
lines.append("")
return "\n".join(lines)
def main() -> int:
parser = argparse.ArgumentParser(description="Generate functional diff report between two scenario YAML files.")
parser.add_argument("--base", default="scenario-ai-coherence/zacus_conversation_bundle_v3/zacus_v2.yaml")
parser.add_argument("--target", default="game/scenarios/zacus_v1.yaml")
parser.add_argument("--out", default=f"artifacts/qa-test/{date.today().isoformat()}/gameplay-functional-diff.md")
args = parser.parse_args()
base_path = Path(args.base)
target_path = Path(args.target)
out_path = Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
try:
base = load_yaml(base_path)
target = load_yaml(target_path)
except (OSError, ValueError, yaml.YAMLError) as exc:
print(f"[scenario-diff] error -> {exc}")
return 1
report = build_report(base_path, target_path, base, target)
out_path.write_text(report)
print(f"[scenario-diff] ok report={out_path}")
return 0
if __name__ == "__main__":
sys.exit(main())
+187
View File
@@ -0,0 +1,187 @@
#!/usr/bin/env python3
"""Validate coherence of Zacus conversation bundle artifacts."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
try:
import yaml
except ImportError: # pragma: no cover
sys.exit("Missing dependency: install PyYAML (pip install pyyaml) to validate bundle YAML files.")
class ValidationError(Exception):
pass
def read_json(path: Path) -> dict:
try:
return json.loads(path.read_text())
except OSError as exc:
raise ValidationError(f"Cannot read JSON file {path}: {exc}")
except json.JSONDecodeError as exc:
raise ValidationError(f"Invalid JSON in {path}: {exc}")
def read_yaml(path: Path) -> dict:
try:
return yaml.safe_load(path.read_text())
except OSError as exc:
raise ValidationError(f"Cannot read YAML file {path}: {exc}")
except yaml.YAMLError as exc:
raise ValidationError(f"Invalid YAML in {path}: {exc}")
def extract_template_scenario(template: dict) -> tuple[str, int]:
prompt_input = template.get("prompt_input")
if not isinstance(prompt_input, dict):
raise ValidationError("template missing `prompt_input` mapping")
scenario = prompt_input.get("scenario")
if not isinstance(scenario, dict):
raise ValidationError("template missing `prompt_input.scenario` mapping")
scenario_id = scenario.get("id")
version = scenario.get("version")
if not isinstance(scenario_id, str) or not scenario_id:
raise ValidationError("template `prompt_input.scenario.id` must be a non-empty string")
if not isinstance(version, int):
raise ValidationError("template `prompt_input.scenario.version` must be an integer")
return scenario_id, version
def extract_canonical(canonical: dict) -> tuple[str, int, list[str], str]:
scenario = canonical.get("scenario")
if not isinstance(scenario, dict):
raise ValidationError("canonical missing `scenario` mapping")
scenario_id = scenario.get("id")
version = scenario.get("version")
initial_step = scenario.get("initial_step")
steps = canonical.get("steps_runtime_order")
if not isinstance(scenario_id, str) or not scenario_id:
raise ValidationError("canonical `scenario.id` must be a non-empty string")
if not isinstance(version, int):
raise ValidationError("canonical `scenario.version` must be an integer")
if not isinstance(initial_step, str) or not initial_step:
raise ValidationError("canonical `scenario.initial_step` must be a non-empty string")
if not isinstance(steps, list) or not steps:
raise ValidationError("canonical `steps_runtime_order` must be a non-empty list")
step_ids: list[str] = []
for idx, step in enumerate(steps, start=1):
if not isinstance(step, dict):
raise ValidationError(f"canonical step #{idx} must be a mapping")
step_id = step.get("step_id")
if not isinstance(step_id, str) or not step_id:
raise ValidationError(f"canonical step #{idx} missing non-empty `step_id`")
step_ids.append(step_id)
if initial_step not in step_ids:
raise ValidationError("canonical initial_step not found in canonical steps_runtime_order")
return scenario_id, version, step_ids, initial_step
def extract_runtime(runtime: dict) -> tuple[str, int, list[str], str]:
scenario = runtime.get("scenario")
if not isinstance(scenario, dict):
raise ValidationError("runtime missing `scenario` mapping")
scenario_id = scenario.get("id")
version = scenario.get("version")
initial_step = scenario.get("initial_step")
steps = runtime.get("steps_runtime_order")
if not isinstance(scenario_id, str) or not scenario_id:
raise ValidationError("runtime `scenario.id` must be a non-empty string")
if not isinstance(version, int):
raise ValidationError("runtime `scenario.version` must be an integer")
if not isinstance(initial_step, str) or not initial_step:
raise ValidationError("runtime `scenario.initial_step` must be a non-empty string")
if not isinstance(steps, list) or not steps:
raise ValidationError("runtime `steps_runtime_order` must be a non-empty list")
step_ids: list[str] = []
for idx, step in enumerate(steps, start=1):
if not isinstance(step, dict):
raise ValidationError(f"runtime step #{idx} must be a mapping")
step_id = step.get("step_id")
if not isinstance(step_id, str) or not step_id:
raise ValidationError(f"runtime step #{idx} missing non-empty `step_id`")
step_ids.append(step_id)
if initial_step not in step_ids:
raise ValidationError("runtime initial_step not found in runtime steps_runtime_order")
return scenario_id, version, step_ids, initial_step
def validate_bundle(runtime_path: Path, canonical_path: Path, template_path: Path) -> None:
runtime = read_json(runtime_path)
canonical = read_yaml(canonical_path)
template = read_yaml(template_path)
canonical_id, canonical_version, canonical_steps, canonical_initial = extract_canonical(canonical)
runtime_id, runtime_version, runtime_steps, runtime_initial = extract_runtime(runtime)
template_id, template_version = extract_template_scenario(template)
errors: list[str] = []
if canonical_id != runtime_id:
errors.append(f"scenario id mismatch: canonical={canonical_id} runtime={runtime_id}")
if canonical_version != runtime_version:
errors.append(f"scenario version mismatch: canonical={canonical_version} runtime={runtime_version}")
if canonical_initial != runtime_initial:
errors.append(f"initial_step mismatch: canonical={canonical_initial} runtime={runtime_initial}")
if canonical_steps != runtime_steps:
errors.append("steps_runtime_order mismatch between canonical and runtime")
if template_id != canonical_id:
errors.append(f"template id mismatch: template={template_id} canonical={canonical_id}")
if template_version != canonical_version:
errors.append(f"template version mismatch: template={template_version} canonical={canonical_version}")
if errors:
raise ValidationError("; ".join(errors))
print(
"[runtime-bundle-validate] ok "
f"id={canonical_id} version={canonical_version} steps={len(canonical_steps)} initial={canonical_initial}"
)
def main() -> int:
parser = argparse.ArgumentParser(description="Validate conversation bundle runtime/canonical/template coherence.")
parser.add_argument(
"--runtime",
default="scenario-ai-coherence/zacus_conversation_bundle_v3/scenario_runtime.json",
help="Path to scenario_runtime.json",
)
parser.add_argument(
"--canonical",
default="scenario-ai-coherence/zacus_conversation_bundle_v3/scenario_canonical.yaml",
help="Path to scenario_canonical.yaml",
)
parser.add_argument(
"--template",
default="scenario-ai-coherence/zacus_conversation_bundle_v3/scenario_promptable_template.yaml",
help="Path to scenario_promptable_template.yaml",
)
args = parser.parse_args()
try:
validate_bundle(Path(args.runtime), Path(args.canonical), Path(args.template))
except ValidationError as exc:
print(f"[runtime-bundle-validate] error -> {exc}")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
cat <<'USAGE'
Usage: bash tools/setup/install_validators.sh
Install Python dependencies required by repository validators.
Environment override:
PYTHON_BIN Python executable to use (default: python3)
USAGE
exit 0
fi
PYTHON_BIN="${PYTHON_BIN:-python3}"
REQ_FILE="tools/requirements/validators.txt"
if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then
echo "[install-validators] error: python interpreter not found: $PYTHON_BIN" >&2
exit 1
fi
if [[ ! -f "$REQ_FILE" ]]; then
echo "[install-validators] error: missing requirements file: $REQ_FILE" >&2
exit 1
fi
"$PYTHON_BIN" -m pip install --user -r "$REQ_FILE"
echo "[install-validators] ok installed requirements from $REQ_FILE"