@imenam/mcp-feature-analyzer 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +18 -4
  2. package/dist/shared/explanation-request.d.ts +9 -0
  3. package/dist/shared/explanation-request.js +55 -0
  4. package/dist/shared/schemas/analysis.schema.d.ts +61 -0
  5. package/dist/shared/schemas/analysis.schema.js +26 -0
  6. package/dist/shared/schemas/api.schema.d.ts +13 -0
  7. package/dist/shared/schemas/api.schema.js +8 -0
  8. package/dist/shared/schemas/ipc.schema.d.ts +1 -1
  9. package/dist/shared/schemas/ipc.schema.js +1 -0
  10. package/dist/src/core/analysis-store.js +1 -0
  11. package/dist/src/core/ids.d.ts +1 -0
  12. package/dist/src/core/ids.js +1 -0
  13. package/dist/src/core/invariants.d.ts +3 -1
  14. package/dist/src/core/invariants.js +27 -1
  15. package/dist/src/core/locations.d.ts +22 -4
  16. package/dist/src/core/locations.js +88 -4
  17. package/dist/src/core/migrate.d.ts +2 -0
  18. package/dist/src/core/migrate.js +3 -1
  19. package/dist/src/core/refresh.d.ts +8 -2
  20. package/dist/src/core/refresh.js +19 -1
  21. package/dist/src/core/review-actions.d.ts +6 -1
  22. package/dist/src/core/review-actions.js +20 -0
  23. package/dist/src/gui-worker.js +7 -1
  24. package/dist/src/index.js +100 -9
  25. package/dist/src/tools/add-explanations.d.ts +9 -0
  26. package/dist/src/tools/add-explanations.js +73 -0
  27. package/dist/src/tools/create-analysis.js +1 -1
  28. package/dist/src/tools/delete-explanations.d.ts +4 -0
  29. package/dist/src/tools/delete-explanations.js +23 -0
  30. package/dist/src/tools/format.d.ts +3 -1
  31. package/dist/src/tools/format.js +5 -0
  32. package/dist/src/tools/get-analysis.js +6 -1
  33. package/dist/src/tools/get-diff.js +2 -1
  34. package/dist/src/tools/inputs.d.ts +14 -0
  35. package/dist/src/tools/inputs.js +20 -2
  36. package/dist/src/tools/refresh-analysis.js +8 -2
  37. package/dist/src/tools/update-explanation.d.ts +7 -0
  38. package/dist/src/tools/update-explanation.js +57 -0
  39. package/dist/src/version.d.ts +1 -1
  40. package/dist/src/version.js +1 -1
  41. package/gui/dist/assets/index-BKGeP-g-.js +74 -0
  42. package/gui/dist/assets/index-CtYu4TyO.css +1 -0
  43. package/gui/dist/index.html +2 -2
  44. package/package.json +1 -1
  45. package/gui/dist/assets/index-Db7Hu3tM.js +0 -72
  46. package/gui/dist/assets/index-Du5eL78F.css +0 -1
package/README.md CHANGED
@@ -163,6 +163,18 @@ L'**état de revue** est dérivé des fichiers revus et de la soumission (`share
163
163
 
164
164
  Un `issue` a toujours un emplacement ; un `requirement_gap` peut ne pas en avoir. Une `suggestion` n'est jamais appliquée : la GUI l'affiche (lignes actuelles barrées, lignes proposées) et le prompt la transmet.
165
165
 
166
+ ### Explications
167
+
168
+ Une explication décrit ce que fait un bloc de code ajouté ou supprimé, et comment il le fait. Elle aide le relecteur à comprendre le code avant de le juger : ce n'est pas un constat, elle ne demande rien et n'entre pas dans le retour de revue. Les instructions du serveur demandent à l'agent d'en écrire une pour chaque bloc long (une trentaine de lignes ou plus) ou complexe (algorithme, machine à états, concurrence, expression régulière délicate…), et jamais pour du code trivial.
169
+
170
+ | Outil | Effet |
171
+ |---|---|
172
+ | `add_explanations` | Ajoute un lot d'explications : titre, corps, `path` + `start_line` (+ `end_line`) et `side`. `side: "new"` (par défaut) vise du code ajouté ou conservé, en numéros du côté nouveau ; `side: "old"` vise du code supprimé, en numéros du côté ancien (première colonne de `get_diff`), lignes toutes présentes dans le diff et dont au moins une est supprimée. Les lignes décrites sont copiées du diff figé. Une seule explication invalide et **rien n'est ajouté**. |
173
+ | `update_explanation` | Modifie le titre, le corps ou l'emplacement d'une explication. Un nouvel emplacement est vérifié contre le diff et rend son statut « à jour » à une explication obsolète. |
174
+ | `delete_explanations` | Supprime des explications par identifiant. Un identifiant inconnu fait échouer l'appel sans rien supprimer. |
175
+
176
+ `refresh_analysis` traite les explications comme les constats : une explication dont les lignes ne correspondent plus au texte copié passe à « obsolète », et redevient à jour si elles correspondent de nouveau.
177
+
166
178
  ### Schémas
167
179
 
168
180
  | Outil | Effet |
@@ -205,6 +217,7 @@ create_analysis { repo_path: "C:/dev/mon-projet", project: "mon-projet", title:
205
217
  get_diff { analysis_id } ← numéros de ligne du côté nouveau
206
218
  update_analysis { analysis_id, objective: "…", approach: "…", attention_points: ["…"],
207
219
  summary: ["…", "…"] } ← vue d'ensemble et changements fonctionnels
220
+ add_explanations { analysis_id, explanations: [...] } ← blocs longs ou complexes, ajoutés ou supprimés
208
221
  add_findings { analysis_id, findings: [...] } ← y compris les requirement_gap
209
222
  set_diagram { analysis_id, kind: "flow", nodes: [...], links: [...] }
210
223
 
@@ -242,14 +255,14 @@ L'ajustement à la fenêtre ne descend jamais sous l'échelle **0,85** : en dess
242
255
 
243
256
  - **Panneau des fichiers** — progression, fichiers groupés par dossier, état revu, pastilles des constats ouverts par gravité. Un nom trop long est tronqué au milieu (`reminder-sch…uler.ts`), l'extension reste visible et le chemin complet est en infobulle. Chaque pastille est un lien : elle ouvre le fichier sur son premier constat ouvert de cette gravité (`?line=N`) ; son infobulle le dit (« 3 constats mineurs ouverts — aller au premier »).
244
257
  - **En-tête du fichier** — chemin, `+`/`−`, statut, fichier précédent et suivant, « Remarque sur le fichier », « Marquer comme revu ».
245
- - **Diff** — numéros du côté nouveau et repère vertical à la couleur de la gravité le long des lignes visées par un constat. Le correctif proposé s'affiche à l'endroit où il s'applique : bandeau « Correctif proposé par l'agent — remplace les lignes X à Y » avec le numéro du constat et « Masquer le correctif », lignes visées barrées sur fond ambre, lignes proposées juste en dessous sur fond violet (marque « › », sans numéro). Le correctif est affiché d'office pour un constat ouvert ; masqué, ou pour un constat sans correctif, une pastille numérotée marque la première ligne visée. Deux correctifs dont les lignes se recouvrent ne s'affichent pas ensemble. Le fond ambre des lignes visées se distingue du rouge des lignes `−` supprimées par la feature. Une légende suit le diff, limitée aux fonds que le mode affiche : « Ajouté par la feature », « Supprimé par la feature », « Lignes remplacées par le correctif » (Avant / après), « Correctif proposé » ou « Correctif appliqué ». Un clic sur un numéro de ligne ouvre la saisie d'une remarque sur cette ligne. Les exigences manquantes sans emplacement s'affichent en bandeau au-dessus du diff de chaque fichier ; le bandeau « N exigences manquantes » se replie, et reste replié pour la session.
258
+ - **Diff** — numéros du côté nouveau et repère vertical à la couleur de la gravité le long des lignes visées par un constat. Le correctif proposé s'affiche à l'endroit où il s'applique : bandeau « Correctif proposé par l'agent — remplace les lignes X à Y » avec le numéro du constat et « Masquer le correctif », lignes visées barrées sur fond ambre, lignes proposées juste en dessous sur fond violet (marque « › », sans numéro). Le correctif est affiché d'office pour un constat ouvert ; masqué, ou pour un constat sans correctif, une pastille numérotée marque la première ligne visée. Un filet sarcelle longe les lignes décrites par une explication. L'en-tête de chaque bloc du diff porte « Copier une demande d'explication » : le bouton copie un prompt à coller dans la conversation de l'agent, qui situe le bloc (analyse, dépôt et refs, fichier, lignes des deux côtés, code tel que figé) et lui demande de l'expliquer en enregistrant ses explications avec `add_explanations`, ou `update_explanation` si une explication couvre déjà ces lignes. Deux correctifs dont les lignes se recouvrent ne s'affichent pas ensemble. Le fond ambre des lignes visées se distingue du rouge des lignes `−` supprimées par la feature. Une légende suit le diff, limitée aux fonds que le mode affiche : « Ajouté par la feature », « Supprimé par la feature », « Lignes remplacées par le correctif » (Avant / après), « Correctif proposé » ou « Correctif appliqué », « Code décrit par une explication ». Un clic sur un numéro de ligne ouvre la saisie d'une remarque sur cette ligne. Les exigences manquantes sans emplacement s'affichent en bandeau au-dessus du diff de chaque fichier ; le bandeau « N exigences manquantes » se replie, et reste replié pour la session.
246
259
  - **Affichage des correctifs** — le sélecteur « Correctifs », au-dessus du diff, propose trois modes, conservés d'un fichier à l'autre pendant la session :
247
260
  - « Sans correctif » : le diff seul, sans aucun correctif ; les interrupteurs des cartes sont inactifs.
248
261
  - « Avant / après » (par défaut) : le rendu décrit ci-dessus, lignes visées barrées puis lignes proposées.
249
262
  - « Code corrigé » : le code tel qu'il se lirait avec les correctifs appliqués. Les lignes visées disparaissent ; les lignes proposées prennent leur place, sur fond violet avec la marque « › » et sans numéro, sous un bandeau « Correctif N appliqué — remplace les lignes X à Y ». Un lien `?line=N` vers une ligne retirée met ce bandeau en évidence ; les remarques ne se posent que sur les lignes du diff, pas sur les lignes proposées.
250
263
 
251
264
  Dans les deux derniers modes, l'interrupteur de chaque carte (« Afficher le correctif dans le code » ou « Appliquer le correctif dans le code ») choisit les correctifs dessinés.
252
- - **Colonne « Constats de ce fichier »** — l'en-tête compte les constats ouverts, et le total quand il diffère (« 4 ouverts · 5 au total »). Dessous, un index compact des constats (numéro à la couleur de la gravité et première ligne, ignorés et obsolètes atténués) : un clic fait défiler jusqu'aux lignes et à la carte, mises en évidence comme avec `?line`. Tant que des cartes de constat sont sous la partie visible, une indication collée en bas de la colonne les compte (« 3 constats plus bas ↓ ») ; un clic amène la suivante. Les constats, numérotés dans l'ordre de leur première ligne, sont des cartes alignées sur leur ligne et reliées au diff ; des cartes voisines s'empilent sans se chevaucher et défilent avec le diff. Chaque carte montre numéro, gravité, lignes, titre, explication, l'interrupteur « Afficher le correctif dans le code » (constat avec correctif) et les boutons « Copier le prompt », « Ignorer » / « Rouvrir », « Remarque ». Les constats ignorés ou obsolètes restent visibles, atténués. Les remarques de ligne sont des cartes alignées de la même façon, datées au format court (date complète en infobulle) ; les remarques sur le fichier entier et ce qui vise des lignes hors du diff affiché sont en tête de colonne. Sous 1200 px de large, la colonne passe sous le diff et ses cartes s'empilent.
265
+ - **Colonne « Constats et explications »** — l'en-tête compte les constats ouverts, et le total quand il diffère (« 4 constats ouverts · 5 au total »). Deux boutons, « Constats » et « Explications », affichent ou masquent chacun leur type, indépendamment l'un de l'autre et pour tous les fichiers de la session ; chacun indique le nombre d'éléments du fichier. Masquer les constats retire aussi leurs repères, pastilles et correctifs du diff et le bandeau des exigences manquantes : on parcourt alors le code avec ses seules explications, avant de rétablir les constats. Masquer les explications retire leurs cartes et leurs filets. Dessous, un index compact des constats (numéro à la couleur de la gravité et première ligne, ignorés et obsolètes atténués) : un clic fait défiler jusqu'aux lignes et à la carte, mises en évidence comme avec `?line`. Tant que des cartes de constat sont sous la partie visible, une indication collée en bas de la colonne les compte (« 3 constats plus bas ↓ ») ; un clic amène la suivante. Les constats, numérotés dans l'ordre de leur première ligne, sont des cartes alignées sur leur ligne et reliées au diff ; des cartes voisines s'empilent sans se chevaucher et défilent avec le diff. Chaque carte montre numéro, gravité, lignes, titre, explication, l'interrupteur « Afficher le correctif dans le code » (constat avec correctif) et les boutons « Copier le prompt », « Ignorer » / « Rouvrir », « Remarque ». Les constats ignorés ou obsolètes restent visibles, atténués. Les explications sont des cartes sarcelle alignées de la même façon sur la première ligne qu'elles décrivent, une ligne supprimée pour du code supprimé ; à hauteur égale, l'explication précède les constats. Chaque carte montre les lignes (côté ancien et mention « Code supprimé » pour du code supprimé), le titre et le texte, replié au-delà de huit lignes avec « Lire la suite ». Une explication obsolète est atténuée et signale que le code décrit a changé. Les remarques de ligne sont des cartes alignées de la même façon, datées au format court (date complète en infobulle), avec « Modifier », qui rouvre le texte sur place dans la même saisie que la création, et « Supprimer » ; modifier une remarque retenue dans une revue déjà soumise recalcule le prompt enregistré ; les remarques sur le fichier entier et ce qui vise des lignes hors du diff affiché sont en tête de colonne. Sous 1200 px de large, la colonne passe sous le diff et ses cartes s'empilent.
253
266
 
254
267
  Un fichier binaire, supprimé ou de plus de 1 Mo n'a pas de contenu conservé : on ne peut pas y ancrer de constat ni de remarque de ligne, seulement une remarque sur le fichier entier.
255
268
 
@@ -274,15 +287,16 @@ Le worker ne lit ni n'écrit aucune donnée : chaque requête REST est transmise
274
287
 
275
288
  ### Persistance
276
289
 
277
- Un fichier JSON par analyse dans `<données>/analyses/<id>.json`, et son diff figé dans `<données>/diffs/<id>.json`. Les écritures sont atomiques (fichier temporaire puis `rename`), sérialisées entre processus par un verrou `.lock`, et le cache mémoire est invalidé dès que la date ou la taille du fichier change. Tout document est validé par zod à la lecture et à l'écriture ; `src/core/migrate.ts` met à niveau les anciens formats à la lecture : une analyse sans projet reçoit le nom du dossier du dépôt (`basename(repoPath)`), une analyse sans vue d'ensemble reçoit `overview: null`.
290
+ Un fichier JSON par analyse dans `<données>/analyses/<id>.json`, et son diff figé dans `<données>/diffs/<id>.json`. Les écritures sont atomiques (fichier temporaire puis `rename`), sérialisées entre processus par un verrou `.lock`, et le cache mémoire est invalidé dès que la date ou la taille du fichier change. Tout document est validé par zod à la lecture et à l'écriture ; `src/core/migrate.ts` met à niveau les anciens formats à la lecture : une analyse sans projet reçoit le nom du dossier du dépôt (`basename(repoPath)`), une analyse sans vue d'ensemble reçoit `overview: null`, une analyse sans explications reçoit une liste vide.
278
291
 
279
292
  ### Invariants
280
293
 
281
294
  Toute modification, qu'elle vienne d'un outil ou de la GUI, passe par `AnalysisStore.mutate(id, editor, fn, { baseRevision })`, qui vérifie le schéma et les invariants avant d'écrire et incrémente la révision :
282
295
 
283
296
  - refs cohérentes avec le mode (`branch` : ref et commit de tête ; `working_tree` : base `HEAD`, sans tête) ;
284
- - identifiants uniques (fichiers, constats, remarques, schémas, nœuds d'un schéma) ;
297
+ - identifiants uniques (fichiers, constats, explications, remarques, schémas, nœuds d'un schéma) ;
285
298
  - un constat ouvert vise un fichier de l'analyse dont le contenu est conservé, avec `1 ≤ startLine ≤ endLine ≤` nombre de lignes ; un `issue` a un emplacement ;
299
+ - une explication à jour vise un fichier de l'analyse ; côté nouveau, son contenu est conservé et ses lignes existent ;
286
300
  - les liens d'un schéma relient des nœuds existants ; en `layers`, chaque nœud est dans une couche déclarée ;
287
301
  - la revue ne sélectionne que des constats et des remarques existants ;
288
302
  - les champs issus du diff (dépôt, refs, commits, liste des fichiers hors « revu ») ne changent que par `refresh_analysis`.
@@ -0,0 +1,9 @@
1
+ import type { Analysis, FileEntry } from "./schemas/analysis.schema.js";
2
+ import type { Hunk } from "./schemas/diff.schema.js";
3
+ /**
4
+ * Prompt qui demande à l'agent d'expliquer un bloc du diff et d'enregistrer ses
5
+ * explications dans l'analyse : analyse, dépôt et refs, fichier, plages de lignes
6
+ * des deux côtés, code du bloc tel que figé, puis la marche à suivre
7
+ * (add_explanations, ou update_explanation pour une explication existante).
8
+ */
9
+ export declare function buildExplanationRequestPrompt(analysis: Analysis, file: FileEntry, hunk: Hunk): string;
@@ -0,0 +1,55 @@
1
+ import { lineRange } from "./text.js";
2
+ const MARKERS = { context: " ", add: "+", del: "-" };
3
+ function refsLabel(analysis) {
4
+ if (analysis.mode === "branch")
5
+ return `branch ${analysis.base}...${analysis.head}`;
6
+ return "working tree changes against HEAD";
7
+ }
8
+ /** Plage `début-fin` d'un côté du bloc, ou null quand ce côté n'a aucune ligne. */
9
+ function sideRange(start, count) {
10
+ return count === 0 ? null : lineRange(start, start + count - 1);
11
+ }
12
+ /** Lignes du bloc au format de get_diff : `ancien | nouveau`, puis le marqueur et le texte. */
13
+ function hunkCode(hunk) {
14
+ let width = 1;
15
+ for (const line of hunk.lines) {
16
+ width = Math.max(width, String(line.oldNo ?? "").length, String(line.newNo ?? "").length);
17
+ }
18
+ const column = (value) => String(value ?? "").padStart(width);
19
+ return [hunk.header, ...hunk.lines.map((line) => `${column(line.oldNo)} | ${column(line.newNo)} ${MARKERS[line.type]} ${line.text}`)];
20
+ }
21
+ /**
22
+ * Prompt qui demande à l'agent d'expliquer un bloc du diff et d'enregistrer ses
23
+ * explications dans l'analyse : analyse, dépôt et refs, fichier, plages de lignes
24
+ * des deux côtés, code du bloc tel que figé, puis la marche à suivre
25
+ * (add_explanations, ou update_explanation pour une explication existante).
26
+ */
27
+ export function buildExplanationRequestPrompt(analysis, file, hunk) {
28
+ const newRange = sideRange(hunk.newStart, hunk.newLines);
29
+ const oldRange = sideRange(hunk.oldStart, hunk.oldLines);
30
+ const where = [
31
+ `- File: ${file.path}${file.oldPath !== null ? ` (renamed from ${file.oldPath})` : ""}`,
32
+ newRange === null ? "- New side: no line (the section only removes code)." : `- New side (code after the feature): lines ${newRange}`,
33
+ oldRange === null ? "- Old side: no line (the section only adds code)." : `- Old side (code before the feature): lines ${oldRange}`,
34
+ ];
35
+ return [
36
+ `Explain a section of code in the feature analysis "${analysis.title}" (analysis_id: "${analysis.id}", project "${analysis.project}").`,
37
+ `Repository: ${analysis.repoPath} — ${refsLabel(analysis)}.`,
38
+ "",
39
+ "Section to explain:",
40
+ ...where,
41
+ "",
42
+ "Code of the section, as frozen in the analysis (columns: old line | new line, then + added, - removed, or a space for context):",
43
+ "```",
44
+ ...hunkCode(hunk),
45
+ "```",
46
+ "",
47
+ "What to do:",
48
+ `1. Read this code and what surrounds it (get_diff with analysis_id "${analysis.id}" and path "${file.path}", and the repository) until you understand what it does and how it works.`,
49
+ `2. Record your explanations in the feature analyzer with add_explanations (analysis_id "${analysis.id}", path "${file.path}"): one explanation per meaningful block of the section, covering it from its first to its last line. ` +
50
+ `Use "side": "new" with new-side line numbers for added or kept code, and "side": "old" with old-side line numbers for removed code.`,
51
+ "3. In each explanation, say what the code does (inputs, outputs, effects), then how it works, step by step in the order of the code; name the key variables and the edge cases it handles. For removed code, say what it did and what replaces it. Describe, do not judge.",
52
+ "4. If an explanation of the analysis already covers these lines (see get_analysis), rewrite it with update_explanation instead of adding a duplicate.",
53
+ "5. Then tell me in a few lines which explanations you recorded.",
54
+ ].join("\n");
55
+ }
@@ -4,6 +4,8 @@ export declare const FILE_STATUSES: readonly ["added", "modified", "deleted", "r
4
4
  export declare const SEVERITIES: readonly ["critical", "major", "minor", "trivial"];
5
5
  export declare const FINDING_KINDS: readonly ["issue", "requirement_gap"];
6
6
  export declare const FINDING_STATUSES: readonly ["open", "ignored", "outdated"];
7
+ export declare const EXPLANATION_SIDES: readonly ["new", "old"];
8
+ export declare const EXPLANATION_STATUSES: readonly ["current", "outdated"];
7
9
  export declare const DIAGRAM_KINDS: readonly ["flow", "layers", "mindmap"];
8
10
  export declare const NODE_SHAPES: readonly ["box", "pill", "decision"];
9
11
  export declare const NODE_STATUSES: readonly ["new", "modified", "impacted", "existing", "finding", "missing"];
@@ -66,6 +68,41 @@ export declare const FindingSchema: z.ZodObject<{
66
68
  }>;
67
69
  createdAt: z.ZodString;
68
70
  }, z.core.$strip>;
71
+ /**
72
+ * Plage de lignes décrite par une explication, bornes incluses : côté `new` (numéros
73
+ * du côté « nouveau », code ajouté ou conservé) ou côté `old` (numéros du côté
74
+ * « ancien », code supprimé).
75
+ */
76
+ export declare const ExplanationLocationSchema: z.ZodObject<{
77
+ path: z.ZodString;
78
+ side: z.ZodEnum<{
79
+ new: "new";
80
+ old: "old";
81
+ }>;
82
+ startLine: z.ZodNumber;
83
+ endLine: z.ZodNumber;
84
+ }, z.core.$strip>;
85
+ /** Description, par l'agent, de ce que fait un bloc de code et de la façon dont il le fait. */
86
+ export declare const ExplanationSchema: z.ZodObject<{
87
+ id: z.ZodString;
88
+ title: z.ZodString;
89
+ body: z.ZodString;
90
+ location: z.ZodObject<{
91
+ path: z.ZodString;
92
+ side: z.ZodEnum<{
93
+ new: "new";
94
+ old: "old";
95
+ }>;
96
+ startLine: z.ZodNumber;
97
+ endLine: z.ZodNumber;
98
+ }, z.core.$strip>;
99
+ anchorText: z.ZodString;
100
+ status: z.ZodEnum<{
101
+ current: "current";
102
+ outdated: "outdated";
103
+ }>;
104
+ createdAt: z.ZodString;
105
+ }, z.core.$strip>;
69
106
  /** Emplacement d'une remarque : une ligne du côté « nouveau », ou le fichier entier (`line` null). */
70
107
  export declare const NoteLocationSchema: z.ZodObject<{
71
108
  path: z.ZodString;
@@ -247,6 +284,26 @@ export declare const AnalysisSchema: z.ZodObject<{
247
284
  }>;
248
285
  createdAt: z.ZodString;
249
286
  }, z.core.$strip>>;
287
+ explanations: z.ZodArray<z.ZodObject<{
288
+ id: z.ZodString;
289
+ title: z.ZodString;
290
+ body: z.ZodString;
291
+ location: z.ZodObject<{
292
+ path: z.ZodString;
293
+ side: z.ZodEnum<{
294
+ new: "new";
295
+ old: "old";
296
+ }>;
297
+ startLine: z.ZodNumber;
298
+ endLine: z.ZodNumber;
299
+ }, z.core.$strip>;
300
+ anchorText: z.ZodString;
301
+ status: z.ZodEnum<{
302
+ current: "current";
303
+ outdated: "outdated";
304
+ }>;
305
+ createdAt: z.ZodString;
306
+ }, z.core.$strip>>;
250
307
  notes: z.ZodArray<z.ZodObject<{
251
308
  id: z.ZodString;
252
309
  location: z.ZodNullable<z.ZodObject<{
@@ -323,6 +380,8 @@ export type FileStatus = (typeof FILE_STATUSES)[number];
323
380
  export type Severity = (typeof SEVERITIES)[number];
324
381
  export type FindingKind = (typeof FINDING_KINDS)[number];
325
382
  export type FindingStatus = (typeof FINDING_STATUSES)[number];
383
+ export type ExplanationSide = (typeof EXPLANATION_SIDES)[number];
384
+ export type ExplanationStatus = (typeof EXPLANATION_STATUSES)[number];
326
385
  export type DiagramKind = (typeof DIAGRAM_KINDS)[number];
327
386
  export type NodeShape = (typeof NODE_SHAPES)[number];
328
387
  export type NodeStatus = (typeof NODE_STATUSES)[number];
@@ -332,6 +391,8 @@ export type Editor = (typeof EDITORS)[number];
332
391
  export type FileEntry = z.infer<typeof FileEntrySchema>;
333
392
  export type FindingLocation = z.infer<typeof FindingLocationSchema>;
334
393
  export type Finding = z.infer<typeof FindingSchema>;
394
+ export type ExplanationLocation = z.infer<typeof ExplanationLocationSchema>;
395
+ export type Explanation = z.infer<typeof ExplanationSchema>;
335
396
  export type NoteLocation = z.infer<typeof NoteLocationSchema>;
336
397
  export type Note = z.infer<typeof NoteSchema>;
337
398
  export type DiagramNodeLocation = z.infer<typeof DiagramNodeLocationSchema>;
@@ -4,6 +4,8 @@ export const FILE_STATUSES = ["added", "modified", "deleted", "renamed"];
4
4
  export const SEVERITIES = ["critical", "major", "minor", "trivial"];
5
5
  export const FINDING_KINDS = ["issue", "requirement_gap"];
6
6
  export const FINDING_STATUSES = ["open", "ignored", "outdated"];
7
+ export const EXPLANATION_SIDES = ["new", "old"];
8
+ export const EXPLANATION_STATUSES = ["current", "outdated"];
7
9
  export const DIAGRAM_KINDS = ["flow", "layers", "mindmap"];
8
10
  export const NODE_SHAPES = ["box", "pill", "decision"];
9
11
  export const NODE_STATUSES = ["new", "modified", "impacted", "existing", "finding", "missing"];
@@ -55,6 +57,29 @@ export const FindingSchema = z.object({
55
57
  status: z.enum(FINDING_STATUSES),
56
58
  createdAt: IsoDateSchema,
57
59
  });
60
+ /**
61
+ * Plage de lignes décrite par une explication, bornes incluses : côté `new` (numéros
62
+ * du côté « nouveau », code ajouté ou conservé) ou côté `old` (numéros du côté
63
+ * « ancien », code supprimé).
64
+ */
65
+ export const ExplanationLocationSchema = z.object({
66
+ path: z.string().min(1),
67
+ side: z.enum(EXPLANATION_SIDES),
68
+ startLine: LineSchema,
69
+ endLine: LineSchema,
70
+ });
71
+ /** Description, par l'agent, de ce que fait un bloc de code et de la façon dont il le fait. */
72
+ export const ExplanationSchema = z.object({
73
+ id: ItemIdSchema,
74
+ title: z.string().min(1),
75
+ body: z.string().min(1),
76
+ location: ExplanationLocationSchema,
77
+ /** Texte exact des lignes décrites au moment où l'explication a été ancrée. */
78
+ anchorText: z.string(),
79
+ /** `outdated` quand les lignes décrites ont changé depuis, au recalcul du snapshot. */
80
+ status: z.enum(EXPLANATION_STATUSES),
81
+ createdAt: IsoDateSchema,
82
+ });
58
83
  /** Emplacement d'une remarque : une ligne du côté « nouveau », ou le fichier entier (`line` null). */
59
84
  export const NoteLocationSchema = z.object({
60
85
  path: z.string().min(1),
@@ -140,6 +165,7 @@ export const AnalysisSchema = z.object({
140
165
  summary: z.array(z.string().min(1)),
141
166
  files: z.array(FileEntrySchema),
142
167
  findings: z.array(FindingSchema),
168
+ explanations: z.array(ExplanationSchema),
143
169
  notes: z.array(NoteSchema),
144
170
  diagrams: z.array(DiagramSchema),
145
171
  review: ReviewSchema,
@@ -19,6 +19,10 @@ export declare const AddNoteBodySchema: z.ZodObject<{
19
19
  text: z.ZodString;
20
20
  baseRevision: z.ZodNumber;
21
21
  }, z.core.$strict>;
22
+ export declare const UpdateNoteBodySchema: z.ZodObject<{
23
+ text: z.ZodString;
24
+ baseRevision: z.ZodNumber;
25
+ }, z.core.$strict>;
22
26
  export declare const DeleteNoteBodySchema: z.ZodObject<{
23
27
  baseRevision: z.ZodNumber;
24
28
  }, z.core.$strict>;
@@ -35,6 +39,7 @@ export declare const SubmitReviewBodySchema: z.ZodObject<{
35
39
  export type SetFileReviewedBody = z.infer<typeof SetFileReviewedBodySchema>;
36
40
  export type SetFindingStatusBody = z.infer<typeof SetFindingStatusBodySchema>;
37
41
  export type AddNoteBody = z.infer<typeof AddNoteBodySchema>;
42
+ export type UpdateNoteBody = z.infer<typeof UpdateNoteBodySchema>;
38
43
  export type DeleteNoteBody = z.infer<typeof DeleteNoteBodySchema>;
39
44
  export type SubmitReviewBody = z.infer<typeof SubmitReviewBodySchema>;
40
45
  /** Données IPC des requêtes de la GUI qui visent une analyse. */
@@ -70,6 +75,14 @@ export declare const AddNoteRequestSchema: z.ZodObject<{
70
75
  baseRevision: z.ZodNumber;
71
76
  }, z.core.$strict>;
72
77
  }, z.core.$strict>;
78
+ export declare const UpdateNoteRequestSchema: z.ZodObject<{
79
+ analysisId: z.ZodString;
80
+ noteId: z.ZodString;
81
+ body: z.ZodObject<{
82
+ text: z.ZodString;
83
+ baseRevision: z.ZodNumber;
84
+ }, z.core.$strict>;
85
+ }, z.core.$strict>;
73
86
  export declare const DeleteNoteRequestSchema: z.ZodObject<{
74
87
  analysisId: z.ZodString;
75
88
  noteId: z.ZodString;
@@ -22,6 +22,10 @@ export const AddNoteBodySchema = z.strictObject({
22
22
  text: z.string().trim().min(1, "the note text is empty"),
23
23
  baseRevision: BaseRevisionSchema,
24
24
  });
25
+ export const UpdateNoteBodySchema = z.strictObject({
26
+ text: z.string().trim().min(1, "the note text is empty"),
27
+ baseRevision: BaseRevisionSchema,
28
+ });
25
29
  export const DeleteNoteBodySchema = z.strictObject({
26
30
  baseRevision: BaseRevisionSchema,
27
31
  });
@@ -39,6 +43,10 @@ export const SetFindingStatusRequestSchema = AnalysisRequestSchema.extend({
39
43
  body: SetFindingStatusBodySchema,
40
44
  });
41
45
  export const AddNoteRequestSchema = AnalysisRequestSchema.extend({ body: AddNoteBodySchema });
46
+ export const UpdateNoteRequestSchema = AnalysisRequestSchema.extend({
47
+ noteId: z.string().min(1),
48
+ body: UpdateNoteBodySchema,
49
+ });
42
50
  export const DeleteNoteRequestSchema = AnalysisRequestSchema.extend({
43
51
  noteId: z.string().min(1),
44
52
  body: DeleteNoteBodySchema,
@@ -25,5 +25,5 @@ export type IPCMessage = z.infer<typeof IPCMessageSchema>;
25
25
  export declare const PUSHED_EVENT_TYPES: readonly ["INITIAL_STATE", "ANALYSES_UPDATED", "ANALYSIS_UPDATED"];
26
26
  export type PushedEventType = (typeof PUSHED_EVENT_TYPES)[number];
27
27
  /** Requêtes du GUI worker au maître ; chacune reçoit une réponse de même `correlationId`. */
28
- export declare const GUI_REQUEST_TYPES: readonly ["GET_CONFIG", "LIST_ANALYSES", "GET_ANALYSIS", "GET_DIFF", "SET_FILE_REVIEWED", "SET_FINDING_STATUS", "ADD_NOTE", "DELETE_NOTE", "SUBMIT_REVIEW"];
28
+ export declare const GUI_REQUEST_TYPES: readonly ["GET_CONFIG", "LIST_ANALYSES", "GET_ANALYSIS", "GET_DIFF", "SET_FILE_REVIEWED", "SET_FINDING_STATUS", "ADD_NOTE", "UPDATE_NOTE", "DELETE_NOTE", "SUBMIT_REVIEW"];
29
29
  export type GuiRequestType = (typeof GUI_REQUEST_TYPES)[number];
@@ -25,6 +25,7 @@ export const GUI_REQUEST_TYPES = [
25
25
  "SET_FILE_REVIEWED",
26
26
  "SET_FINDING_STATUS",
27
27
  "ADD_NOTE",
28
+ "UPDATE_NOTE",
28
29
  "DELETE_NOTE",
29
30
  "SUBMIT_REVIEW",
30
31
  ];
@@ -269,6 +269,7 @@ export class AnalysisStore {
269
269
  summary: input.summary ?? [],
270
270
  files: snapshot.files.map((file) => ({ ...file, reviewed: false })),
271
271
  findings: [],
272
+ explanations: [],
272
273
  notes: [],
273
274
  diagrams: [],
274
275
  review: pendingReview(),
@@ -7,6 +7,7 @@ export declare function analysisId(title: string): string;
7
7
  /** Préfixe des identifiants générés, par type d'élément. */
8
8
  export declare const ITEM_ID_PREFIXES: {
9
9
  readonly finding: "f";
10
+ readonly explanation: "x";
10
11
  readonly note: "n";
11
12
  readonly diagram: "d";
12
13
  readonly node: "nd";
@@ -27,6 +27,7 @@ export function analysisId(title) {
27
27
  /** Préfixe des identifiants générés, par type d'élément. */
28
28
  export const ITEM_ID_PREFIXES = {
29
29
  finding: "f",
30
+ explanation: "x",
30
31
  note: "n",
31
32
  diagram: "d",
32
33
  node: "nd",
@@ -3,12 +3,14 @@ import type { Analysis } from "../../shared/schemas/analysis.schema.js";
3
3
  * Liste les violations des règles de cohérence d'une analyse :
4
4
  * - refs cohérentes avec le mode : `branch` a une ref et un commit de tête ; `working_tree`
5
5
  * compare à HEAD, sans ref ni commit de tête ;
6
- * - identifiants uniques (fichiers, constats, remarques, schémas, nœuds d'un schéma) ;
6
+ * - identifiants uniques (fichiers, constats, explications, remarques, schémas, nœuds d'un schéma) ;
7
7
  * - un constat `issue` a un emplacement, un `requirement_gap` peut ne pas en avoir ;
8
8
  * - un emplacement de constat porte un `anchorText` ; sans emplacement, ni `anchorText` ni `suggestion` ;
9
9
  * - un constat `open` vise un fichier de l'analyse dont le contenu est disponible, avec
10
10
  * 1 ≤ startLine ≤ endLine ≤ nombre de lignes ; un constat `ignored` ou `outdated` garde
11
11
  * l'emplacement qu'il avait, même si le snapshot a changé depuis ;
12
+ * - une explication `current` vise un fichier de l'analyse ; côté `new`, son contenu est
13
+ * disponible et endLine ≤ nombre de lignes ;
12
14
  * - les liens d'un schéma relient des nœuds existants ; en `layers`, chaque nœud a une couche
13
15
  * déclarée dans `layers`, sans doublon ;
14
16
  * - la revue ne sélectionne que des constats et remarques existants, et son état est cohérent.
@@ -14,12 +14,14 @@ function duplicates(ids) {
14
14
  * Liste les violations des règles de cohérence d'une analyse :
15
15
  * - refs cohérentes avec le mode : `branch` a une ref et un commit de tête ; `working_tree`
16
16
  * compare à HEAD, sans ref ni commit de tête ;
17
- * - identifiants uniques (fichiers, constats, remarques, schémas, nœuds d'un schéma) ;
17
+ * - identifiants uniques (fichiers, constats, explications, remarques, schémas, nœuds d'un schéma) ;
18
18
  * - un constat `issue` a un emplacement, un `requirement_gap` peut ne pas en avoir ;
19
19
  * - un emplacement de constat porte un `anchorText` ; sans emplacement, ni `anchorText` ni `suggestion` ;
20
20
  * - un constat `open` vise un fichier de l'analyse dont le contenu est disponible, avec
21
21
  * 1 ≤ startLine ≤ endLine ≤ nombre de lignes ; un constat `ignored` ou `outdated` garde
22
22
  * l'emplacement qu'il avait, même si le snapshot a changé depuis ;
23
+ * - une explication `current` vise un fichier de l'analyse ; côté `new`, son contenu est
24
+ * disponible et endLine ≤ nombre de lignes ;
23
25
  * - les liens d'un schéma relient des nœuds existants ; en `layers`, chaque nœud a une couche
24
26
  * déclarée dans `layers`, sans doublon ;
25
27
  * - la revue ne sélectionne que des constats et remarques existants, et son état est cohérent.
@@ -36,6 +38,7 @@ export function findInvariantViolations(analysis) {
36
38
  for (const [label, ids] of [
37
39
  ["file path", analysis.files.map((file) => file.path)],
38
40
  ["finding id", analysis.findings.map((finding) => finding.id)],
41
+ ["explanation id", analysis.explanations.map((explanation) => explanation.id)],
39
42
  ["note id", analysis.notes.map((note) => note.id)],
40
43
  ["diagram id", analysis.diagrams.map((diagram) => diagram.id)],
41
44
  ]) {
@@ -75,6 +78,29 @@ export function findInvariantViolations(analysis) {
75
78
  `which has ${file.lineCount} line(s)`);
76
79
  }
77
80
  }
81
+ for (const explanation of analysis.explanations) {
82
+ const where = `explanation "${explanation.id}"`;
83
+ const location = explanation.location;
84
+ if (location.startLine > location.endLine) {
85
+ problems.push(`${where} has startLine ${location.startLine} after endLine ${location.endLine}`);
86
+ }
87
+ if (explanation.status !== "current")
88
+ continue;
89
+ const file = files.get(location.path);
90
+ if (!file) {
91
+ problems.push(`${where} describes "${location.path}", which is not a changed file of this analysis`);
92
+ continue;
93
+ }
94
+ if (location.side !== "new")
95
+ continue;
96
+ if (!file.contentAvailable) {
97
+ problems.push(`${where} describes new-side lines of "${location.path}", whose content is not available`);
98
+ }
99
+ else if (location.endLine > file.lineCount) {
100
+ problems.push(`${where} describes lines ${location.startLine}-${location.endLine} of "${location.path}", ` +
101
+ `which has ${file.lineCount} line(s)`);
102
+ }
103
+ }
78
104
  for (const diagram of analysis.diagrams) {
79
105
  const where = `diagram "${diagram.id}"`;
80
106
  const nodeIds = new Set(diagram.nodes.map((node) => node.id));
@@ -1,5 +1,5 @@
1
- import type { FileEntry } from "../../shared/schemas/analysis.schema.js";
2
- import type { DiffSnapshot } from "../../shared/schemas/diff.schema.js";
1
+ import type { ExplanationLocation, ExplanationSide, FileEntry } from "../../shared/schemas/analysis.schema.js";
2
+ import type { DiffSnapshot, FileDiff } from "../../shared/schemas/diff.schema.js";
3
3
  export interface LocationRequest {
4
4
  path: string;
5
5
  startLine: number;
@@ -17,9 +17,27 @@ export interface ResolvedLocation {
17
17
  * Vérifie un emplacement (lignes du côté « nouveau ») contre le snapshot et
18
18
  * renvoie le texte des lignes visées. Erreur explicite si le fichier n'est pas
19
19
  * dans l'analyse, si son contenu n'est pas disponible ou si les lignes sortent
20
- * du fichier.
20
+ * du fichier ; `unavailableHint` complète le message d'un contenu indisponible.
21
21
  */
22
- export declare function resolveLocation(snapshot: DiffSnapshot, files: FileEntry[], request: LocationRequest): ResolvedLocation;
22
+ export declare function resolveLocation(snapshot: DiffSnapshot, files: FileEntry[], request: LocationRequest, unavailableHint?: string): ResolvedLocation;
23
+ /**
24
+ * Texte actuel des lignes décrites par une explication, ou null quand elles ne
25
+ * sont plus lisibles : côté `new`, dans le contenu conservé du fichier ; côté
26
+ * `old`, dans les lignes du côté « ancien » que montre le diff.
27
+ */
28
+ export declare function explanationText(diff: FileDiff | undefined, location: ExplanationLocation): string | null;
29
+ export interface ExplanationLocationRequest extends LocationRequest {
30
+ side: ExplanationSide;
31
+ }
32
+ /**
33
+ * Vérifie l'emplacement d'une explication et renvoie le texte des lignes décrites.
34
+ * - côté `new` : mêmes règles qu'un constat ;
35
+ * - côté `old` : toutes les lignes doivent figurer dans le diff (numéros de la
36
+ * première colonne de get_diff), et au moins une doit être supprimée.
37
+ */
38
+ export declare function resolveExplanationLocation(snapshot: DiffSnapshot, files: FileEntry[], request: ExplanationLocationRequest): ExplanationLocation & {
39
+ anchorText: string;
40
+ };
23
41
  /**
24
42
  * Vérifie l'emplacement d'une remarque : un fichier de l'analyse, et une ligne
25
43
  * du côté « nouveau » quand `line` est fournie.
@@ -14,13 +14,15 @@ function unavailableReason(file) {
14
14
  return "it is binary";
15
15
  return "it is larger than the snapshot size limit";
16
16
  }
17
+ /** Indication donnée à un constat dont le fichier n'a pas de contenu conservé. */
18
+ const FINDING_UNAVAILABLE_HINT = "Anchor the point on lines of another changed file, or omit the location (allowed for requirement_gap findings).";
17
19
  /**
18
20
  * Vérifie un emplacement (lignes du côté « nouveau ») contre le snapshot et
19
21
  * renvoie le texte des lignes visées. Erreur explicite si le fichier n'est pas
20
22
  * dans l'analyse, si son contenu n'est pas disponible ou si les lignes sortent
21
- * du fichier.
23
+ * du fichier ; `unavailableHint` complète le message d'un contenu indisponible.
22
24
  */
23
- export function resolveLocation(snapshot, files, request) {
25
+ export function resolveLocation(snapshot, files, request, unavailableHint = FINDING_UNAVAILABLE_HINT) {
24
26
  const endLine = request.endLine ?? request.startLine;
25
27
  const { path, startLine } = request;
26
28
  const file = files.find((entry) => entry.path === path);
@@ -32,8 +34,7 @@ export function resolveLocation(snapshot, files, request) {
32
34
  }
33
35
  const diff = snapshot.files.find((entry) => entry.path === path);
34
36
  if (!file.contentAvailable || !diff || diff.newContent === null) {
35
- throw new AnalysisError(`Cannot anchor on lines of "${path}": its content is not available because ${unavailableReason(file)}. ` +
36
- `Anchor the point on lines of another changed file, or omit the location (allowed for requirement_gap findings).`);
37
+ throw new AnalysisError(`Cannot anchor on lines of "${path}": its content is not available because ${unavailableReason(file)}. ${unavailableHint}`);
37
38
  }
38
39
  const lines = splitLines(diff.newContent);
39
40
  if (endLine > lines.length) {
@@ -42,6 +43,89 @@ export function resolveLocation(snapshot, files, request) {
42
43
  }
43
44
  return { path, startLine, endLine, anchorText: lines.slice(startLine - 1, endLine).join("\n") };
44
45
  }
46
+ /** Texte des lignes du côté « ancien » que montre le diff (lignes supprimées et contexte), par numéro. */
47
+ function oldSideLines(diff) {
48
+ const lines = new Map();
49
+ for (const hunk of diff.hunks) {
50
+ for (const line of hunk.lines) {
51
+ if (line.oldNo !== null)
52
+ lines.set(line.oldNo, { text: line.text, removed: line.type === "del" });
53
+ }
54
+ }
55
+ return lines;
56
+ }
57
+ /**
58
+ * Texte actuel des lignes décrites par une explication, ou null quand elles ne
59
+ * sont plus lisibles : côté `new`, dans le contenu conservé du fichier ; côté
60
+ * `old`, dans les lignes du côté « ancien » que montre le diff.
61
+ */
62
+ export function explanationText(diff, location) {
63
+ if (!diff || location.startLine > location.endLine)
64
+ return null;
65
+ if (location.side === "new") {
66
+ if (diff.newContent === null)
67
+ return null;
68
+ const lines = splitLines(diff.newContent);
69
+ if (location.startLine < 1 || location.endLine > lines.length)
70
+ return null;
71
+ return lines.slice(location.startLine - 1, location.endLine).join("\n");
72
+ }
73
+ const old = oldSideLines(diff);
74
+ const texts = [];
75
+ for (let line = location.startLine; line <= location.endLine; line++) {
76
+ const entry = old.get(line);
77
+ if (!entry)
78
+ return null;
79
+ texts.push(entry.text);
80
+ }
81
+ return texts.join("\n");
82
+ }
83
+ /**
84
+ * Vérifie l'emplacement d'une explication et renvoie le texte des lignes décrites.
85
+ * - côté `new` : mêmes règles qu'un constat ;
86
+ * - côté `old` : toutes les lignes doivent figurer dans le diff (numéros de la
87
+ * première colonne de get_diff), et au moins une doit être supprimée.
88
+ */
89
+ export function resolveExplanationLocation(snapshot, files, request) {
90
+ if (request.side === "new") {
91
+ const resolved = resolveLocation(snapshot, files, request, `Explain its removed lines on the old side ("side": "old"), or explain lines of another changed file.`);
92
+ return { path: resolved.path, side: "new", startLine: resolved.startLine, endLine: resolved.endLine, anchorText: resolved.anchorText };
93
+ }
94
+ const endLine = request.endLine ?? request.startLine;
95
+ const { path, startLine } = request;
96
+ if (!files.some((entry) => entry.path === path)) {
97
+ throw new AnalysisError(`File "${path}" is not part of this analysis (${describeFiles(files)}).`);
98
+ }
99
+ if (!Number.isInteger(startLine) || !Number.isInteger(endLine) || startLine < 1 || endLine < startLine) {
100
+ throw new AnalysisError(`Invalid line range ${startLine}-${endLine} for "${path}": lines are 1-based integers and end_line must be >= start_line.`);
101
+ }
102
+ const diff = snapshot.files.find((entry) => entry.path === path);
103
+ const old = diff ? oldSideLines(diff) : new Map();
104
+ const missing = [];
105
+ let removed = false;
106
+ for (let line = startLine; line <= endLine; line++) {
107
+ const entry = old.get(line);
108
+ if (!entry)
109
+ missing.push(line);
110
+ else if (entry.removed)
111
+ removed = true;
112
+ }
113
+ if (missing.length > 0) {
114
+ throw new AnalysisError(`Old-side lines ${startLine}-${endLine} of "${path}" are not all in the diff (missing: ${lineList(missing)}). ` +
115
+ `Old-side numbers are those of the first column of get_diff; an old-side explanation covers removed lines and their context.`);
116
+ }
117
+ if (!removed) {
118
+ throw new AnalysisError(`Old-side lines ${startLine}-${endLine} of "${path}" contain no removed line. ` +
119
+ `The old side is for removed code: explain kept or added code on the new side ("side": "new").`);
120
+ }
121
+ const location = { path, side: "old", startLine, endLine };
122
+ return { ...location, anchorText: explanationText(diff, location) };
123
+ }
124
+ /** `3, 4, 9` ; au-delà de dix numéros, les dix premiers suivis de `…`. */
125
+ function lineList(lines) {
126
+ const shown = lines.slice(0, 10).join(", ");
127
+ return lines.length > 10 ? `${shown}, …` : shown;
128
+ }
45
129
  /**
46
130
  * Vérifie l'emplacement d'une remarque : un fichier de l'analyse, et une ligne
47
131
  * du côté « nouveau » quand `line` est fournie.
@@ -12,6 +12,8 @@ export declare function repoFolderName(repoPath: string): string;
12
12
  export declare const addProject: MigrationStep;
13
13
  /** Ajoute `overview` à null : l'analyse n'a pas encore de vue d'ensemble. */
14
14
  export declare const addOverview: MigrationStep;
15
+ /** Ajoute `explanations` vide : l'analyse n'a pas encore d'explication. */
16
+ export declare const addExplanations: MigrationStep;
15
17
  /** Étapes appliquées aux analyses (`analyses/<id>.json`), dans l'ordre. */
16
18
  export declare const ANALYSIS_MIGRATIONS: readonly MigrationStep[];
17
19
  /** Étapes appliquées aux snapshots de diff (`diffs/<id>.json`), dans l'ordre. */
@@ -24,8 +24,10 @@ export const addProject = (doc) => {
24
24
  };
25
25
  /** Ajoute `overview` à null : l'analyse n'a pas encore de vue d'ensemble. */
26
26
  export const addOverview = (doc) => (doc.overview !== undefined ? null : { ...doc, overview: null });
27
+ /** Ajoute `explanations` vide : l'analyse n'a pas encore d'explication. */
28
+ export const addExplanations = (doc) => (doc.explanations !== undefined ? null : { ...doc, explanations: [] });
27
29
  /** Étapes appliquées aux analyses (`analyses/<id>.json`), dans l'ordre. */
28
- export const ANALYSIS_MIGRATIONS = [addProject, addOverview];
30
+ export const ANALYSIS_MIGRATIONS = [addProject, addOverview, addExplanations];
29
31
  /** Étapes appliquées aux snapshots de diff (`diffs/<id>.json`), dans l'ordre. */
30
32
  export const SNAPSHOT_MIGRATIONS = [];
31
33
  /** Applique `steps` à la suite. Renvoie null si aucune étape n'a modifié le document. */