Aller au contenu

Génération automatisée de Gram

Cette page s’adresse aux outils qui génèrent des fichiers .gram (scripts d’import, scrapers, ou modèles d’IA convertissant une recette depuis un site web, un scan de livre, un JSON-LD), plutôt qu’aux humains qui les rédigent. La commande officielle gram import fournit d’ailleurs à son modèle d’IA un prompt système (packages/cli/src/prompts/gram-spec.ts), scrupuleusement synchronisé avec ces directives. Considérez donc cette page comme la version publique et humanisée de ces mêmes instructions.

Le texte d’une recette traditionnelle contient de nombreuses étapes qui n’existent que parce que la prose n’a pas d’autre moyen de les exprimer. En Gram, la plupart d’entre elles se fondent directement dans la syntaxe au lieu de rester des étapes séparées :

  • « Couper le citron en deux » → simplement @citron{1}(coupé en deux) sur l’ingrédient qui l’utilise.
  • « Réserver l’assaisonnement » → simplement &assaisonnement lorsqu’il est référencé plus tard.
  • « Mettre de côté et laisser refroidir » → simplement ~_{30min} à la fin de l’étape qui le produit.

Un fichier .gram élégamment généré comporte souvent moins d’étapes que sa source, redessine les frontières de sections, et propose un enchaînement logique différent. Une recette source de 10 étapes peut tout à fait se résumer en 5 étapes Gram. Préserver le décompte d’étapes initial n’est pas un objectif ; la clarté, si. Face à chaque étape source, posez-vous la question : cette étape induit-elle une véritable action de cuisine, ou n’est-elle là que pour véhiculer une information que Gram pourrait exprimer de manière embarquée (inline) ? Dans ce second scénario, il est préférable de l’absorber directement dans l’ingrédient ou le minuteur plutôt que de lui dédier une étape entière.

Pour transformer une recette existante en .gram, parcourir ces cinq points dans l’ordre évite la plupart des erreurs de structure :

  1. Structure — Identifier les phases distinctes (pâte, garniture, sauce, montage…). Chaque phase avec sa propre liste d’ingrédients est une candidate pour une ## Section. Tout ce qui doit être préparé à l’avance reçoit un délai de rétroplanning (voir Structure du Document).
  2. Frontmatter — Extraire title, author, source, category, portions et les dates depuis les métadonnées disponibles dans la source. Ne jamais inventer une valeur absente de la source.
  3. Ingrédients : Listez chaque @ingrédient unique avec son nom complet et ses espaces ("huile d'olive", jamais "huile-d-olive"). Traquez ceux qui apparaissent plusieurs fois : la seconde mention (et les suivantes) doit impérativement utiliser la référence @&. Privilégiez les unités du système international pour la rigueur, mais tolérez c.à.s/c.à.c/tasse pour les petites quantités si la source l’exige. Pour désigner la fraction d’un ingrédient (jus, zeste, jaune d’œuf…), anticipez l’usage de la syntaxe composite plutôt qu’une étape de préparation isolée.
  4. Étapes : Rédigez chaque étape sous forme de paragraphe, précédé d’un préfixe d’action [Verbe], en injectant vos tags @ingrédient, #matériel, ~minuteur et ^température au fil de l’eau. Dégainez les minuteurs passifs (~_) pour les phases d’attente libérant le cuisinier (four, repos, réfrigération, pousse) et les minuteurs actifs (~) pour les tâches chronophages. Si plusieurs minuteurs passifs doivent s’enchaîner (ex: cuisson séquentielle), attribuez-leur le MÊME NOM (~_cuisson{10m} suivi de ~_cuisson{30m}) pour les séquencer automatiquement, sans pénaliser le temps actif.
  5. Relecture : Avant de valider, passez au crible les écueils détaillés ci-dessous : noms en kebab-case, unités planquées dans les accolades du matériel, @& manquant sur un ingrédient répété, déclarations ->&nom redondantes, et étapes orphelines ne servant qu’à préparer un ingrédient pour la suite.

Voici les erreurs les plus fréquentes dans les fichiers .gram générés automatiquement.

@huile-d-olive{2 c.à.s}     →   ✅  @huile d'olive{2 c.à.s}
@farine-de-ble{250g}        →   ✅  @farine de blé{250g}

En Gram, les noms d’@ingrédient sont de vrais mots avec des espaces, pas des slugs. Le kebab-case est un détail d’implémentation interne à la base de données, il n’a rien à faire dans la syntaxe de la recette.

❌  Ajouter le @jus de citron et mélanger.
✅  Ajouter le @jus de citron{} et mélanger.

❌  X @jus de citron|@vinaigre et remuer.
✅  X @jus de citron{}|@vinaigre{} et remuer.

Un @ingrédient ou #matériel composé de plusieurs mots requiert obligatoirement des {} de clôture (ou {quantité} pour le matériel). Même sans quantité définie, servez-vous d’accolades vides {}. La syntaxe courte (sans accolade) est autorisée uniquement pour les noms d’un seul mot (@sel, #poêle). Sans ces accolades sur un nom composé, seul le premier mot sera interprété, reléguant le reste à du simple texte narratif. (Notez d’ailleurs qu’au sein d’une alternative |, cette omission provoquera désormais une erreur de parsing explicite au lieu de corrompre le groupe).

#poêle{20cm}      →   ✅  #poêle(20cm)
#bol{grand}       →   ✅  #bol(grand)

Les accolades du #matériel n’acceptent que des nombres entiers. Dimensions, matériaux et descriptions vont toujours entre parenthèses — voir Matériel.

@sel{au goût}          →   ✅  @sel{}   ou   @sel
@farine{environ 200g}  →   ✅  @farine{200g}
~{environ 10 minutes}  →   ✅  ~{10min}

Les quantités et les minuteurs ont besoin d’un vrai nombre (ou d’accolades vides pour « au goût ») ; un texte flou ne se parse pas.

@&beurre{200g}   (première apparition du beurre)
@beurre{200g}    (première déclaration)
@&beurre{50g}    (deuxième utilisation, dans une étape ultérieure)

Confondre @& (ingrédient brut) et & (variable intermédiaire)

Section intitulée « Confondre @& (ingrédient brut) et & (variable intermédiaire) »
// le poulet a été déclaré via @poulet{4}, PAS comme variable intermédiaire
❌  Faire dorer le &poulet pendant ~{3min}.     // & suppose une variable intermédiaire qui n'existe pas
✅  Faire dorer le @&poulet pendant ~{3min}.    // @& = deuxième référence à un ingrédient brut

// la pâte a été produite par une étape se terminant par ->&pâte
❌  Étaler la @&pâte.                          // @& suppose un ingrédient de la liste de courses
✅  Étaler la &pâte.                           // & = référence à la variable intermédiaire déclarée

Voir Variables Intermédiaires pour la distinction complète. Plus généralement, dès qu’une étape transforme un ingrédient en un nouveau produit référencé par son nom dans les étapes suivantes (« le poulet assaisonné », « la pâte »), le déclarer avec ->&nom élimine cette ambiguïté.

Des étapes autonomes qui ne font que préparer un ingrédient

Section intitulée « Des étapes autonomes qui ne font que préparer un ingrédient »
❌  [Prép] Couper le @citron{1} en deux. Émincer finement une moitié pour la décoration.
    [Cuisson] Presser le jus de @citron{1/2} dans la poêle.

✅  [Cuisson] Presser le @jus de citron{}<@citron{1}(coupé en deux, une moitié émincée pour la décoration) dans la poêle.

Toute étape dont l’unique but se résume à « prendre X et lui faire subir Y avant l’étape principale » devrait être absorbée directement dans la référence de l’ingrédient de l’étape consommatrice. Utilisez pour cela le modificateur de préparation () : ici, on l’accroche au parent du composite (<@citron{1}(...)), car le qualificatif « coupé en deux » porte sur le citron entier, non sur son jus.

La formulation « le jus/zeste/partie de » au lieu de la syntaxe composite

Section intitulée « La formulation « le jus/zeste/partie de » au lieu de la syntaxe composite »
❌  le jus de @citron{1/2}   →   ✅  @jus de citron{}<@citron{1/2}
❌  le zeste de @citron{2}    →   ✅  @zeste de citron{1}<@citron{2}

Les ingrédients composites gardent la liste de courses exacte (la règle de chevauchement : zeste + jus du même citron s’agrègent toujours en un seul citron) et suppriment le besoin d’une étape de préparation autonome. Donnez toujours à l’enfant son nom complet (mot du parent inclus), jamais un mot générique isolé comme @jus — un nom d’enfant isolé devient sa propre entrée dans la base de données d’ingrédients partagée, sans aucune mémoire du parent d’où il provient : le « jus » d’un citron et le « jus » d’une orange ailleurs entreraient alors à tort en collision dans une seule entrée. Voir Nommer l’enfant pour le raisonnement complet.

@zeste de citron{1} < @citron{2}
@zeste de citron{1} <@citron{2}
@zeste de citron{1}<@citron{2}

Rattacher la préparation d’un composite au mauvais côté

Section intitulée « Rattacher la préparation d’un composite au mauvais côté »
@jus de citron(coupé en deux, une moitié émincée pour la décoration)<@citron{1}    // dit que le JUS a été coupé en deux
@jus de citron<@citron{1}(coupé en deux, une moitié émincée pour la décoration)    // dit que le CITRON a été coupé en deux

L’enfant et le parent d’un composite peuvent chacun porter leur propre préparation (), de manière indépendante :

  • La préparation de l’enfant se place juste après son propre nom (et sa quantité, le cas échéant).
  • La préparation du parent se place juste après <@nomParent{coûtParent}.

Rattachez-la à l’élément que la préparation décrit réellement — « coupé en deux » s’applique au citron entier, pas au jus extrait. Les deux peuvent se combiner quand c’est réellement nécessaire : @jus de citron{150 ml}(filtré)<@citron{1}(coupé en deux).

Un ->&nom redondant quand le titre de section en déclare déjà un

Section intitulée « Un ->&nom redondant quand le titre de section en déclare déjà un »
❌ ## Mélange d'Épices ->&assaisonnement

   [Mélanger] Le @paprika{2 c.à.c} et le @sel{1 c.à.c}. ->&mix1   // FAUX : la section déclare déjà la sortie

✅ ## Mélange d'Épices ->&assaisonnement

   [Mélanger] Le @paprika{2 c.à.c} et le @sel{1 c.à.c}.           // aucune déclaration locale nécessaire

Un ->&nom au niveau d’une section capture déjà la sortie de toutes les étapes qu’elle contient — voir Variables Intermédiaires.

❌  Cuire au four pendant ~{45min}.        // suppose que le cuisinier reste occupé 45 minutes
✅  Cuire au four pendant ~_{45min}.       // le four fait le travail, le cuisinier est libre
✅  Pétrir pendant ~{10min}.               // le cuisinier EST occupé — un minuteur actif est correct ici

// commente jusqu’à la fin de la ligne : tout ce qui suit — ingrédients compris — disparaît en silence. Le fichier compile quand même, sans le moindre avertissement, en étant simplement amputé.

❌  [Mélanger] Combiner @farine{} // quantité non indiquée, @sucre{} et @sel{}.
✅  [Mélanger] Combiner @farine{} /* quantité non indiquée */, @sucre{} et @sel{}.
✅  [Mélanger] Combiner @farine{}, @sucre{} et @sel{}.  // quantités non indiquées

À l’intérieur d’une étape, utilisez un commentaire bloc. Réservez // à la toute fin d’une ligne, ou à une ligne entière. gram import détecte spécifiquement cette perte et refuse un résultat où elle s’est produite.