Format d'échange de revue
L'échange de revue (« review exchange ») est le format de fichier de leanreview pour les conversations de revue hors ligne : un document JSON unique portant un diff unifié et les commentaires qui s'y rapportent, passé d'un outil à l'autre. La boucle canonique est un LLM qui écrit une revue, un humain qui l'édite dans la TUI leanreview, et le LLM qui relit le résultat — mais deux parties quelconques parlant ce format peuvent tenir la conversation.
- Type de média : JSON (UTF-8). Nom de fichier recommandé :
*.review.json. - Version :
1(cette page). Un schéma JSON lisible par machine est publié pour la validation et l'outillage éditeur. - Détection : par le contenu, pas par le nom de fichier — la présence de
la clé
"leanreview_review"près du début d'un objet JSON.leanreview <file>ouvre tout fichier qui se reconnaît comme un échange en mode conversation.
Exemple
{
"leanreview_review": 1,
"title": "auth refactor",
"summary": "Two issues, one blocking.",
"patch": [
"diff --git a/internal/api/handler.go b/internal/api/handler.go",
"--- a/internal/api/handler.go",
"+++ b/internal/api/handler.go",
"@@ -70,5 +70,6 @@ func Handle(input Input) (Output, error) {",
" ctx := context.Background()",
" defer cancel(ctx)",
"- result := calculate(input)",
"+ result, err := calculate(input)",
"+ _ = err",
" return result, nil"
],
"comments": [
{
"id": "c1",
"author": "assistant",
"path": "internal/api/handler.go",
"side": "RIGHT",
"start_line": 72,
"body": "`err` is assigned and discarded — return it instead.",
"state": "active",
"snippet": "result, err := calculate(input)",
"replies": [
{ "author": "hduran", "body": "Agreed, but only log it: callers can't handle it." }
]
}
]
}
Champs du document
| Champ | Requis | Signification |
|---|---|---|
leanreview_review |
oui | Version du format, entier. Les lecteurs doivent rejeter les versions inconnues. À garder comme première clé : elle sert aussi de marqueur de détection. |
title |
non | Titre de la revue lisible par un humain (affiché dans la barre de titre de la TUI). |
summary |
non | Aperçu/verdict au niveau de la revue ; fait l'aller-retour vers le résumé de soumission. |
patch |
oui | Le diff unifié auquel les commentaires s'ancrent (voir ci-dessous). |
comments |
oui | La conversation ; peut être vide. |
Les champs inconnus, où qu'ils soient dans le document, sont tolérés :
les lecteurs les ignorent, ce qui permet une évolution additive sans
incrémenter la version — mais les intermédiaires (y compris la réécriture de
leanreview) ne préservent pas les champs qu'ils ne connaissent pas, donc les
générateurs ne doivent pas compter sur la survie de champs supplémentaires à
un aller-retour. Les changements incompatibles incrémentent
leanreview_review.
Le patch
Le patch est le diff unifié complet (style git) dont traite la conversation. L'intégrer — plutôt que de référencer un commit — est ce qui rend le fichier autonome et rend la conversation possible hors ligne.
Les générateurs doivent l'émettre comme un tableau JSON de lignes,
une ligne de diff par élément, sans élément vide final (le saut de ligne
final est implicite). Les lecteurs doivent aussi accepter une chaîne
unique (avec des \n intégrés) par tolérance envers les générateurs
rudimentaires.
La forme en tableau est délibérée : elle garde le fichier lisible pour les humains, fait que les allers-retours produisent des diffs texte significatifs ligne par ligne (le fichier de conversation lui-même est souvent conservé dans git), et permet aux éditeurs de surligner le diff intégré — voir support éditeur.
Échappement
Il n'y a pas de mise entre guillemets propre au format : l'échappement
appartient entièrement au codec JSON. Guillemets, antislashs, tabulations,
caractères de contrôle et texte non ASCII dans les lignes du diff sont des
échappements de chaînes JSON ordinaires et survivent à l'aller-retour
octet pour octet. Les rédacteurs devraient désactiver l'échappement
HTML (émettre <, >, & littéralement — p. ex. SetEscapeHTML(false)
en Go) pour que le code reste lisible ; les échappements de type \u003c
restent néanmoins du JSON valide, et les lecteurs traitent les deux
écritures de façon identique.
Commentaires
| Champ | Requis | Signification |
|---|---|---|
id |
recommandé | Identité stable à travers les allers-retours. leanreview préserve les ids et en assigne des aléatoires quand ils sont absents. |
author |
non | Attribution libre ("assistant", un nom d'utilisateur). Affichée dans la TUI. |
path |
oui | Chemin du fichier tel qu'il apparaît dans le patch. |
side |
oui | LEFT (image ancienne : lignes supprimées) ou RIGHT (image nouvelle : lignes ajoutées/de contexte). Insensible à la casse. |
start_line |
oui | Ligne 1-indexée dans cette image du patch. |
end_line |
non | Fin de la portée multi-lignes incluse ; par défaut start_line. |
body |
oui | Texte du commentaire en Markdown. |
state |
non | active (par défaut), dismissed, orphaned, ou stale. |
snippet |
non | La ou les lignes de diff ancrées, à titre informatif. Recalculé si vide. |
at |
non | Horodatage de création au format RFC 3339. |
replies |
non | Réponses, de la plus ancienne à la plus récente : { "author", "body", "at" } (at optionnel). |
Les horodatages sont optionnels mais font partie de la version 1 à dessein : les intermédiaires ne conservent pas les champs inconnus, donc tout ce qu'un aller-retour doit transporter doit exister dès le départ. Les réponses s'accumulent au fil des rondes, et leur chronologie fait partie de la conversation.
États — le protocole de conversation
state est la façon dont le verdict de l'humain voyage jusqu'à l'auteur de
la revue :
active— le commentaire tient. Le prochain acteur doit le traiter.dismissed— un humain l'a rejeté. Il est conservé dans le fichier (le verdict lui-même est une information) mais ne doit pas être traité ni soumis à nouveau. Dans la TUI,xbascule le rejet.orphaned— l'ancrage ne se résout plus contre le patch (ou le diff suivant). leanreview positionne cet état à l'import quand(path, side, start_line)n'existe pas dans le patch intégré ; les commentaires orphelins ne sont jamais soumis.stale— l'ancrage a peut-être bougé ; une passe de réancrage est en attente.
Un commentaire dismissed garde son état même quand il ne s'ancre plus : la
décision humaine prime sur l'échec d'ancrage.
Règles d'ancrage
Les numéros de ligne sont interprétés par rapport au patch intégré, avec
la même sémantique que les commentaires de revue des forges : side choisit
l'image pré-/post-, et start_line est le numéro de ligne 1-indexé dans
cette image. À l'import, leanreview résout chaque commentaire par rapport au
patch analysé :
- résoluble → le commentaire reçoit un ancrage de contexte (lignes environnantes), pour pouvoir survivre à de futures révisions du diff par correspondance de contenu ;
- non résoluble → le commentaire est importé comme
orphanedplutôt que d'être abandonné ou deviné : un décalage d'un générateur est une condition visible et corrigible, jamais une perte de données silencieuse.
Les générateurs devraient revérifier les numéros de ligne par rapport au
patch qu'ils intègrent ; le champ snippet existe pour que les lecteurs
(humains ou LLM) puissent repérer les décalages d'un coup d'œil.
Garanties d'aller-retour
Quand leanreview ouvre un fichier d'échange, les modifications ont lieu dans
la TUI et chaque enregistrement de brouillon réécrit le fichier sur
place (quitter enregistre aussi). Ce qui suit survit à un aller-retour sans
changement : les id des commentaires, les author, les horodatages at, les replies, le
patch, title, et summary. La TUI ne change que ce que l'humain a
changé : les corps, les états, et l'ensemble des commentaires
(ajoutés/supprimés).
La sortie est déterministe : indentation de deux espaces, ordre des clés stable, une ligne de diff par élément du patch, saut de ligne final. Les allers-retours successifs d'une revue inchangée sont identiques octet par octet.
Notes de conception
Le format a connu plusieurs itérations délibérées avant d'être figé :
- JSON plutôt qu'un format texte maison. Un format « diff entrelacé avec blocs de commentaires » a été envisagé — il se lit agréablement et ancre les commentaires implicitement. Il a été écarté parce que les principaux générateurs et lecteurs sont des programmes (les LLM émettent du JSON valide bien plus fiablement qu'une syntaxe maison), l'humain édite dans la TUI plutôt que dans un éditeur de texte, et un analyseur personnalisé est une source infinie de bugs d'échappement.
- Patch en tableau de lignes, pas en chaîne. La première ébauche
intégrait le patch comme une seule chaîne JSON ; un diff plein de
\néchappés sur une seule ligne est illisible, indiffable, et in-surlignable. La forme en tableau règle les trois problèmes sans coût d'analyse supplémentaire, avec la tolérance aux chaînes conservée pour les générateurs minimalistes. - États plutôt que suppression. Les commentaires rejetés restent dans le fichier parce que le rejet est précisément ce que l'autre partie de la conversation a le plus besoin de savoir.
Support éditeur
Le répertoire
editors/ du
dépôt fournit un support prêt à l'emploi :
- Vim / Neovim — un filetype (
lreview) pour*.review.jsonqui superpose des couleurs de diff (lignes ajoutées/supprimées/hunk dans le tableaupatch) à la coloration syntaxique JSON. - VS Code — une extension minimale avec la même grammaire superposée, plus une association de schéma JSON pour la validation et l'autocomplétion.
- IDE JetBrains — un mappage de schéma JSON (validation + autocomplétion) via l'URL du schéma publié ; la grammaire TextMate de l'extension VS Code peut être importée pour les couleurs de diff.
Tous s'appuient sur la convention de nommage *.review.json — une raison de
plus de la suivre.