Fusion de la MR !5 : scénarios en Gherkin standard ; ADR amendables

This commit was merged in pull request #5.
This commit is contained in:
2026-11-02 12:53:46 +00:00
14 changed files with 196 additions and 82 deletions
+2 -2
View File
@@ -16,7 +16,7 @@ est la feuille de route.
- **Licence** : tout le code sous GPLv3. Les contenus pédagogiques (leçons, textes) sous CC BY-SA 4.0.
- **Langue** : interface, contenus, commentaires de code et messages de commit en français ; identifiants du code en anglais.
- **Architecture et code** : simplicité d'abord, pas de sur-ingénierie ; domaine pur, moteur derrière `engine.h`, parties en journal d'événements ; règles complètes dans l'ADR 0007, à transmettre à chaque sous-agent.
- **Glossaire et aide** : chaque terme du jeu a sa fiche dans `docs/glossaire/` (traduction anglaise = nom dans le code) ; chaque fonction visible a son scénario dans `docs/scenarios/`, à la fois aide de l'app et test de bout en bout. Une seule source, jamais dupliquée (ADR 0008).
- **Glossaire et aide** : chaque terme du jeu a sa fiche dans `docs/glossaire/` (traduction anglaise = nom dans le code) ; chaque fonction visible a son scénario Gherkin dans `docs/scenarios/`, à la fois aide de l'app et test d'intégration sur le domaine et le moteur. Une seule source, jamais dupliquée (ADR 0008).
- **Communauté de départ** : l'app sera d'abord diffusée auprès des joueurs de l'Atelier du Huit, café associatif à Cugnaux. Elle doit être accessible à de vrais débutants.
- **Identité visuelle** : définie par le brief créatif (https://claude.ai/artifact/36WMwZWqvMxXvSSVBJTgwd). Les choix de produit, de ton et d'identité appartiennent au porteur du projet ; le rôle de Claude Code est technique : les mettre en œuvre et signaler leurs conséquences techniques.
@@ -55,7 +55,7 @@ Des cibles plus fines (`make engine`, `make engine-wasm`, `make bench`…) sont
4. **Ne jamais masquer un échec.** Pas de test désactivé, pas de résultat simulé. Dire clairement ce qui bloque et la cause probable.
5. **Commits fréquents**, messages courts en français, sur une branche dédiée. Jamais de commit direct sur `main` : chaque changement passe par une merge request Gitea (`tea pulls create --login sans-pub`), décrite selon `docs/organisation.md`. Claude Code la fusionne lui-même une fois `make lint` et `make test` au vert.
6. **Dépendances** : peu, maintenues, compatibles GPLv3. Signaler chaque nouvelle dépendance et sa licence.
7. **Journal** : `docs/journal.md` reçoit une entrée datée à la fin de chaque session (fait, décidé, reste à faire). Les décisions structurantes vont dans un ADR (`docs/adr/`), jamais réécrit une fois accepté.
7. **Journal** : `docs/journal.md` reçoit une entrée datée à la fin de chaque session (fait, décidé, reste à faire). Les décisions structurantes vont dans un ADR (`docs/adr/`), amendé (section datée) mais jamais réécrit une fois accepté.
## Sources du moteur
+2
View File
@@ -21,6 +21,8 @@ précisément ici dès que du code en sera repris :
## Outils de développement
- **ShellCheck** (GPLv3), analyse des scripts shell, utilisé via Docker.
- **Node.js** (MIT), image `node:22.11.0-alpine3.20`, et **@cucumber/gherkin**
(MIT), analyseur Gherkin qui valide les scénarios de `docs/scenarios/`.
## Remerciements
+5 -1
View File
@@ -11,8 +11,10 @@ BUILD_DIR := build
# Outils lancés dans Docker (aucune installation sur le PC).
SHELLCHECK_IMAGE := koalaman/shellcheck-alpine:v0.10.0
# Image construite ici : changer le numéro à chaque modification de docker/gherkin/.
GHERKIN_IMAGE := ustagammon/gherkin:2
export SHELLCHECK_IMAGE
export SHELLCHECK_IMAGE GHERKIN_IMAGE
.PHONY: help setup build test lint deploy clean
@@ -25,6 +27,8 @@ setup: ## Installe la chaîne d'outils (images Docker) ; sans effet si déjà fa
@command -v docker >/dev/null || { echo "Docker est requis : sudo apt install docker.io"; exit 1; }
@docker image inspect $(SHELLCHECK_IMAGE) >/dev/null 2>&1 \
|| docker pull $(SHELLCHECK_IMAGE)
@docker image inspect $(GHERKIN_IMAGE) >/dev/null 2>&1 \
|| docker build -q -t $(GHERKIN_IMAGE) docker/gherkin
@echo "Chaîne d'outils prête."
build: ## Compile le moteur (si nécessaire) puis l'application
+8
View File
@@ -0,0 +1,8 @@
# Analyseur Gherkin officiel de Cucumber, pour valider docs/scenarios/ (ADR 0008).
FROM node:22.11.0-alpine3.20
WORKDIR /opt/gherkin
RUN npm install --no-audit --no-fund @cucumber/gherkin@29.0.0 @cucumber/messages@26.0.1
COPY validate.mjs .
ENTRYPOINT ["node", "/opt/gherkin/validate.mjs"]
+46
View File
@@ -0,0 +1,46 @@
// Valide des fichiers .feature avec l'analyseur Gherkin officiel.
// Usage : node validate.mjs fichier.feature…
// Sort en erreur si un fichier n'est pas du Gherkin valide, n'est pas en
// français (# language: fr), ne contient aucun scénario, ou a une étape égarée
// dans une description (signe d'un mot-clé mal orthographié, que Gherkin
// accepte alors comme texte libre).
import { readFileSync } from 'node:fs'
import { AstBuilder, GherkinClassicTokenMatcher, Parser } from '@cucumber/gherkin'
import { IdGenerator } from '@cucumber/messages'
const STEP = /^\s*(Étant donné|Etant donné|Quand|Alors|Et|Mais)\b/
function strayStep(description) {
return description.split('\n').find((line) => STEP.test(line))
}
let errors = 0
for (const file of process.argv.slice(2)) {
const parser = new Parser(new AstBuilder(IdGenerator.uuid()), new GherkinClassicTokenMatcher())
try {
const { feature } = parser.parse(readFileSync(file, 'utf8'))
if (!feature) {
throw new Error('aucune fonctionnalité')
}
if (feature.language !== 'fr') {
throw new Error(`langue « ${feature.language} » au lieu de « fr »`)
}
const scenarios = feature.children.filter((child) => child.scenario).map((child) => child.scenario)
if (scenarios.length === 0) {
throw new Error('aucun scénario')
}
for (const description of [feature.description, ...scenarios.map((scenario) => scenario.description)]) {
const line = strayStep(description)
if (line) {
throw new Error(`étape hors d'un scénario (mot-clé mal orthographié ?) : ${line.trim()}`)
}
}
} catch (error) {
console.log(`${file} : ${error.message}`)
errors++
}
}
process.exit(errors === 0 ? 0 : 1)
@@ -40,3 +40,15 @@ dans un ADR, dans la description de la merge request ou dans le journal.
- `make test` vérifie la forme des ADR (numérotation, statut, sections, index).
- Les ADR 0002 à 0006 reprennent des décisions antérieures à celui-ci, avec
leur date d'origine.
## Amendements
### 2026-11-02 — Amender plutôt que multiplier les ADR
Décidé par Pierre, pour ne pas créer un ADR à chaque précision. Un ADR accepté
peut recevoir un amendement : une section « Amendements » en fin de fichier,
une sous-section datée par amendement, qui dit qui l'a décidé, ce qu'il change
et pourquoi. Le texte d'origine n'est pas réécrit ; s'il le faut, une courte
mention renvoie à l'amendement. Un nouvel ADR (qui fait passer l'ancien au
statut « remplacé par NNNN ») reste nécessaire quand la décision elle-même est
abandonnée au profit d'une autre.
@@ -67,7 +67,8 @@ documentation affichée est donc toujours celle de la version installée.
- **Documentation utilisateur rédigée à part des tests** : deux textes qui
divergent tôt ou tard.
- **Gherkin classique (`.feature`)** : exécutable, mais austère comme page
d'aide. Le Markdown avec Gherkin donne les deux.
d'aide. Le Markdown avec Gherkin donne les deux. (Choix inversé par
l'amendement du 2026-11-02, ci-dessous.)
- **Aide intégrée en dur dans l'application** : une seconde source à
maintenir.
@@ -80,3 +81,30 @@ documentation affichée est donc toujours celle de la version installée.
de glossaire dans la même merge request.
- Toute fonction visible par l'utilisateur demande son scénario dans la même
merge request.
## Amendements
### 2026-11-02 — Gherkin standard, exécution sur le domaine et le moteur
Décidé par Pierre. Remplace, dans la décision ci-dessus, le format Markdown
avec Gherkin et l'exécution de bout en bout par l'interface.
- **Format** : Gherkin standard, fichiers `.feature` commençant par
`# language: fr`. Le Markdown avec Gherkin est la variante la moins outillée
de Cucumber, et sa prise en charge des mots-clés français n'est pas assurée ;
le Gherkin standard est compris par tous les outils. Le paragraphe libre sous
`Fonctionnalité:` porte l'introduction destinée au joueur ; l'application met
le fichier en forme comme page d'aide.
- **Validation dès maintenant** : `make test` passe chaque scénario dans
l'analyseur officiel de Cucumber (`@cucumber/gherkin`, MIT, image
`docker/gherkin/`), et refuse une étape égarée dans une description, signe
d'un mot-clé mal orthographié que Gherkin accepterait comme texte libre.
- **Exécution** : piloter une application native de bout en bout serait
compliqué. Les scénarios sont donc exécutés comme tests d'intégration :
leurs étapes appellent directement le domaine, branché sur le vrai moteur.
Ils s'écrivent en conséquence avec des intentions (« vous commencez une
nouvelle partie ») et non des gestes d'interface (« vous touchez le bouton
… ») : le même scénario reste vrai quelle que soit l'interface.
- **Perspective** : si les scénarios peuvent un jour être joués dans une
instance réelle de l'application, la même exécution produira les captures
d'écran de l'aide, toujours à jour avec la version installée.
+3 -2
View File
@@ -1,8 +1,9 @@
# Décisions d'architecture (ADR)
Un fichier par décision structurante, au format de `modele.md`. Un ADR accepté
n'est plus réécrit : une décision qui change donne un nouvel ADR, et l'ancien
passe au statut « remplacé par NNNN ». Voir l'ADR 0001.
n'est pas réécrit : une précision s'ajoute en amendement daté à la fin du
fichier ; une décision abandonnée au profit d'une autre donne un nouvel ADR, et
l'ancien passe au statut « remplacé par NNNN ». Voir l'ADR 0001.
| N° | Décision | Statut |
| --- | --- | --- |
+1 -1
View File
@@ -21,7 +21,7 @@ Acquis : socle du dépôt, organisation (ADR 0001, 0003, 0006), source du moteur
| Mesures | Pierre relève les mesures sur le Fairphone 4 | ⏳ | |
| Décision | Socle PWA ou Android natif, consigné dans un ADR — validation de Pierre | ⏳ | |
| 0c | Android natif (NDK, Gradle en ligne de commande, `make deploy` via adb) | ⏸ si 0b échoue | |
| 0d | Plateau minimal jouable sur le socle retenu ; vérifié avec Pierre en suivant `docs/scenarios/jouer-une-partie.feature.md` | ⏳ | |
| 0d | Plateau minimal jouable sur le socle retenu ; vérifié avec Pierre en suivant `docs/scenarios/jouer-une-partie.feature` | ⏳ | |
**Critère de fin** : une partie complète jouée sur le Fairphone 4, moins de 2 s
par coup de l'IA en niveau standard, tous les tests au vert.
+2 -2
View File
@@ -27,10 +27,10 @@ Deux familles de documents, de durée de vie différente (ADR 0001).
| Document | Contenu | Mis à jour |
| --- | --- | --- |
| `docs/cadrage.md` | Vision, périmètre, architecture cible | Quand Pierre change le cadrage |
| `docs/adr/` | Un ADR par décision structurante ; index dans `docs/adr/README.md` | À chaque décision ; jamais réécrit une fois accepté |
| `docs/adr/` | Un ADR par décision structurante ; index dans `docs/adr/README.md` | À chaque décision ; une fois accepté, amendé (section datée) mais pas réécrit |
| `docs/journal.md` | Une entrée datée par session : fait, décidé, suite | En fin de session |
| `docs/glossaire/` | Une fiche par terme du jeu, avec sa traduction anglaise utilisée dans le code (ADR 0008) | Avec chaque nouveau terme du domaine |
| `docs/scenarios/` | Documentation utilisateur en scénarios, aussi tests de bout en bout (ADR 0008) | Avec chaque fonction visible |
| `docs/scenarios/` | Documentation utilisateur en scénarios Gherkin, aussi tests d'intégration (ADR 0008) | Avec chaque fonction visible |
| Merge requests (git.sans.pub) | Le détail de chaque changement : pourquoi, comment, vérification | À chaque changement |
**Temporaires** : ils décrivent le travail à faire et disparaissent une fois
+27 -15
View File
@@ -2,29 +2,41 @@
La documentation utilisateur d'Ustagammon : un fichier par tâche réelle d'un
joueur, écrit pour lui. Ces fichiers sont la seule source (ADR 0008) :
l'application les affiche comme pages d'aide, et ils servent de tests de bout
en bout.
l'application les met en forme comme pages d'aide, et ils sont exécutés comme
tests d'intégration sur le domaine et le moteur réel.
Tant que l'outil d'exécution n'est pas choisi (avec le socle, fin de
l'étape 0), les scénarios se déroulent à la main : ils servent alors de guide
de test.
Tant que le domaine n'existe pas, les scénarios se déroulent à la main : ils
servent alors de guide de test.
## Format
Markdown avec Gherkin (`.feature.md`), étapes en français :
Gherkin standard (`.feature`), en français, validé par l'analyseur officiel de
Cucumber (`make test`) :
- `# Fonctionnalité : <la tâche>`, puis un court paragraphe qui explique à quoi
elle sert, pour le joueur ;
- `## Scénario : <un cas précis>` ;
- des étapes en liste : `* Étant donné …`, `* Quand …`, `* Et …`, `* Alors …`.
```gherkin
# language: fr
Fonctionnalité: <la tâche>
Règles d'écriture : la deuxième personne (« vous »), les mots du glossaire
(`docs/glossaire/`), des étapes que le joueur peut faire et vérifier lui-même,
pas de détail technique. La syntaxe exacte sera validée avec l'outil
d'exécution.
Un court paragraphe qui explique au joueur à quoi elle sert.
Scénario: <un cas précis>
Étant donné que <la situation de départ>
Quand <ce que fait le joueur>
Alors <ce qu'il constate>
Et <autre constat>
```
Règles d'écriture :
- la deuxième personne (« vous ») et les mots du glossaire (`docs/glossaire/`) ;
- des intentions, pas des gestes d'interface : « vous commencez une nouvelle
partie », pas « vous touchez le bouton Nouvelle partie ». Le même scénario
reste vrai quelle que soit l'interface, et peut piloter directement le
domaine ;
- des constats vérifiables par le joueur, sans détail technique.
## Scénarios
| Fichier | Tâche | Depuis |
| --- | --- | --- |
| [jouer-une-partie.feature.md](jouer-une-partie.feature.md) | Jouer une partie contre l'ordinateur | Étape 0d |
| [jouer-une-partie.feature](jouer-une-partie.feature) | Jouer une partie contre l'ordinateur | Étape 0d |
+45
View File
@@ -0,0 +1,45 @@
# language: fr
Fonctionnalité: Jouer une partie contre l'ordinateur
Vous jouez une partie complète contre l'ordinateur, qui s'appuie sur le
moteur GNU Backgammon. L'application tire les dés, n'accepte que les coups
permis par les règles, et l'ordinateur joue et décide du videau tout seul.
Scénario: Commencer une partie
Quand vous commencez une nouvelle partie
Alors les pions sont en position de départ
Et le premier jet de dés désigne qui commence
Scénario: Jouer un coup permis
Étant donné que c'est à vous de jouer avec le jet 3-1
Quand vous jouez 8/5 6/5
Alors votre coup est accepté
Et c'est au tour de l'ordinateur
Scénario: Un coup interdit est refusé
Étant donné que c'est à vous de jouer avec le jet 3-1
Quand vous essayez de jouer 8/4
Alors votre coup est refusé
Et la position n'a pas changé
Scénario: L'ordinateur joue
Étant donné que c'est au tour de l'ordinateur
Quand ses dés sont tirés
Alors il joue le coup recommandé par le moteur en moins de 2 secondes
Scénario: Prendre le double de l'ordinateur
Étant donné que l'ordinateur vous propose de doubler
Quand vous prenez
Alors la partie continue à valeur doublée
Et le videau est de votre côté : vous seul pourrez redoubler
Scénario: Passer le double de l'ordinateur
Étant donné que l'ordinateur vous propose de doubler
Quand vous passez
Alors la partie s'arrête
Et vous perdez la partie à sa valeur d'avant le double
Scénario: Finir la partie
Étant donné qu'un joueur a sorti tous ses pions
Alors la partie s'arrête
Et le résultat indique le gagnant et les points gagnés
@@ -1,48 +0,0 @@
# Fonctionnalité : Jouer une partie contre l'ordinateur
Vous jouez une partie complète contre l'ordinateur, qui s'appuie sur le moteur
GNU Backgammon. L'application tire les dés, vous propose uniquement des coups
permis par les règles, et l'ordinateur joue et décide du videau tout seul.
## Scénario : Commencer une partie
* Étant donné que vous avez ouvert Ustagammon
* Quand vous touchez « Nouvelle partie »
* Alors le plateau s'affiche avec les pions en position de départ
* Et le premier jet de dés désigne qui commence
## Scénario : Jouer votre coup
* Étant donné que c'est à vous de jouer et que les dés sont tirés
* Quand vous touchez un pion puis la case où vous voulez l'amener
* Alors le pion s'y déplace si ce déplacement est permis par les dés
* Et un déplacement interdit est refusé sans changer le plateau
* Quand vous avez utilisé vos dés
* Alors votre coup est validé et c'est au tour de l'ordinateur
## Scénario : L'ordinateur joue
* Étant donné que c'est au tour de l'ordinateur
* Quand ses dés sont tirés
* Alors il joue son coup en moins de 2 secondes
* Et son coup s'affiche sur le plateau
## Scénario : Prendre le double de l'ordinateur
* Étant donné que l'ordinateur vous propose de doubler
* Quand vous touchez « Prendre »
* Alors la partie continue à valeur doublée
* Et le videau passe de votre côté : vous seul pourrez redoubler
## Scénario : Passer le double de l'ordinateur
* Étant donné que l'ordinateur vous propose de doubler
* Quand vous touchez « Passer »
* Alors la partie s'arrête
* Et vous perdez la partie à sa valeur d'avant le double
## Scénario : Finir la partie
* Étant donné qu'un joueur a sorti tous ses pions
* Alors la partie s'arrête
* Et le résultat indique le gagnant et les points gagnés : simple, gammon ou backgammon, multipliés par le videau
+14 -10
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Vérifie la forme des scénarios d'utilisation (docs/scenarios/, ADR 0008) :
# un titre « Fonctionnalité », au moins un « Scénario », des étapes qui
# commencent par un mot-clé Gherkin français, chaque fichier dans l'index.
# Vérifie les scénarios d'utilisation (docs/scenarios/, ADR 0008) :
# chaque .feature est du Gherkin valide en français pour l'analyseur officiel
# de Cucumber (dans Docker), et figure dans l'index README.md.
set -euo pipefail
cd "$(dirname "$0")/../docs/scenarios"
@@ -12,14 +12,18 @@ report() {
errors=$((errors + 1))
}
for f in *.feature.md; do
head -n1 "$f" | grep -qE '^# Fonctionnalité : .+' \
|| report "$f : la première ligne doit être « # Fonctionnalité : … »"
grep -qE '^## Scénario : .+' "$f" || report "$f : aucun « ## Scénario : … »"
while IFS= read -r step; do
report "$f : étape sans mot-clé Gherkin : $step"
done < <(grep -E '^\* ' "$f" | grep -vE '^\* (Étant donné|Quand|Alors|Et|Mais) ' || true)
features=(*.feature)
[ -e "${features[0]}" ] || report "aucun scénario dans docs/scenarios/"
for f in "${features[@]}"; do
[ -e "$f" ] || continue
grep -qF "]($f)" README.md || report "$f : absent de l'index README.md"
done
if ! docker image inspect "${GHERKIN_IMAGE:?lancer via make test}" >/dev/null 2>&1; then
report "Analyseur Gherkin absent : lancer d'abord make setup"
elif ! docker run --rm -v "$PWD:/src:ro" -w /src "$GHERKIN_IMAGE" "${features[@]}"; then
report "scénarios invalides (voir ci-dessus)"
fi
[ "$errors" -eq 0 ]