Poste de pilotage Chapitre 5 / 42

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-load charge le fichier .env dans l’environnement de chaque recette : c’est par là que OPENROUTER_API_KEY arrivera aux ADW au module 4, sans jamais apparaître dans une commande.
  • set positional-arguments expose 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 cd sur une ligne ne vaut plus rien à la ligne suivante. D’où les enchaînements cd apps/plume && bun test sur 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

BesoinSans justfileAvec justfile
Retrouver une commandemémoire, README, historique shelljust la liste, documentée
Onboarder un collèguerituel à transmettrejust et tout est là
Un agent lance les testsexploration, quelques milliers de tokensjust test, un appel
Changer une commandecorriger partout où elle est notéeun 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

  • mod n’hérite de rien : set dotenv-load, set positional-arguments et 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.just qui lance uv run adws/adw_plan.py cherchera donc just/adws/adw_plan.py, d’où la ligne rituelle set working-directory := '..' en tête de chaque module.
  • Chaque module a besoin de sa propre recette default qui liste : sans elle, un just adw nu exécute la première recette du fichier, rarement ce que vous vouliez.
  • import est 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 sh minimal 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 shell permet 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èremodimport
Espace de nomsoui : just adw plannon : recettes à plat
Variables et set du parentrien n’est héritétout est partagé
Répertoire de travaille dossier du fichier modulecelui du parent
Rôle dans l’usineune zone : adw, obs, sandboxdé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.


Quiz — teste tes connaissances
Poste de pilotage 7 questions Objectif : 5/7 minimum
0/7
bonnes reponses
Objectif non atteint (minimum 5/7 requis).
Remonte relire la fiche memo en pretant attention aux points manques, puis cliquer sur « Recommencer » pour retenter.