Déploiement Afresto Next
Pile : backend Go (push → GHCR → Watchtower ~5 min) · web React (Vite → Cloudflare Worker, manuel wrangler deploy) · mobile RN/Expo dans dépôt séparé <repo-mobile> (EAS OTA). Le déploiement en production nécessite toujours l'autorisation préalable de l'utilisateur (il approuve chaque fois).
🔴 Règle #1 — ce dépôt est souvent utilisé par PLUSIEURS AGENTS SIMULTANÉMENT
git status affiche souvent des fichiers modifiés appartenant à d'autres sessions. Par conséquent :
- NE PAS utiliser
git add -a/git commit -a/git add .— cela inclurait le travail inachevé d'autres sessions dans votre commit. - Stagez SÉLECTIVEMENT chaque fichier qui vous appartient, puis vérifiez :
git diff --cached --name-only— assurez-vous qu'aucun fichier étranger n'est présent (fichiers fréquemment inclus par erreur :web/src/pages/ChatPage.tsx,backend/internal/db/dbgen/models.gos'ils ne résultent pas de votre propre régénération). - Fichier partagé par deux sessions (ex. ExamPage) et vous ne voulez que vos hunks : ne faites pas
stash/reset(cela retirerait le fichier sous les pieds d'un autre agent). Modifiez l'index Git via un blob :git show :path > tmp→ supprimez leurs hunks detmp→SHA=$(git hash-object -w tmp)→git update-index --cacheinfo 100644,$SHA,path. Le fichier sur le disque reste intact. - 🚨 L'outil cwd du Bash peut PERSISTER après un
cd .../web(ou worktree) lors d'un appel précédent. Alorsgit add backend/...échoue avecpathspec did not match(ou stage l'arbre erroné). Avant un stage sélectif, faites d'abordcd <repo-root> &&(ou utilisez un chemin absolu). Vérifiez quegit diff --cached --name-onlyaffiche les chemins relatifs à la racine corrects (pas../backend,src/...). - Après le déploiement : vérifiez
git status— assurez-vous que les fichiers de travail des autres sessions sont toujours intacts.
Qualité Go (avant commit backend)
Équivalent de Pint + PHPStan de Laravel pour Go. Exécutez localement avant de stage les fichiers backend :
- Format :
cd backend && gofmt -l .→ doit être VIDE. S'il y a du contenu →gofmt -w .(correction automatique), stagez le résultat. (Équivalent depint --test.) - Build + vet :
go build ./... && go vet ./...vert. - Analyse statique (cible) :
golangci-lint run ./...(englobe gofmt/govet/staticcheck/errcheck/ineffassign/unused/misspell). Équivalent dephpstan analyse. - ⚠️ Statut : qualité CI non activée. Il n'y a pas encore de
backend/.golangci.ymlni de.github/workflows/ci.yml, etstaticcheckest obsolète → donc format+build+vet sont manuels pour l'instant. Prévision d'activation dans la CI + configuration complète :docs/plan/00-tooling-quality-gate.md. Le code généréinternal/db/dbgenest exclu du lint. - Les tests accompagnent les changements → skill afresto-testing (
go test ./...; les tests d'intégration sont ignorés sansTEST_DATABASE_URL).
🔴 Règle #00 — tout travail SIGNIFICATIF passe par une GitHub Issue D'ABORD (depuis le 5 Sep 2026)
S'applique avant la Règle #0 (branche + PR) : avant de toucher au code.
Le but n'est pas une formalité. Les demandes formulées par chat ne sont connues que de deux personnes — l'équipe ne peut pas les lire, le propriétaire n'a pas d'endroit pour les assigner, et le module ne laisse aucune trace de qui fait quoi.
Quand une issue est OBLIGATOIRE
Nouvelle fonctionnalité · changement visible par l'école · modification du schéma DB · tout ce qui nécessite une décision produit · un travail qui s'étend sur plus d'un PR.
Quand ce n'est PAS nécessaire
Bug évident et limité · correction de texte/étiquette · documentation · suivi direct d'un commentaire de revue de PR.
En cas de doute → créez une issue. C'est peu coûteux, et on peut la fermer. C'est ce seuil qui maintient la règle en vie : si chaque demande insignifiante devient une issue, les gens commenceront à la contourner en silence, et une fois contournée sans conséquence, la règle est morte.
Ce que fait l'agent
- Explorez suffisamment le code pour remplir les "constatations préliminaires" et identifier les blocages — ne devinez pas.
- Créez l'issue en utilisant le modèle
.github/ISSUE_TEMPLATE/pekerjaan.md, ajoutez l'étiquettemodul:*(c'est ce qui constitue l'historique par module), ainsi quebutuh-keputusansi quelque chose est en suspens. - ARRÊTEZ-VOUS. Ne codez pas, ne créez pas de branche, ne vous assignez pas la tâche. L'assignation est une action du propriétaire ; l'équipe a aussi besoin d'espace pour donner son avis/réviser les idées.
- Signalez le numéro de l'issue au propriétaire, ainsi que les décisions toujours en suspens (le cas échéant).
🔑 Le propriétaire peut contourner cette règle en disant "fais-le maintenant" — l'issue est tout de même créée comme trace, le travail continue. Cette règle régit le comportement par défaut, elle n'emprisonne pas.
Blocage découvert EN COURS DE réalisation
Prévenez avant de continuer : commentez dans l'issue (pour que l'équipe voie) et informez le propriétaire. Si la décision interrompt le travail → ajoutez l'étiquette butuh-keputusan. Ne choisissez pas silencieusement votre propre interprétation et continuez — c'est ce qui produit un travail "terminé" mais incorrect.
Boucler la boucle
Un PR doit obligatoirement contenir Closes #<numéro> dans son corps. C'est ce qui connecte l'issue → PR → personne, de sorte que gh issue list --label modul:ujian --state all devienne la trace de qui a fait quoi dans ce module. Sans cette ligne, la piste est rompue.
🔴 Règle #0 — NE PAS commit/push directement sur main (équipe de 4 personnes, depuis le 25 Jul 2026)
Tout travail passe par branche + Pull Request ; seul le relecteur fusionne dans main (la fusion déclenche le déploiement).
- Avant de commencer :
git checkout main && git pull— s'il y a de nouvelles migrations, exécutez d'abord les migrations. - Créez/vérifiez une branche thématique AVANT de toucher au code :
- fonctionnalité →
feat/<nom>· bogue →bugfix/<nom>· bogue avec issue → nouvelle branche + étudiez l'issue.
- fonctionnalité →
- Commit sélectif →
git push -u origin <branche>(PAS sur main). - Ouvrez un PR vers
main:gh pr createsighest connecté ; sinon, donnez à l'utilisateur l'URLhttps://github.com/Afresto-Next/next/compare/main...<branche>?expand=1. - Le relecteur approuve & fusionne → déploiement. NE PAS fusionner votre propre PR.
- La section "Push backend / Deploy WEB" ci-dessous décrit la mécanique qui s'exécute APRÈS la fusion du PR dans
main(généralement par la personne chargée du déploiement), ce n'est pas une autorisation à push directement surmain. - Les modifications du skill passent également par un PR (CODEOWNERS).
Ordre des étapes
1. Commit (Outil Bash = Git Bash / sh, PAS PowerShell)
- Message multi-lignes : heredoc
git commit -q -F - <<'EOF' … EOF. NE PAS utiliser@'…'@(c'est un here-string PowerShell → le sujet serait pollué par@ ...). - Terminez le message :
Co-Authored-By: Claude Opus 4.8 <[email protected]>. - Avertissement "CRLF will be replaced by LF" = normal, ignorez-le.
- Commitez sur votre branche de travail (
feat/*·bugfix/*), pas surmain(voir Règle #0). L'utilisateur commit quand on lui demande.
🔴 NE PAS utiliser git add -A / git add <dir>/ — cela a déjà supprimé les fichiers de l'utilisateur 3 fois
Ce dépôt contient de nombreux fichiers de travail non suivis de l'utilisateur : docs/bskap/ (images+PDF), docs/analisis butir soal/, elibrary/, cloudflare/**/.wrangler/ (cache de compte). Une fois avec -A, tout est inclus.
Cas réels : git add docs/ a inclus des PDF/xls/images BSKAP (25 Jul & 2 Agu) ; git add -A a inclus 446 fichiers dont wrangler-account.json (9 Agu).
git add chemin/vers/fichier1 chemin/vers/fichier2 # UN PAR UN, toujours
git commit -q -F - <<'EOF'
...
EOF
git show --name-only --format="" HEAD | wc -l # correspond à ce que vous vouliez ?
Si le nombre est erroné : git reset --soft HEAD~1 && git reset, refaite le stage correctement.
Si déjà poussé vers votre propre branche, git push --force-with-lease est sûr.
2. Migration DB — OBLIGATOIREMENT dans le commit SOMMET
.github/workflows/migrate.yml détecte les migrations via git diff --name-only HEAD~1 HEAD (uniquement le dernier commit), s'exécute après le succès de "Build & Push Docker Images" (workflow_run).
- Si le push contient une migration → la migration doit être dans le commit SOMMET (un commit contenant la migration + le code = sûr).
- La migration s'exécute automatiquement sur le runner auto-hébergé de la VM après le build.
run --rm migratese synchronise → le job ÉCHOUE FORTEMENT en cas d'erreur (pas silencieux). - Vérifiez la migration : consultez l'onglet Actions → "Migrate DB" vert pour ce commit (
ghCLI disponible :gh run list --workflow="Migrate DB (self-hosted)" --limit 1— l'agent peut vérifier lui-même, sans demander à l'utilisateur). Watchtower remplace l'imageapilors du prochain sondage (~5 min), presque toujours après la fin de la migration. - AVANT de pousser une migration, vérifiez son SQL via psql
BEGIN; … ROLLBACK;sur la DB locale (v102 = prod). Voir le skillafresto-db-change/ jebakan-rekayasa §4.
🔴 Numéro de migration : vérifiez à nouveau JUSTE AVANT DE FUSIONNER, pas lors de la création de la branche
Un collègue peut fusionner une autre série de migrations pendant que votre branche est ouverte. Des numéros identiques font que goose PANIQUE dans sortAndConnectMigrations — ce n'est pas une erreur SQL, donc facile à mal interpréter.
✅ Aucune corruption de données : la panique se produit lors de la collecte de la liste, avant qu'une seule ne soit exécutée.
Cas réel (9 Agu 2026) : 00138 a été choisi lors de la création de la branche ; lors de la fusion, l'équipe utilisait déjà 00138–00140. La première correction l'a renommé 00140 et était toujours en conflit — parce qu'on a deviné à nouveau. Comptez, ne devinez pas :
git pull # OBLIGATOIRE d'abord, pour voir les migrations des collègues
MAX=$(ls backend/migrations/ | sed 's/_.*//' | sort -n | tail -1)
NEXT=$(printf "%05d" $((10#$MAX + 1)))
ls backend/migrations/ | sed 's/_.*//' | sort | uniq -d # DOIT ÊTRE VIDE
Si c'est déjà fait : git mv vers un nouveau numéro, petit PR, fusionnez, puis relancez le workflow "Migrate DB (self-hosted)" depuis l'onglet Actions.
3. Push backend (APRÈS la fusion du PR dans main — voir Règle #0)
Fusion du PR → main mis à jour → GHCR → Watchtower ~5 min remplace l'image. Aucun changement backend = pas besoin d'attendre Watchtower. (Pendant le développement : git push -u origin <branche> puis PR — ne poussez pas main.)
⚠️ APRÈS la fusion backend : VÉRIFIEZ l'erreur 502 (déjà survenue 6 fois)
La recréation du conteneur api/worker déclenche souvent une panne totale — tous les conteneurs sont Up mais toutes les routes donnent 502. En pratique, chaque fusion backend est un coup de dés.
curl -s -o /dev/null -w "%{http_code}\n" --max-time 15 https://next.afresto.co/api/v1/auth/me
# 401 = EN BONNE SANTÉ (authentification requise) · 502 = panne
Si 502 → Actions → "Ops — restaurer l'API" → restart-docker (sans SSH, restauration en <1 minute).
Ne cherchez pas à analyser la fonctionnalité nouvellement déployée : 502 + conteneur Up = redémarrage du démon Docker. Détails : docs/ops-auto-pulih.md.
4. Déployer le WEB — depuis un WORKTREE PROPRE (à cause des nombreux agents)
npm run build lit L'ARBRE DE TRAVAIL ENTIER → le travail inachevé d'autres sessions est également publié. Construisez et déployez donc depuis un worktree sur votre commit :
rm -rf /c/wtd 2>/dev/null; git worktree add --detach /c/wtd <SHA-de-votre-commit>
cd /c/wtd && git status --short # doit être VIDE (propre)
cd /c/wtd/web && npm ci && npm run build
cd /c/wtd/cloudflare/web-worker && npx wrangler deploy
cd <repo-projectTwo> && git worktree remove --force /c/wtd && git worktree prune
- 🔥 Le chemin du worktree DOIT ÊTRE COURT (
/c/wtd) — le scratchpad interne + les longs noms de fichier PDF kaldik DKI → erreur Windows "Filename too long" lors du checkout. - Vérifiez que le bundle est correct si nécessaire :
grep -rl "<marqueur-de-votre-fonctionnalité>" /c/wtd/web/dist/assets/*.js— mais méfiez-vous des faux positifs (un marqueur commun peut provenir d'une autre fonctionnalité). - Le cache Cloudflare dure ~20 s → testez avec Ctrl+F5. (200 mais taille ~397 octets = fallback, pas l'actif réel.)
5. Mobile OTA — dépôt séparé, --environment OBLIGATOIRE
cd "<repo-mobile>"
npx eas update --branch preview --environment preview --message "…" --non-interactive
- 🔥
--environmentOBLIGATOIRE (preview/production) — sans cela, la mise à jour concerne le mauvais environnement/échoue. - Le dépôt mobile a un dépôt distant GitHub (
Afresto-Next/afresto-next-mobile) → la Règle #0 s'applique : branche → PR → fusion, pas de push direct surmain. - OTA concerne uniquement le JS/les actifs. Un changement de module natif → CRASH si fait via OTA → nécessite une reconstruction.
🔥 eas update envoie L'ARBRE DE TRAVAIL, pas le commit
Exactement comme npm run build à l'étape 4 — tout le répertoire de travail est inclus dans le bundle, y compris le travail inachevé d'autres sessions. Le symbole * sur la ligne Commit <hash>* dans le résultat de publication = arbre sale.
OBLIGATOIRE juste avant la publication — imprimez, puis comparez avec la ligne Commit dans le résultat :
echo "publié depuis : $(git branch --show-current) @ $(git log --oneline -1 --format=%h)"
git log --oneline HEAD..origin/main # VIDE = aucun travail d'autrui n'est oublié
- Publiez depuis
main, pas depuis une branche de fonctionnalité. Depuis une branche, le travail des autres déjà fusionné DISPARAÎT du canal — OTA remplace l'intégralité du bundle, il ne le corrige pas. - Production : mettez de côté d'abord les modifications non commitées des autres sessions (
git stash push -- <fichiers>…git stash pop). La production ne doit pas transporter de code non examiné.
🚨 La branche peut CHANGER en cours de travail (plusieurs agents, un seul clone)
Cas 20 Agu 2026 : la publication preview provenait clairement de main @ d0938fa ; la commande suivante a trouvé HEAD déjà sur une autre branche @ 8126a50 — une autre session a changé de branche sur le même clone. La production a exécuté du code de 3 PR de plus pendant ce temps, sans aucune erreur.
➡️ Ne supposez pas que la branche est la même d'une commande à l'autre. Imprimez-la à chaque fois (commande ci-dessus). ➡️ Prévention réelle : OTA depuis un WORKTREE, comme pour le déploiement web à l'étape 4 — les autres sessions ne peuvent pas changer sa branche :
rm -rf /c/wtm 2>/dev/null; git worktree add --detach /c/wtm <SHA-de-votre-commit>
cd /c/wtm && git status --short # doit être VIDE
npm ci && npx eas update --branch <ch> --environment <env> --message "…" --non-interactive
cd <repo-mobile> && git worktree remove --force /c/wtm && git worktree prune
🪤 Chemin du worktree court (/c/wtm) — même raison qu'à l'étape 4.
🪤 npm ci dans le worktree prend du temps ; pour les petites mises à jour OTA, vous pouvez rester sur le clone principal à condition que la vérification de la branche+commit ci-dessus soit effectuée et que le résultat corresponde.
🏢 GHCR & organisation GitHub — le dépôt est désormais dans l'org Afresto-Next
Les dépôts (next, afresto-next-mobile, afresto-claude-skills) ont été déplacés de ristology/* → org Afresto-Next (20 Jul 2026). Le runbook de transfert complet : docs/runbook-transfer-next-org.md.
- 🚨 Transférer un dépôt vers une org = le chemin GHCR suit le propriétaire → Watchtower SE BLOQUE SILENCIEUSEMENT. La CI tague l'image
ghcr.io/${GITHUB_REPOSITORY_OWNER}/afresto-next-{api,web,migrate}— le propriétaire suit le propriétaire du dépôt. La section suivante…
État CI — dépôt racine next
| Workflow | Statut (actuel) | Runner | Déclencheur | Décrit dans |
|---|---|---|---|---|
| Build & Push Docker Images | ✅ OK | ubuntu-latest | push sur main (backend) | §3 ci-dessus |
| Migrate DB (self-hosted) | ✅ OK | self-hosted | workflow_run sur Build & Push | §2 ci-dessus |
| Deploy Web (Workers) | 🚧 À faire | ubuntu-latest | push sur main (web) | §4 ci-dessus |
| Lint & Test (Go) | 🚧 À faire | ubuntu-latest | pull request (pull_request) | docs/plan/… |
Statut auto-explicatif :
✅ OK= workflow actif et stable.🚧 À faire= workflow pas encore écrit (la qualité manuelle s'applique).
Tous les workflows sont dans .github/workflows/.
🧩 Dépendances & Prérequis
# Actif de ce dépôt requis pour le déploiement
# (fichiers de configuration : wrangler.toml, Dockerfile, etc.)
# Dépôts externes (déjà clonés sur la machine ? `cd <chemin> && git remote -v`)
# - <repo-mobile> : Afresto-Next/afresto-next-mobile
# Outils à vérifier (tooling GitHub)
# - gh CLI : `gh --version` (authentifié : `gh auth status`)
# - npx : inclus avec Node.js (LTS)
# - wrangler : `npx wrangler --version`
# - eas-cli : `npx eas --version`
Commandes de base par dépôt (scripts package.json/setup)
| Dépôt | Commande | Usage |
|---|---|---|
next (web) | npm run build | Build web React |
next (cloudflare/web-worker) | npx wrangler deploy | Déploiement Worker |
afresto-next-mobile | npx eas update --branch <br> --environment <env> --message "…" | Mise à jour OTA |
Versioning & Règles Backwards Compatibility
Par principe : compatibilité ascendante entre versions. Si une modification est cassante, elle doit être annoncée via une issue et documentée. Les déploiements OTA mobile ne doivent pas casser les fonctionnalités existantes. Toute modification non rétrocompatible doit être planifiée.
Gestion des erreurs de déploiement
Tous les déploiements sont suivis via les Actions GitHub. En cas d'échec :
- Backend :
Actions → Build & Push Docker Images→Actions → (redéclencher)ougit revertdu commit problématique, puisgit pushsurmain. Watchtower remettra l'ancienne image. - Web :
Actions → (redéclencher)ougit revert. Si le code du Worker est défectueux, déployez la version précédente :npx wrangler rollback. - Mobile :
eas update --branch preview --message "Revert to <hash>"avec le code précédent.
Attention : ne revertez jamais un commit de migration de base de données. Écrivez plutôt une migration inverse (down migration).
🔴 Règle #2 — ne laissez jamais un WORKTREE derrière vous
git worktree list le prouve. Si vous avez créé un worktree, supprimez-le toujours après usage : git worktree remove --force <chemin> puis git worktree prune. Un worktree oublié == fuite de disque + plantages Git.
📍 Côté utilisateur — les agents n'ont PAS de terminal interactif
L'agent utilise un outil Bash (sh), pas un terminal interactif. Pas de sessions persistantes, pas de export qui reste entre les appels, pas de cd qui persiste en dehors du même bloc. Toujours spécifier les chemins absolus ou cd <dossier> && au début de chaque bloc.
Pile & Prérequis
- Backend : Go, Docker, GHCR, Watchtower
- Web : Node.js, npm, Vite, Wrangler (Cloudflare Workers) – pas de Docker pour le web
- Mobile : Node.js, npm, Expo, EAS CLI
- Déploiement : gh CLI (GitHub), git, psql