Dev Studio — pilotage du développement
Un seul cockpit, deux portes protégées. Dans l'Admin Panel, le bouton permanent du footer du panneau gauche ouvre /admin/platform/dev-studio. Son badge reprend le backlog en attente, avec les décisions ouvertes et les…
Un seul cockpit, deux portes protégées. Dans l'Admin Panel, le bouton permanent du footer du panneau gauche ouvre
/admin/platform/dev-studio. Son badge reprend le backlog en attente, avec les décisions ouvertes et les runs actifs en récapitulatif. Les deux anciennes entrées du menu Plateforme ont disparu. Le cockpit admin utilise le centre et le panneau gauche du shell Admin, sans deuxième StudioShell, deuxième fond à pois ou double défilement. Les six sections remplacent le rail Admin dans ce contexte ; les Réglages et la documentation passent dans le menu « … » du centre. L'ancien Overview/adminredirige vers Dev Studio. La palette ⌘K reste utilisable sans champ Search dans le panneau gauche. Les ressources de design (Figma, Excalidraw, liens HTTPS) s'éditent directement dans?s=design, avec une garde admin. Il n'existe plus de plafonds financiers de délégation dans Dev Studio. L'URL historique/admin/platform/dev-studio/settingsredirige vers Design./ops/devreste la porte des collaborateurs mandatés viaplatform.dev.operate: même implémentation de cockpit, pas d'accès aux réglages réservés à l'administrateur.
| Route | Répond à |
|---|---|
/admin/platform/dev-studio?s=backlog | Quels éléments restent prêts ou bloqués, et lesquels sont pris ? |
/admin/platform/dev-studio?s=decisions | Quelles décisions attendent le propriétaire ? |
/admin/platform/dev-studio?s=github | Quelles PR sont ouvertes et quels workflows ont tourné ? |
/admin/platform/dev-studio?s=runs | Quels agents tournent et quel est leur coût ? |
/admin/platform/dev-studio?s=ci | Quelles commandes et quels workflows gardent une PR ? |
/admin/platform/dev-studio?s=design | Quelles sources design sont partagées et éditables par un admin ? |
Claude Code et les abonnements
Le lanceur du backlog propose Claude Code dans le navigateur
(claude.ai/code), le deep link local vers la machine/Claude Desktop,
la commande claude --cloud, ainsi que la commande claude a coller
dans le terminal integre a Cursor. Il ouvre ou prepare la session ;
il ne connait pas et ne stocke pas les identifiants Claude.
Chaque surface utilise le compte Anthropic deja authentifie sur cette surface.
Les abonnements Max ont leurs propres limites d'usage ; le Studio ne calcule
pas de cout par run et n'applique pas de plafond en USD.
Frontiere de facturation : l'agent natif Cursor n'est pas Claude Code
dans le terminal Cursor. Le lanceur passe par la CLI claude pour ne pas
basculer involontairement sur le moteur Cursor. La variable locale
ANTHROPIC_API_KEY peut faire basculer Claude Code vers la facturation API
malgre un abonnement connecte : la connexion effective doit etre verifiee
dans l'outil Claude, et non declaree connectee dans le panel.
L'ancienne fenetre de run suivi et les routes
/api/ops/dev/{estimate,runs} ont ete retirees du cockpit. Les donnees
historiques AgentRun et la lecture des PR et CI restent preservees
pour le suivi, sans transformer le clic de lancement en dispatch API.
Deux sources, jointes une seule fois
Le manifeste (src/services/fleet/manifest.generated.json, ecrit par
pnpm fleet:derive) sait ce qui est A FAIRE : il projette
backlog/<pilier>/<id>-<slug>.md. La table AgentRun sait ce qui TOURNE.
Aucune des deux ne connait l'autre, et c'est la page Flotte qui les
rejoint, cote serveur, sur backlogRef (<pilier>/<id>).
Rien n'est lu sur le disque a l'execution. Ni backlog/, ni
package.json, ni .github/workflows/, ni public/. La raison est
celle qu'app-shell/0281 a payee sur le moniteur cron : un chemin de
fichier construit au runtime est invisible au traceur de Next, donc les
fichiers ne sont pas dans le bundle de la lambda. La lecture rend la
bonne liste en local et une liste vide en production, ce qui est le mode
d'echec le plus couteux qui soit : un ecran qui ne ment qu'une fois
deploye.
D'ou deux artefacts derives, tous deux -merge dans .gitattributes :
| Artefact | Generateur | Source |
|---|---|---|
src/services/fleet/manifest.generated.json | pnpm fleet:derive | backlog/, package.json, .github/workflows/ |
src/services/fleet/brand-assets.generated.ts | pnpm brand:derive | public/**/*.{svg,png,jpg,webp,ico} |
Les deux sont verifies au pre-push (fleet:derive:check,
brand:derive:check) : un item ajoute au backlog sans regeneration
bloque le push au lieu de disparaitre silencieusement de l'ecran.
Le moteur par defaut est Claude Code, et le modele plancher est Opus
Decision de gouvernance. La console n'est pas un selecteur de fournisseur : elle est centree sur Claude Code, et les autres moteurs n'existent que comme secours.
| Moteur | Role | Quand |
|---|---|---|
CLAUDE_ACTION | primaire, defaut | GitHub Actions execute le run et ouvre la PR |
CLAUDE_LOCAL | primaire | lien profond claude-cli://open, sur le poste de l'operateur |
CURSOR | secours | uniquement quand le quota Claude est epuise |
L'ordre vit dans LAUNCH_ENGINES (src/services/fleet/launch.ts), avec
DEFAULT_LAUNCH_ENGINE = "CLAUDE_ACTION". Le dialogue de lancement le
rend dans cet ordre et etiquette Cursor « (secours) » : un operateur ne
doit pas avoir a se souvenir de la politique, il doit la lire.
Le modele plancher est claude-opus-5, expose par FLEET_MODEL
(src/services/fleet/prompt.ts) et ecrit dans le prompt remis a chaque
run. La raison n'est pas le confort : un modele plus petit sur un item
transverse coute plus en revue qu'il n'economise en tokens, et cette
derive est invisible dans un diff. Le workflow
.github/workflows/fleet-agent.yml doit passer --model claude-opus-5
dans ses claude_args, et cite FLEET_MODEL en commentaire comme la
constante dont il est le miroir.
Le format de lien profond claude-cli://open n'accepte aujourd'hui que
repo et q (cf. la doc des deep links) : il n'y a pas de parametre de
modele a poser, donc le plancher passe par le PROMPT pour ce moteur-la.
C'est une difference reelle entre les deux moteurs Claude, et elle est
ecrite ici plutot que devinee.
Le cout se montre AVANT de lancer
GET /api/admin/fleet/estimate?backlogRef=&engine= rend
{ usd, basis, sample } : la mediane des runs termines de meme moteur et
meme taille quand il y en a assez (basis: "history"), la grille par
defaut sinon ("default"), et "unmeasurable" quand le moteur ne
rapporte pas son cout. La taille n'est PAS un parametre de la requete :
elle est lue dans le manifeste a partir de la reference, pour qu'un
appelant ne puisse pas changer l'estimation en changeant sa saisie.
Afficher le cout apres le lancement en ferait un chiffre qui ne sert plus a decider. La fiche d'un run montre ensuite l'estime FACE au reel : c'est la seule facon de savoir si la grille par defaut est encore juste.
Le lancement, et ses cinq facons d'echouer
POST /api/admin/fleet/runs { backlogRef, engine } appelle
launchAgentRun. Toute la decision est dans le service ; la route ne fait
que traduire FleetLaunchError.code en statut HTTP
(src/app/api/admin/fleet/runs/launch-status.ts) :
| Code | Statut | Ce que ca veut dire |
|---|---|---|
item-not-found | 404 | la reference ne designe aucun fichier de backlog |
already-running | 409 | un run non terminal existe deja sur cet item |
item-not-launchable | 422 | l'item est archive, done ou inbox |
engine-not-configured | 422 | le jeton du moteur n'est pas pose |
dispatch-failed | 502 | le moteur a ete joint et a refuse |
Un code inconnu vaut 500, jamais 200 : repondre en succes ferait croire au panel qu'un run existe.
La route porte withAdminRoute(..., { action: "fleet.run.launch", resource: "AgentRun" }), donc chaque lancement ecrit sa ligne
AdminAuditLog. Lancer un agent contre la production est une mutation,
et elle se journalise comme telle.
Pour CLAUDE_LOCAL, la reponse porte un deepLink. Le client pose
window.location.href dessus et affiche le lien en clair : un
claude-cli:// que le systeme ne sait pas ouvrir echoue sans rien dire,
et l'operateur se retrouverait avec une ligne QUEUED sans savoir
pourquoi rien ne s'est passe.
La page Scripts ne lance rien
Deliberement. pnpm db:push derriere un bouton serait une ecriture de
schema sur la production a un clic, et le panel a deja la surface qui le
fait avec une confirmation tapee (/admin/platform/data-integrity). La
page repond « quelle est la commande », pas « exécute-la pour moi ».
La bibliotheque est en lecture seule, et pour une raison
/admin/creative/library rassemble trois matieres : les StudioAsset
recents toutes organisations confondues, le kit de marque derive de
public/, et les liens GitHub vers les tokens et le pipeline Remotion.
Aucun verdict n'y est rendu. Approuver une creative, la rejeter, publier
un drop : tout cela vit sur /admin/creative/qc. Deux surfaces de
decision sur les memes lignes seraient deux boutons pour le meme etat, et
c'est exactement le defaut que le Studio marchand a ete scinde pour
eviter (cf. (dashboard)/CLAUDE.md, « Des resultats, pas des
controles »).
Les vignettes passent par <img> et non next/image : les objets Blob
sont des rendus finaux de tailles heterogenes, et re-encoder 48 vignettes
a l'optimiseur couterait une invocation chacune pour une planche qu'on
parcourt.
Lancer et éditer depuis /ops/dev (2026-09-26)
- Lancer : chaque carte porte un split-button. Le bouton principal
ouvre le dernier outil choisi sur le compte déjà connecté de
l'opérateur ; la flèche ouvre le panneau (Claude Code web, local,
cloud, Codex, Cursor), le modèle Claude (catalogue
ai-models.ts), un contexte libre et une capture à coller. Liens et prompt :src/services/fleet/agent-links.ts, prompt en sept blocs fixes. claude.ai/code et Codex ne lisent pas de prompt dans l'URL : il est copié, puis la page s'ouvre. Le run suivi (GitHub Action, budget) resteLaunchRunDialog, dernière ligne du panneau. - Éditer : le crayon d'une carte ou d'une décision relit le fichier
sur
mainetPOST /api/ops/dev/itemsouvre une PR draft surbacklog-edit/<pilier>-<id>-<horodatage>(src/services/fleet/item-edit.ts). Jamais de commit surmain.GITHUB_FLEET_TOKENdoit portercontents:writeetpull_requests:write.
Quand tous les jobs échouent en quelques secondes (2026-10-02)
La section GitHub (src/services/fleet/live.ts) devinait « quota Actions
épuisé, ça revient tout seul » sur la seule DURÉE des runs. Or la durée dit
que les jobs n'ont rien compilé, pas pourquoi. Le 2 octobre l'annotation du
check-run disait « recent account payments have failed or your spending
limit needs to be increased » : la facturation GitHub, à la main du
propriétaire, qui ne revient pas seule (platform-ops/2950, 3158).
- La signature de durée (
everyRunFailedInSeconds) ne conclut plus rien. Quand elle se présente, le service lit l'annotation du premier job du dernier run rouge (deux lectures de plus, jamais en état normal) et la classe (classifyBlackout) :billingsi le message parle de paiement ou de plafond de dépenses,unknownsinon. Aucun des deux cas ne dit « revient seul ». - Le cockpit et @Atlas (
getDevSection, sectiongithub, champblackout) CITENT le message de GitHub, bornés à 400 caractères, avec le lien vers la facturation de l'organisation (billingUrl) quand la cause estbilling. - Lire une annotation demande
Checks: readauGITHUB_FLEET_TOKEN. Sans, la cause resteunknownet la bannière dit que le message n'a pas été livré : on n'invente jamais une cause.