Hier, j’ai écrit un message de commit avec Claude Code. Le diff contenait un changement d’une seule ligne : une faute de frappe dans un commentaire. Claude Opus a analysé le diff, réfléchi pendant deux secondes, et généré fix: correct typo in auth comment. Pour cela, il a consommé environ 800 tokens en entrée et 30 en sortie, à $15 et $75 par million respectivement. Coût : une fraction de centime. Mais multipliez cela par 40 commits par jour, 250 jours par an, dans une entreprise comptant 200 développeurs utilisant les coding agents, et cette fraction de centime se transforme en milliers de dollars dépensés pour réaliser une tâche intellectuelle équivalente à appliquer des pansements.

Le problème n’est pas qu’Opus soit cher. Le problème, c’est que les coding agents ne distinguent pas entre les tâches à $0,001 et celles à $0,10. Tout passe par le même modèle. Générer un message de commit, classer une issue, valider un format – tout va au modèle gigantesque au même coût que concevoir une architecture de microservices. C’est l’équivalent d’embaucher un chirurgien pour appliquer des pansements.

Les chiffres

Faisons les calculs avec les tarifs de Claude Opus 4 (la génération précédente, utilisée encore par la majorité en production) :

TâcheTokens entréeTokens sortieCoût
Message de commit (petit diff)~800~30$0,014
Classer une issue~500~50$0,011
Valider le format de commit~300~20$0,006
Résumé de standup~2000~200$0,045

Aucune de ces tâches n’exige un modèle avec 2 trillions de paramètres et une capacité de raisonnement multi-pas. Ce sont des tâches de classification et de génération avec des contraintes strictes. L’équivalent de trier des cartes par couleur.

Avec le modèle on-device d’Apple Intelligence (3 milliards de paramètres, inclus dans macOS 26) : coût $0, latence ~300 ms, sans réseau, sans clé d’API.

foundation-hooks

foundation-hooks est un ensemble de 4 binaires Swift utilisant le framework Foundation Models d’Apple pour automatiser des tâches de développement qui ne nécessitent pas un modèle cloud :

BinaireFonctionHook git
fm-commit-msgGénère des messages de commit conventionnels à partir du diffprepare-commit-msg
fm-validate-msgValide le format et suggère des correctionscommit-msg
fm-lql-createClassifie et crée des issues dans Linear via lqlCLI
fm-lql-standupGénère un résumé de standup à partir de git log + issuesCLI

Tous ont le même schéma : définir une struct Swift avec @Generable, transmettre un minimum de contexte au modèle, puis obtenir une sortie structurée en millisecondes.

Installation :

git clone https://github.com/frr149/foundation-hooks
cd foundation-hooks
make build && make install-hooks REPO=/path/to/your/repo

Dès cet instant, chaque git commit génère automatiquement un message conventionnel. Le hook est installé dans 11 repos de production depuis deux semaines.

Comment ça fonctionne : @Generable et constrained decoding

C’est la partie qui mérite une attention technique. @Generable ne consiste pas à “demander au modèle de générer du JSON et espérer le meilleur”. C’est constrained decoding – le modèle ne peut littéralement pas générer des tokens qui violent le schéma.

Le mécanisme

  1. @Generable est une macro Swift qui génère un schéma JSON à la compilation à partir de la struct.
  2. Le framework injecte ce schéma dans le prompt comme spécification du format de réponse.
  3. Pendant l’inférence, à chaque étape de décodage, token masking est appliqué : les tokens du vocabulaire produisant une sortie invalide selon le schéma sont masqués (probabilité nulle dans le softmax).
  4. Le modèle ne peut choisir que parmi les tokens valides.

Apple appelle cela “guided generation” dans la documentation de la WWDC25. C’est la même technique qu’OpenAI utilise avec response_format: json_schema et qu’Anthropic applique pour l’utilisation des outils. La différence : Apple l’intègre dans le système de types de Swift. Vous définissez la struct, le compilateur génère le schéma, et le runtime l’applique pendant l’inférence. Sécurité de type de bout en bout.

Les trois niveaux de restriction

@Generable
struct CommitMessage {
    // Niveau 1 : restriction STRICTE — enum effectif
    // Token masking actif : seuls "fix", "feat", "refactor", etc. sont possibles.
    // Les tokens formant "bug" ou "update" ont une probabilité 0.
    @Guide(.anyOf(["fix", "feat", "refactor", "test", "docs", "chore", "style"]))
    var type: String

    // Niveau 2 : restriction SOUPLE — comme un prompt système pour ce champ
    // Le modèle a tendance à suivre cela, mais ce n'est pas forcé.
    @Guide(description: "Scope of the change, e.g. auth, ui, db. One word, lowercase.")
    var scope: String

    // Niveau 3 : aucune restriction — string libre, le modèle décide
    var subject: String
}

L’analogie : anyOf est un menu déroulant, description est un champ avec placeholder, et un champ sans Guide est une zone de texte vide. La différence entre les trois n’est pas graduelle mais mécanique. Le premier opère au niveau des tokens (le modèle ne peut pas s’en écarter), le deuxième au niveau du prompt (le modèle tend à suivre), le troisième n’a aucune contrainte.

C’est pertinent parce que le cas d’usage des hooks est exactement le scénario où la restriction stricte est optimale. Un commit type doit être l’un des 7 valeurs possibles. Pas d’ambiguïté, pas de créativité, pas de raisonnement. C’est de la pure classification. Un modèle de 3 milliards de paramètres avec constrained decoding le fait aussi bien qu’un modèle de 200 milliards. La différence : l’un prend 300 ms et est gratuit, l’autre prend 2 secondes et coûte de l’argent.

Le code complet d’un hook

Voici fm-commit-msg, le hook prepare-commit-msg. Il contient 106 lignes de Swift, sans dépendances externes :

import Foundation
import FoundationModels

@Generable
struct CommitMessage {
    @Guide(description: "Type of change")
    @Guide(.anyOf(["fix", "feat", "refactor", "test", "docs", "chore", "style"]))
    var type: String

    @Guide(description: "Scope of the change, e.g. auth, ui, db, api. One word, lowercase.")
    var scope: String

    @Guide(description: "Imperative summary of the change, max 50 chars, lowercase, no period")
    var subject: String
}

guard SystemLanguageModel.default.isAvailable else {
    exit(0) // Pas d'Apple Intelligence — sortir silencieusement, utilisateur écrit lui-même
}

Avant et après

ÉtapeAvec coding agent (Opus)Avec foundation-hooks
Générer un commit~2s, ~800 tokens, ~$0,014~300 ms, 0 tokens, coût 0
Valider un format~1,5s, ~300 tokens, ~$0,006~200 ms, 0 tokens, coût 0

Les coding agents sont extraordinaires dans les tâches complexes, mais leur usage uniforme n’est ni nécessaire ni rentable. Grâce à foundation-hooks, les tâches simples et courantes peuvent être automatisées localement, gratuitement.