Skip to content
This repository was archived by the owner on Aug 26, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
dfa0c6c
feat(lib): add rigor predicate helpers and show_section
Dxsk Apr 11, 2026
121c3bf
feat(quest-01): teach init.defaultBranch=main as global config
Dxsk Apr 11, 2026
73607cd
fix(quest-04): enforce remote name, branch, and cross-repo consistency
Dxsk Apr 11, 2026
13d56bb
fix(quest-04): replace origine with origin in code blocks
Dxsk Apr 11, 2026
c0048f1
fix(quest-08): use git switch instead of deprecated git checkout
Dxsk Apr 11, 2026
94783dd
fix(quest-17): correctly count workflow steps in verifier
Dxsk Apr 11, 2026
59d575c
feat(lint): add lint-content target for git command regression preven…
Dxsk Apr 11, 2026
5edb304
test(quest-17): add boundary test for exactly one indented step
Dxsk Apr 11, 2026
a1c1fc0
fix(quest-04): require clone-depuis-bare to have a remote named 'origin'
Dxsk Apr 11, 2026
d3b4a48
fix(quest-08): replace remaining git checkout with git switch
Dxsk Apr 11, 2026
4220b7c
fix(quests): make git init commands robust with explicit -b main
Dxsk Apr 11, 2026
3f52073
fix(quest-09): standardize remote name to 'origin' instead of 'portail'
Dxsk Apr 11, 2026
e596481
feat(lint): extend lint-content to catch any git checkout <branch>
Dxsk Apr 11, 2026
6dc130f
fix(quest-04): verify mon-archive remote (the actual pedagogical action)
Dxsk Apr 11, 2026
9ffc93d
fix(quest-09): translate branch name to English in EN version
Dxsk Apr 12, 2026
d8618ee
fix(quest-02): require parchemin.txt non-empty and exact filename match
Dxsk Apr 16, 2026
a932f8e
fix(quest-03): require mission.txt to differ from the shipped template
Dxsk Apr 16, 2026
ef357d5
fix(quest-05): use git check-ignore instead of hardcoded .log regex
Dxsk Apr 16, 2026
4d85bfb
fix(quest-06): assert the student is back on main at the end
Dxsk Apr 16, 2026
8665152
fix(quest-10): add final 'back on main/master' assertion
Dxsk Apr 16, 2026
f1085e2
fix(quest-11): require an empty stash stack and a final main branch
Dxsk Apr 16, 2026
1422392
fix(quest-13): require all tags to follow semver (not just one)
Dxsk Apr 16, 2026
7d6f23f
fix(quest-14): require pre-commit hook to have a non-zero exit path
Dxsk Apr 16, 2026
99c6cb1
fix(quest-15): enforce remote name 'origin' and final main branch
Dxsk Apr 16, 2026
df2c7f9
fix(quest-18): skip comment lines when checking for deploy/production
Dxsk Apr 16, 2026
334516a
fix(quest-19): validate CI config content, not just file existence
Dxsk Apr 16, 2026
8b276c9
fix(quest-12): detect cherry-pick via commit subject, not reflog grep
Dxsk Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,16 @@ package: ## Build release zip locally (same output as CI)
# -----------------------------------------------------------------------------

.PHONY: lint
lint: lint-spell lint-i18n lint-nav lint-links lint-a11y lint-shell lint-ci lint-powershell ## Run all linters
lint: lint-spell lint-content lint-i18n lint-nav lint-links lint-a11y lint-shell lint-ci lint-powershell ## Run all linters

.PHONY: lint-spell
lint-spell: ## Run cspell on FR + EN content
npm run spell

.PHONY: lint-content
lint-content: ## Check content for forbidden git command patterns (regression prevention)
@scripts/makefile/run-lint-content.sh

.PHONY: lint-i18n
lint-i18n: ## Check FR/EN page parity
npm run check:i18n
Expand Down Expand Up @@ -136,7 +140,7 @@ ifeq ($(BATS),)
@exit 1
else
@echo "▶ Running bats tests…"
bats $(TESTS_DIR)
bats $(TESTS_DIR) tests/lib
endif

.PHONY: test-pester
Expand Down
2 changes: 2 additions & 0 deletions exercises/01-la-guilde-des-archivistes/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ show_banner "$QUEST_TITLE"
check_step 1 "Git est installé" 'command -v git >/dev/null 2>&1'
check_step 2 "Ton nom est configuré" '[ -n "$(git config --global user.name)" ]'
check_step 3 "Ton email est configuré" '[ -n "$(git config --global user.email)" ]'
check_step 4 "Ta branche par défaut est configurée sur 'main'" \
'[ "$(git config --global init.defaultBranch)" = "main" ]'

show_score
6 changes: 3 additions & 3 deletions exercises/02-les-trois-salles-du-savoir/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ check_step 1 \
"git rev-parse --is-inside-work-tree" \
|| true

# ── Step 2 : Le fichier parchemin.txt existe ───────────────────────────────
# ── Step 2 : Le fichier parchemin.txt existe et contient du texte ──────────
check_step 2 \
"Le fichier parchemin.txt existe dans la Salle de Travail" \
"test -f parchemin.txt" \
"test -s parchemin.txt" \
|| true

# ── Step 3 : parchemin.txt est dans le staging area ────────────────────────
check_step 3 \
"parchemin.txt est dans la Salle de Préparation (staging area)" \
"git diff --cached --name-only | grep -q parchemin.txt" \
"git diff --cached --name-only | grep -qFx parchemin.txt" \
|| true

# ── Step 4 : Pas encore de commit (pas de HEAD valide) ─────────────────────
Expand Down
4 changes: 4 additions & 0 deletions exercises/03-le-premier-parchemin/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,8 @@ check_step 4 "Ton premier commit a un message personnalisé" \
&& [ "$premier_msg" != "init" ]
'

# --- Step 5 : mission.txt a été modifié depuis le parchemin fourni ---
check_step 5 "Tu as complété le parchemin (mission.txt diffère du modèle fourni)" \
'! cmp -s mission.txt "$SCRIPT_DIR/parchemins/mission.txt"'

show_score
55 changes: 46 additions & 9 deletions exercises/04-larchive-est-partout/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,62 @@ WORKDIR="$(cd "$WORKDIR" 2>/dev/null && pwd)" || {

printf " %sDossier analyse : %s%s\n\n" "${CLR_CYAN}" "$WORKDIR" "${CLR_RESET}"

show_section "Setup des depots"

# ---- Step 1 : ma-copie/ existe et c'est un repo git ----
check_step 1 "ma-copie/ existe et c'est un repo Git" \
'[ -d "$WORKDIR/ma-copie/.git" ]'

# ---- Step 2 : ma-copie a une remote origin pointant vers un chemin local ----
check_step 2 "ma-copie/ a une remote origin vers un chemin local" \
'grep -q "url = " "$WORKDIR/ma-copie/.git/config" 2>/dev/null && \
! grep "url = http" "$WORKDIR/ma-copie/.git/config" >/dev/null 2>&1 && \
! grep "url = git@" "$WORKDIR/ma-copie/.git/config" >/dev/null 2>&1'
# ---- Step 2 : ma-copie/ a une remote nommee 'origin' ----
check_step 2 "ma-copie/ a une remote nommee 'origin'" \
'assert_remote_name "$WORKDIR/ma-copie" "origin"'

# ---- Step 3 : ma-copie/ est sur la branche 'main' ----
check_step 3 "ma-copie/ est sur la branche 'main'" \
'assert_branch_is "$WORKDIR/ma-copie" "main"'

show_section "Depot central (bare)"

# ---- Step 3 : archive-centrale.git/ est un bare repo ----
check_step 3 "archive-centrale.git/ existe et c'est un bare repo" \
# ---- Step 4 : archive-centrale.git/ est un bare repo ----
check_step 4 "archive-centrale.git/ existe et c'est un bare repo" \
'[ -f "$WORKDIR/archive-centrale.git/HEAD" ] && \
[ -d "$WORKDIR/archive-centrale.git/objects" ] && \
[ -d "$WORKDIR/archive-centrale.git/refs" ] && \
! [ -d "$WORKDIR/archive-centrale.git/.git" ]'

# ---- Step 4 : clone-depuis-bare/ existe et c'est un repo git ----
check_step 4 "clone-depuis-bare/ existe et c'est un repo Git" \
# ---- Step 5 : archive-centrale.git/HEAD pointe vers main ----
check_step 5 "archive-centrale.git/ pointe HEAD vers main" \
'[ "$(cat "$WORKDIR/archive-centrale.git/HEAD" 2>/dev/null)" = "ref: refs/heads/main" ]'

show_section "Clone depuis le bare"

# ---- Step 6 : clone-depuis-bare/ existe et c'est un repo git ----
check_step 6 "clone-depuis-bare/ existe et c'est un repo Git" \
'[ -d "$WORKDIR/clone-depuis-bare/.git" ]'

# ---- Step 7 : clone-depuis-bare/ est sur la branche 'main' ----
check_step 7 "clone-depuis-bare/ est sur la branche 'main'" \
'assert_branch_is "$WORKDIR/clone-depuis-bare" "main"'

# ---- Step 8 : clone-depuis-bare/ a une remote nommee 'origin' ----
check_step 8 "clone-depuis-bare/ a une remote nommee 'origin'" \
'assert_remote_name "$WORKDIR/clone-depuis-bare" "origin"'

show_section "Depot source lie au bare"

# ---- Step 9 : mon-archive/ a une remote nommee 'origin' ----
# This is the pedagogical action taught in the quest body: inside
# mon-archive, the student must 'git remote add origin ../archive-centrale.git'
# and 'git push origin main'. Before the rigor sweep, this action was
# completely untested because the verifier only looked at ma-copie
# (whose remote comes from the automatic clone, not the taught step).
check_step 9 "mon-archive/ a une remote nommee 'origin'" \
'assert_remote_name "$WORKDIR/mon-archive" "origin"'

show_section "Coherence inter-depots"

# ---- Step 10 : Les trois depots partagent le meme HEAD ----
check_step 10 "Les trois depots partagent le meme HEAD" \
'assert_same_head "$WORKDIR/mon-archive" "$WORKDIR/ma-copie" "$WORKDIR/clone-depuis-bare"'

show_score
6 changes: 4 additions & 2 deletions exercises/05-les-lignes-du-temps/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ check_step 1 "Tu es dans un dépôt Git" \
check_step 2 "Le fichier .gitignore existe" \
'[ -f .gitignore ]'

# ---- Step 3 : Le .gitignore contient un pattern pour les .log ----
# ---- Step 3 : .gitignore couvre effectivement les fichiers .log ----
# We rely on git check-ignore rather than a hardcoded regex so that the
# student can use any valid pattern (*.log, **/*.log, debug.log, ...).
check_step 3 "Le .gitignore ignore les fichiers .log" \
'grep -qE "^\*\.log$|^debug\.log$" .gitignore'
'printf "%s\n" "debug.log" | git check-ignore --stdin -q'

# ---- Step 4 : debug.log n'est plus tracké ----
check_step 4 "debug.log n'est plus suivi par Git" \
Expand Down
4 changes: 4 additions & 0 deletions exercises/06-larbre-des-possibles/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,8 @@ check_step 5 "La branche expedition-sud contient un commit propre" \
check_step 6 "Il y a au moins 3 branches" \
'[ "$(git branch | wc -l)" -ge 3 ]'

# ---- Step 7 : De retour sur main ----
check_step 7 "Tu es de retour sur la branche main" \
'assert_branch_is . main'

show_score
4 changes: 4 additions & 0 deletions exercises/10-le-protocole-des-guildes/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ check_step 4 "Un commit de merge existe (Protocole respecté)" \
check_step 5 "La branche de proposition a été nettoyée" \
'[ "$(git branch | grep -v "main\|master\|\*" | wc -l)" -eq 0 ]'

# ---- Step 6 : Retour sur la branche principale ----
check_step 6 "Tu es de retour sur la branche principale" \
'assert_branch_is . main || assert_branch_is . master'

show_score
13 changes: 10 additions & 3 deletions exercises/11-le-tisseur-de-temps/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,19 @@ check_step 1 "Tu es dans un dépôt Git" \
check_step 2 "Au moins 2 branches existent" \
'[ "$(git branch -a 2>/dev/null | wc -l)" -ge 2 ]'

# ---- Step 3 : Le reflog montre une activité de stash ----
check_step 3 "Le reflog montre une activité de stash" \
'git reflog show stash 2>/dev/null | grep -q . || git log -g refs/stash --oneline 2>/dev/null | grep -q . || git reflog 2>/dev/null | grep -qi stash'
# ---- Step 3 : La pile de stash est vide (stash appliqué ou dépilé) ----
# Note: once git stash pop empties the stash stack, refs/stash is deleted,
# so there is no durable reflog evidence of past stash activity. We can
# only check the terminal state: nothing pending.
check_step 3 "Aucun stash en attente (le tissage du temps est complet)" \
'[ -z "$(git stash list 2>/dev/null)" ]'

# ---- Step 4 : Au moins 3 commits existent ----
check_step 4 "Il y a au moins 3 commits dans l'historique" \
'[ "$(git log --all --oneline 2>/dev/null | wc -l)" -ge 3 ]'

# ---- Step 5 : Retour sur main ----
check_step 5 "Tu es de retour sur la branche main" \
'assert_branch_is . main'

show_score
25 changes: 22 additions & 3 deletions exercises/12-loracle-du-code/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,28 @@ check_step 1 "Tu es dans un dépôt Git" \
check_step 2 "git bisect a été utilisé (trace dans le reflog)" \
'[ "$(git reflog | grep -c "checkout: moving from [0-9a-f]\{7,\} to [0-9a-f]\{7,\}")" -ge 2 ]'

# ---- Step 3 : git cherry-pick a été utilisé (trace dans le reflog) ----
check_step 3 "git cherry-pick a été utilisé (trace dans le reflog)" \
'git reflog | grep -q "cherry-pick"'
# ---- Step 3 : Le commit de la branche correctif est présent sur HEAD ----
# State-based check: the student must have cherry-picked the fix commit
# from origin/correctif (or a local 'correctif' branch in the test harness).
# Cherry-pick copies the subject line, so HEAD's log contains an entry
# whose subject matches the correctif tip. A manual edit with a
# different commit message cannot satisfy this check.
check_step 3 "Le commit de la branche correctif a été appliqué (cherry-pick)" \
'
(
fix_ref=""
for candidate in origin/correctif correctif refs/remotes/origin/correctif; do
if git rev-parse --verify --quiet "$candidate" >/dev/null 2>&1; then
fix_ref="$candidate"
break
fi
done
[ -n "$fix_ref" ] || exit 1
fix_subject="$(git log -1 --format=%s "$fix_ref")"
[ -n "$fix_subject" ] || exit 1
git log HEAD --format=%s | grep -qF -- "$fix_subject"
)
'

# ---- Step 4 : Le grimoire ne contient plus le mot CORROMPU ----
check_step 4 "Le grimoire ne contient plus le mot CORROMPU" \
Expand Down
8 changes: 5 additions & 3 deletions exercises/13-les-sceaux-magiques/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ check_step 2 "Au moins 2 tags existent" \
check_step 3 "Au moins un tag annoté existe" \
'found=false; for t in $(git tag); do if [ "$(git cat-file -t "$t" 2>/dev/null)" = "tag" ]; then found=true; break; fi; done; $found'

# ---- Step 4 : Les tags suivent le format de versionnage (v*) ----
check_step 4 "Les tags suivent le format de versionnage (v*)" \
'git tag | grep -qE "^v[0-9]+\.[0-9]+\.[0-9]+$"'
# ---- Step 4 : Tous les tags suivent le format de versionnage (vX.Y.Z) ----
# Require every tag to match semver, not just one - a student mixing
# semver tags with garbage ones (e.g. release-notes) should not pass.
check_step 4 "Tous les tags suivent le format de versionnage (vX.Y.Z)" \
'[ "$(git tag | wc -l)" -gt 0 ] && ! git tag | grep -qvE "^v[0-9]+\.[0-9]+\.[0-9]+$"'

show_score
12 changes: 10 additions & 2 deletions exercises/14-les-outils-de-larchiviste/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,16 @@ check_step 3 "Le hook pre-commit existe et est exécutable" \
'[ -x "$(git rev-parse --git-dir)/hooks/pre-commit" ]'

# ---- Step 4 : Le hook pre-commit fonctionne (détecte les TODO) ----
check_step 4 "Le hook pre-commit détecte les TODO dans les fichiers" \
'grep -q "TODO" "$(git rev-parse --git-dir)/hooks/pre-commit"'
# Require the hook to both mention TODO AND have a non-zero exit path,
# so a file that is only a comment ("# blocks TODO markers\nexit 0")
# does not pass.
check_step 4 "Le hook pre-commit détecte les TODO et bloque le commit" \
'
hook="$(git rev-parse --git-dir)/hooks/pre-commit"
[ -f "$hook" ] \
&& grep -q "TODO" "$hook" \
&& grep -qE "exit[[:space:]]+[1-9]|exit[[:space:]]+\\\$" "$hook"
'

# ---- Step 5 : Le hook commit-msg existe et est exécutable ----
check_step 5 "Le hook commit-msg existe et est exécutable" \
Expand Down
10 changes: 7 additions & 3 deletions exercises/15-les-forges-eternelles/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ show_banner "$QUEST_TITLE"
check_step 1 "Tu es dans un dépôt Git" \
'git rev-parse --is-inside-work-tree'

# ---- Step 2 : Au moins un remote est configuré ----
check_step 2 "Au moins un remote est configuré" \
'git remote 2>/dev/null | grep -q .'
# ---- Step 2 : Un remote nommé 'origin' est configuré ----
check_step 2 "Un remote nommé 'origin' est configuré" \
'assert_remote_name . origin'

# ---- Step 3 : Au moins un push a été effectué ----
check_step 3 "Au moins un push a été effectué (des refs distantes existent)" \
Expand All @@ -35,4 +35,8 @@ check_step 3 "Au moins un push a été effectué (des refs distantes existent)"
check_step 4 "Le dépôt contient au moins 3 commits" \
'[ "$(git rev-list --count HEAD 2>/dev/null)" -ge 3 ]'

# ---- Step 5 : Le dépôt local est sur la branche main ----
check_step 5 "Tu es sur la branche main" \
'assert_branch_is . main'

show_score
9 changes: 7 additions & 2 deletions exercises/17-les-epreuves-automatiques/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@ check_step 2 "Le dossier .github/workflows existe" \
check_step 3 "Un workflow contient une stratégie de matrice (matrix)" \
'grep -rl "matrix:" .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null | grep -q .'

# ---- Step 4 : Le workflow contient plusieurs étapes (steps) ----
# ---- Step 4 : Le workflow contient plusieurs etapes (steps) ----
# Counts indented "- name:" entries which is the conventional YAML form for
# step entries under "steps:". This avoids the false-positive of the previous
# implementation which counted bare "name:" lines (workflow name, job name).
check_step 4 "Le workflow contient plusieurs étapes (steps)" \
'fichier=$(ls .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null | head -1) && [ -n "$fichier" ] && [ "$(grep -c "name:" "$fichier")" -ge 2 ]'
'fichier=$(ls .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null | head -1) \
&& [ -n "$fichier" ] \
&& [ "$(grep -cE "^[[:space:]]+- name:" "$fichier")" -ge 2 ]'

# ---- Step 5 : Au moins un script de test existe ----
check_step 5 "Au moins un script de test existe" \
Expand Down
10 changes: 8 additions & 2 deletions exercises/18-le-deploiement-sacre/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@ check_step 1 "Tu es dans un dépôt Git" \
check_step 2 "Un dossier .github/workflows existe avec un workflow" \
'ls .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null | grep -q .'

# ---- Step 3 : Le workflow mentionne le déploiement (production ou deploy) ----
# ---- Step 3 : Le workflow mentionne le déploiement hors commentaires ----
# Strip comment-only lines before matching so a placeholder workflow
# with "# TODO: deploy to production" at the top does not pass.
check_step 3 "Le workflow mentionne le déploiement (production ou deploy)" \
'grep -rqlE "production|deploy" .github/workflows/ 2>/dev/null'
'
find .github/workflows -type f \( -name "*.yml" -o -name "*.yaml" \) \
-exec grep -hvE "^[[:space:]]*#" {} + 2>/dev/null \
| grep -qE "(deploy|production)"
'

# ---- Step 4 : Au moins un tag existe ----
check_step 4 "Au moins un tag de version existe" \
Expand Down
31 changes: 20 additions & 11 deletions exercises/19-les-autres-forges/verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,26 @@ show_banner "$QUEST_TITLE"
check_step 1 "Tu es dans un dépôt Git" \
'git rev-parse --is-inside-work-tree'

# ---- Step 2 : Le fichier GitHub Actions existe ----
check_step 2 "Le fichier .github/workflows/ci.yml existe" \
'ls .github/workflows/*.yml >/dev/null 2>&1'

# ---- Step 3 : Le fichier GitLab CI existe ----
check_step 3 "Le fichier .gitlab-ci.yml existe" \
'[ -f ".gitlab-ci.yml" ]'

# ---- Step 4 : Le fichier Bitbucket Pipelines existe ----
check_step 4 "Le fichier bitbucket-pipelines.yml existe" \
'[ -f "bitbucket-pipelines.yml" ]'
# ---- Step 2 : Le fichier GitHub Actions existe et déclare au moins un job
# Wrapped in a subshell so the loop-internal 'exit' only returns from the
# check, not from the verifier as a whole.
check_step 2 "Le fichier .github/workflows/*.yml déclare un 'jobs:'" \
'
(
for f in .github/workflows/*.yml .github/workflows/*.yaml; do
[ -f "$f" ] && assert_file_contains "$f" "^jobs:" && exit 0
done
exit 1
)
'

# ---- Step 3 : Le fichier GitLab CI existe et définit au moins un job
check_step 3 "Le fichier .gitlab-ci.yml définit au moins un job (script:)" \
'assert_file_contains ".gitlab-ci.yml" "^[[:space:]]*script:"'

# ---- Step 4 : Le fichier Bitbucket Pipelines existe et déclare 'pipelines:'
check_step 4 "Le fichier bitbucket-pipelines.yml déclare 'pipelines:'" \
'assert_file_contains "bitbucket-pipelines.yml" "^pipelines:"'

# ---- Step 5 : Le script de test existe ----
check_step 5 "Le script de test scripts/test.sh existe" \
Expand Down
Loading
Loading