just & les recettes modulaires
Une seule surface de commandes pour toute l'usine : le justfile racine, la pièce qui fait qu'humains et agents lancent la même chose de la même façon.
Hier, votre préflight vous a affiché « poste de pilotage : OK ». Mais regardez ce que vous tapez
depuis trois chapitres : uv run adws/doctor.py, uv run adws/hello_factory.py --harness pi,
cd apps/plume && bun test… Trois commandes, trois formes différentes, toutes à retenir de tête,
et l’usine n’a que cinq pièces. À la fin du livre, elle en aura trente. Ce chapitre vous donne la
solution avant que le problème ne devienne douloureux : une seule surface de commandes, où
chaque action de l’usine porte un nom court et se découvre en tapant just. La pièce du jour est
le justfile racine, prévu sur votre plan depuis le chapitre 1 : le panneau de commandes du poste
de pilotage, juste à côté du doctor.py posé hier. Parce que cette surface grandira avec l’usine,
vous apprendrez aussi comment elle se découpe en modules, et les pièges bien réels qui attendent
ceux qui les découvrent sans guide.
just, le lanceur de recettes
L’idée en une phrase
just est un lanceur de recettes : des commandes nommées, avec arguments, déclarées dans un
fichier justfile à la racine du dépôt. Dans l’usine, ce fichier devient la surface de commandes
unique, une pièce entièrement côté déterministe que les humains et, plus tard, les agents
invoqueront de la même façon.
Points clés
- Une recette = un nom, des arguments optionnels, des lignes de shell. Le commentaire placé
juste au-dessus devient sa documentation dans
just --list: la surface s’auto-documente. set dotenv-loadcharge le fichier.envdans l’environnement de chaque recette : c’est par là queOPENROUTER_API_KEYarrivera aux ADW au module 4, sans jamais apparaître dans une commande.set positional-argumentsexpose les arguments de la ligne de commande en$1,$2,"$@"dans le corps des recettes, indispensable pour les recettes qui relaient tout à un script.- Chaque ligne d’une recette s’exécute dans son propre shell : un
cdsur une ligne ne vaut plus rien à la ligne suivante. D’où les enchaînementscd apps/plume && bun testsur une seule ligne. - just est un binaire unique, disponible sur tous les systèmes. Au moment d’écrire, la série 1.58. Comme uv, il s’installe en une commande et ne demande aucune préparation.
Exemple concret
Projetez-vous au module 3, quand les ADW seront posés. Sans surface de commandes, demander à un
agent de « lancer les tests du projet » déclenche une exploration : lire l’arborescence, ouvrir
package.json, deviner l’outil. Comptez quelques milliers de tokens et une à deux minutes,
facturés à chaque session, car l’agent ne se souvient de rien d’un run à l’autre. Avec le
justfile, la même question se règle en un appel : just --list renvoie la surface complète,
documentée, en quelques centaines de tokens. Un facteur 10 environ sur ce poste, et zéro
divergence possible entre ce que lance l’agent et ce que vous lancez vous-même. Pour vous, le gain
est plus discret mais quotidien : trois commandes hétérogènes deviennent just doctor,
just hello, just test.
Sans justfile vs avec justfile
| Besoin | Sans justfile | Avec justfile |
|---|---|---|
| Retrouver une commande | mémoire, README, historique shell | just la liste, documentée |
| Onboarder un collègue | rituel à transmettre | just et tout est là |
| Un agent lance les tests | exploration, quelques milliers de tokens | just test, un appel |
| Changer une commande | corriger partout où elle est notée | un seul endroit |
Commande — installer just
Aucun harnais n’est impliqué : cette pièce est 100 % déterministe, la même installation vaut pour tous. Une commande selon votre système, puis la vérification :
# macOS
brew install just
# Linux (binaire officiel, ici dans ~/.local/bin)
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to ~/.local/bin
# Windows
winget install --id Casey.Just --exact
# vérifier — au moment d'écrire : la série 1.58.x
just --version
Un mot aux lecteurs sous Windows : just exécute les recettes dans sh, que PowerShell ne fournit
pas, et un just lancé tel quel échoue avec « could not find the shell sh ». Le livre écrit
toutes ses recettes en dialecte bash, et la solution est déjà sur votre poste : Git pour Windows,
un prérequis depuis le chapitre 1, embarque bash. La pièce du jour contient la ligne
set windows-shell qui y raccorde just. Vous continuez à taper just depuis PowerShell, les
recettes s’exécutent dans le bash de Git. Ni WSL, ni double maintenance des recettes.
Piège courant : « just, c’est make » est inexact. make est un moteur de build qui décide quoi reconstruire en comparant des dates de fichiers, just a volontairement abandonné cette mécanique. Une recette s’exécute toujours, à l’identique, quand vous l’appelez : c’est un lanceur de commandes, pas un système de build, exactement ce qu’une surface de commandes doit être.
Modules, répertoires de travail et pièges réels
L’idée en une phrase
Quand la surface grandit, mod nom 'chemin/fichier.just' monte un fichier de recettes comme
espace de noms (just adw plan …, just obs tail …). Mais un module n’hérite de rien de
son parent : ni variables, ni réglages, ni répertoire de travail. Connaître ces trois règles
maintenant vous épargnera les pannes les plus déroutantes de la suite du livre, côté déterministe
de la couture.
Points clés
modn’hérite de rien :set dotenv-load,set positional-argumentset les variables du justfile racine doivent être redéclarés dans chaque fichier de module.- Le répertoire de travail d’un module est, par défaut, le dossier de son propre fichier. Un
just/adws.justqui lanceuv run adws/adw_plan.pycherchera doncjust/adws/adw_plan.py, d’où la ligne rituelleset working-directory := '..'en tête de chaque module. - Chaque module a besoin de sa propre recette
defaultqui liste : sans elle, unjust adwnu exécute la première recette du fichier, rarement ce que vous vouliez. importest l’autre mécanisme : le fichier importé partage le scope et le répertoire de travail de son parent, sans espace de noms. Utile pour découper un gros module en fichiers, sans refaire les raccordements.- Dernier piège, le shell : just exécute les recettes dans un
shminimal et non interactif. Un outil installé comme fonction de votre shell personnel, ou une syntaxe comme"${@:2}", peut échouer là où votre terminal réussit.set shellpermet d’imposer le shell voulu.
Exemple concret
Voici la panne type, vécue un jour ou l’autre par tous ceux qui modularisent : vous déplacez vos
recettes ADW vers just/adws.just, vous montez le module, et just adw doctor répond
« No such file or directory ». La commande est pourtant identique à celle qui marchait hier.
Diagnostic : le répertoire de travail est devenu just/, et adws/doctor.py n’y existe pas.
À la main, comptez quelques minutes de perplexité. Si c’est un agent qui tombe dessus au milieu
d’un workflow, comptez plusieurs allers-retours facturés, quelques dizaines de milliers de
tokens à tâtonner sur une panne que trois lignes de réglages en tête de module font disparaître
définitivement. C’est l’intérêt de les apprendre aujourd’hui, avant que vos modules n’existent :
quand vous poserez just/adws.just au module 3, ses trois premières lignes seront écrites
d’avance.
mod vs import
| Critère | mod | import |
|---|---|---|
| Espace de noms | oui : just adw plan | non : recettes à plat |
Variables et set du parent | rien n’est hérité | tout est partagé |
| Répertoire de travail | le dossier du fichier module | celui du parent |
| Rôle dans l’usine | une zone : adw, obs, sandbox | découper un module en fichiers |
Config — l’en-tête rituel d’un module
Aucun harnais ici non plus, ce sont des réglages purement déterministes. Quand vous créerez
just/adws.just au module 3, son en-tête reprendra ces trois lignes. Les voici commentées une
fois pour toutes :
# En tête de chaque fichier de module : un module n'hérite de RIEN.
set working-directory := '..' # revenir à la racine du dépôt — sinon cwd = just/
set positional-arguments # redéclaré : le réglage du parent ne se propage pas
set dotenv-load # idem : sans lui, pas de .env dans les recettes
# liste les recettes du module — sans default, `just adw` lancerait la première recette
default:
@just --justfile {{ source_file() }} --list
Piège courant : « mon module hérite du justfile racine, comme un sous-répertoire hérite de son parent » est le réflexe naturel, et c’est l’inverse. L’isolation est un choix de conception de just : chaque module est autosuffisant et lisible seul, au prix de trois lignes rituelles. Les oublier ne casse pas tout de suite. Ça casse plus tard, ailleurs, avec un message qui ne pointe pas vers la cause.
Fil rouge — la pièce posée aujourd’hui
La pièce du jour est le justfile racine, la case du plan juste sous PLAN.md, dans la zone
« Poste de pilotage », à côté de doctor.py (chapitre 4) et au-dessus des pièces qu’il commande
déjà : hello_factory.py (chapitre 3) et Plume (chapitre 2). La couture ne traverse pas cette
pièce, tout y est déterministe. Elle prépare pourtant la frontière : c’est par cette surface que
les agents du module 3 agiront sur l’usine. Un agent qui tape just test reste un nœud borné qui
propose, la recette exécute toujours la même chose. Coût à l’usage : zéro token, quelques
millisecondes de surcoût par commande. Ce qu’elle économise : l’exploration que chaque session
d’agent refacturerait pour redécouvrir vos commandes, quelques milliers de tokens par session, et
pour vous la charge de mémoire d’une usine qui comptera bientôt des dizaines de recettes.
Travaux pratiques — la pièce du jour
Une pièce complète à poser dans le repo compagnon plume-factory, qui devient, chapitre après
chapitre, votre usine logicielle agentique. Aujourd’hui : le panneau de commandes, toutes les
pièces déjà posées, désormais à portée d’un nom court.
Pièce — justfile
Le fichier vit à la racine du dépôt, entièrement côté déterministe. Il commande les pièces des
chapitres 2 à 4 : le préflight, l’embryon du runner (avec le choix du harnais en argument) et le
payload Plume. Les modules just/ viendront s’y monter aux modules 3, 5 et 6 : le plan les
annonce, le justfile les attendra.
# justfile — la surface de commandes de plume-factory.
# `just` seul liste tout : c'est le panneau de commandes de l'usine.
# .env est chargé dans l'environnement des recettes (OPENROUTER_API_KEY au module 4).
set dotenv-load
# Les arguments de la ligne de commande deviennent $1, $2, "$@" dans les recettes.
set positional-arguments
# Windows : les recettes s'exécutent dans le bash de Git (installé avec git) — pas de WSL.
# Ce réglage est ignoré sur macOS/Linux ; adaptez le chemin si Git est installé ailleurs.
set windows-shell := ["C:/Program Files/Git/bin/bash.exe", "-cu"]
# liste les recettes — sans elle, `just` nu exécuterait la première recette du fichier
default:
@just --list
# le préflight du poste de pilotage (ch. 4)
doctor:
uv run adws/doctor.py
# l'embryon du runner (ch. 3) — choisir le harnais : just hello claude
hello HARNESS="pi":
uv run adws/hello_factory.py --harness {{ HARNESS }}
# les tests du payload Plume (ch. 2) — cd et commande sur UNE ligne : chaque ligne a son shell
test:
cd apps/plume && bun test
# servir Plume en local sur le port 4500 (ch. 2)
serve:
cd apps/plume && bun run server.ts
La gate du TP
just && just doctor && just test && echo "gate : OK"
Attendu : la liste des cinq recettes documentées, le tableau du préflight où just passe
d’« absent » à sa version (la pièce du jour coche elle-même sa case), puis les tests de Plume
verts et gate : OK. Coût : zéro token, quelques secondes. Sous Windows, lancez cette gate
depuis Git Bash (livré avec git), car l’enchaînement && n’existe pas dans les vieux PowerShell,
ou tapez les trois commandes une à une. Si just s’arrête sur une erreur de syntaxe, vérifiez
l’indentation des corps de recettes (des espaces ou des tabulations, mais cohérents). Quand la
gate passe, commitez le fichier.