@imenam/mcp-feature-analyzer 1.0.19 → 1.0.21

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 (111) hide show
  1. package/README.md +22 -9
  2. package/dist/shared/follow-ups.d.ts +36 -0
  3. package/dist/shared/follow-ups.js +33 -0
  4. package/dist/shared/schemas/analysis.schema.d.ts +44 -1
  5. package/dist/shared/schemas/analysis.schema.js +7 -1
  6. package/dist/shared/schemas/api.schema.d.ts +13 -0
  7. package/dist/shared/schemas/api.schema.js +6 -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.d.ts +10 -1
  11. package/dist/src/core/analysis-store.js +37 -1
  12. package/dist/src/core/follow-ups.d.ts +19 -0
  13. package/dist/src/core/follow-ups.js +85 -0
  14. package/dist/src/core/migrate.d.ts +7 -0
  15. package/dist/src/core/migrate.js +41 -1
  16. package/dist/src/core/review-actions.d.ts +16 -3
  17. package/dist/src/core/review-actions.js +25 -0
  18. package/dist/src/gui-worker.js +7 -1
  19. package/dist/src/index.js +23 -5
  20. package/dist/src/tools/create-analysis.d.ts +5 -1
  21. package/dist/src/tools/create-analysis.js +22 -4
  22. package/dist/src/tools/delete-analysis.d.ts +4 -1
  23. package/dist/src/tools/delete-analysis.js +11 -5
  24. package/dist/src/tools/format.d.ts +6 -0
  25. package/dist/src/tools/format.js +16 -1
  26. package/dist/src/tools/get-analysis.js +3 -1
  27. package/dist/src/tools/get-review-feedback.js +12 -5
  28. package/dist/src/tools/list-analyses.js +5 -0
  29. package/dist/src/tools/mark-feedback-processed.d.ts +2 -3
  30. package/dist/src/tools/mark-feedback-processed.js +5 -18
  31. package/gui/dist/assets/{angular-html-BG32ZSEg.js → angular-html-Cek_sXrk.js} +1 -1
  32. package/gui/dist/assets/{angular-ts-W997xU6j.js → angular-ts-UDyn2p3m.js} +1 -1
  33. package/gui/dist/assets/{apl-6ogEEQiK.js → apl-x_xlCIBP.js} +1 -1
  34. package/gui/dist/assets/{astro-DBxTz6gT.js → astro-DmQALJnP.js} +1 -1
  35. package/gui/dist/assets/{blade-PbZvkn0I.js → blade-pIl9LNwT.js} +1 -1
  36. package/gui/dist/assets/{c-C5-3wfrB.js → c-D0w4WEPA.js} +1 -1
  37. package/gui/dist/assets/{chapel-KHYeKJD5.js → chapel-DYhi5M_6.js} +1 -1
  38. package/gui/dist/assets/{cobol-D51AxaJB.js → cobol-Bi9NY2Z2.js} +1 -1
  39. package/gui/dist/assets/{coffee-4h6_4dhl.js → coffee-BW5DOyKl.js} +1 -1
  40. package/gui/dist/assets/{cpp-B63oWnHV.js → cpp-sz7PwxJX.js} +1 -1
  41. package/gui/dist/assets/{crystal-OG_TkIE7.js → crystal-d2FoewBA.js} +1 -1
  42. package/gui/dist/assets/{css-Csea3soR.js → css-BELRmmks.js} +1 -1
  43. package/gui/dist/assets/{edge-HBaaYuuJ.js → edge-mQnFjIkx.js} +1 -1
  44. package/gui/dist/assets/{elixir-DZlxxd9b.js → elixir-Dy83Pfni.js} +1 -1
  45. package/gui/dist/assets/{elm-DZAjSs9M.js → elm-CbWJN26n.js} +1 -1
  46. package/gui/dist/assets/{erb-BurFBvpV.js → erb-YoUGVS38.js} +1 -1
  47. package/gui/dist/assets/{git-rebase-B7wPwCu-.js → git-rebase-B1jMLAq6.js} +1 -1
  48. package/gui/dist/assets/{glimmer-js-Bgk8Z51T.js → glimmer-js-CEmA0NTT.js} +1 -1
  49. package/gui/dist/assets/{glimmer-ts-ldZtmANh.js → glimmer-ts-kGsWetmY.js} +1 -1
  50. package/gui/dist/assets/{glsl-7SImpJuS.js → glsl-BeGzr9bJ.js} +1 -1
  51. package/gui/dist/assets/{graphql-CJ0BLjCq.js → graphql-C6NIC2e-.js} +1 -1
  52. package/gui/dist/assets/{hack-D50FsJQB.js → hack-KEwBG4mT.js} +1 -1
  53. package/gui/dist/assets/{haml-xkw_a2ii.js → haml-CkZmvo0g.js} +1 -1
  54. package/gui/dist/assets/{handlebars-C0upK5dG.js → handlebars-Auwsmq4s.js} +1 -1
  55. package/gui/dist/assets/{html-derivative-BfB3QMj5.js → html-derivative-vuqQ6-H_.js} +1 -1
  56. package/gui/dist/assets/{html-CerDkvKq.js → html-oCOc0XdA.js} +1 -1
  57. package/gui/dist/assets/{http-pXaTY6-R.js → http--nPEUAue.js} +1 -1
  58. package/gui/dist/assets/{hurl-BNtMT88T.js → hurl-CtpqLncF.js} +1 -1
  59. package/gui/dist/assets/index-A6BLowRB.css +1 -0
  60. package/gui/dist/assets/index-B8qqkmWn.js +87 -0
  61. package/gui/dist/assets/{java-B3e3IyVz.js → java-DYzsoje1.js} +1 -1
  62. package/gui/dist/assets/{javascript-DgggYE1f.js → javascript-C9DLMJzT.js} +1 -1
  63. package/gui/dist/assets/{jinja-BHtm4gvT.js → jinja-bSnL04FA.js} +1 -1
  64. package/gui/dist/assets/{jison-B2YK1Y49.js → jison-Cn8E8V5z.js} +1 -1
  65. package/gui/dist/assets/{json-7jbWqmUr.js → json-d-QlpP_z.js} +1 -1
  66. package/gui/dist/assets/{jsx-BFPQ2oqh.js → jsx-CIpU_P-t.js} +1 -1
  67. package/gui/dist/assets/{julia-DX1Bsm0x.js → julia-DkcJm8gB.js} +1 -1
  68. package/gui/dist/assets/{just-D524YAex.js → just-DIrw6CS3.js} +1 -1
  69. package/gui/dist/assets/{latex-Dc0dzAGl.js → latex-BhxpsHBQ.js} +1 -1
  70. package/gui/dist/assets/{liquid-DXohsHev.js → liquid-C2rHX9Ca.js} +1 -1
  71. package/gui/dist/assets/{lua-G_rux6Ay.js → lua-7VPHhWYZ.js} +1 -1
  72. package/gui/dist/assets/{marko-BdidjG96.js → marko-CyYT4p0Z.js} +1 -1
  73. package/gui/dist/assets/{mdc-DxAZb3tM.js → mdc-BabtknzF.js} +1 -1
  74. package/gui/dist/assets/{nginx-CuvNT-q8.js → nginx-CFQwDuks.js} +1 -1
  75. package/gui/dist/assets/{nim-DuDgLkQa.js → nim-DpxcgIHl.js} +1 -1
  76. package/gui/dist/assets/{org-BJKUhgoL.js → org-Di1bzXp6.js} +1 -1
  77. package/gui/dist/assets/{perl-BdJ-pZH6.js → perl-CiesRDJJ.js} +1 -1
  78. package/gui/dist/assets/{php-DUOUy2DX.js → php-XqsiYIMC.js} +1 -1
  79. package/gui/dist/assets/{pug-WUnoGP-C.js → pug-aREWfnPz.js} +1 -1
  80. package/gui/dist/assets/{qml-Dl8tY0zd.js → qml-DW8nc6nh.js} +1 -1
  81. package/gui/dist/assets/{r-Ci9Hbbcn.js → r-D_yTXRZ0.js} +1 -1
  82. package/gui/dist/assets/{razor-0bx4DTys.js → razor-CyRfgle5.js} +1 -1
  83. package/gui/dist/assets/{regexp-C0ZlJfpq.js → regexp-Iw19WR-v.js} +1 -1
  84. package/gui/dist/assets/{rst-BC_vqA0U.js → rst-B_-k2S-N.js} +1 -1
  85. package/gui/dist/assets/{ruby-DPPk42YS.js → ruby-CuA36vF6.js} +1 -1
  86. package/gui/dist/assets/{sas-B8jfw3hR.js → sas-D1kb68o2.js} +1 -1
  87. package/gui/dist/assets/{scss-7mTWL496.js → scss-CqVwaT6R.js} +1 -1
  88. package/gui/dist/assets/{shellscript-CYNcJqc-.js → shellscript-ZH1qXABi.js} +1 -1
  89. package/gui/dist/assets/{shellsession-Dh2E3t_B.js → shellsession-C6Na6TIE.js} +1 -1
  90. package/gui/dist/assets/{soy-BVR-TmA2.js → soy-ByQ4bEBr.js} +1 -1
  91. package/gui/dist/assets/{sql-D3S51lwn.js → sql-CZ6S4qI7.js} +1 -1
  92. package/gui/dist/assets/{stata-BjuRTlAG.js → stata-BRy0gI05.js} +1 -1
  93. package/gui/dist/assets/{surrealql-BKRHdvyc.js → surrealql-lzF2k3Nd.js} +1 -1
  94. package/gui/dist/assets/{svelte-DIKo_e2T.js → svelte-kK73PgnC.js} +1 -1
  95. package/gui/dist/assets/{templ-DC42iqks.js → templ-6OIvTPYb.js} +1 -1
  96. package/gui/dist/assets/{tex-BZ2lfoiV.js → tex-CInwFac_.js} +1 -1
  97. package/gui/dist/assets/{ts-tags-o8CUeNFk.js → ts-tags-FsaaGgNy.js} +1 -1
  98. package/gui/dist/assets/{tsx-DeMh4Cat.js → tsx-PUBs9-s-.js} +1 -1
  99. package/gui/dist/assets/{twig-BUr_lvSK.js → twig-Bod5HEgw.js} +1 -1
  100. package/gui/dist/assets/{typescript-DeKHrHpS.js → typescript-DG8BYJBt.js} +1 -1
  101. package/gui/dist/assets/{typst-CcbUABTI.js → typst-D2jY-D59.js} +1 -1
  102. package/gui/dist/assets/{vue-html-soMMIiuc.js → vue-html-zYeGUz6E.js} +1 -1
  103. package/gui/dist/assets/{vue-JHDlnmNz.js → vue-r2mYghHk.js} +1 -1
  104. package/gui/dist/assets/{vue-vine-D8TZS6Lm.js → vue-vine-DWGog3Vg.js} +1 -1
  105. package/gui/dist/assets/{xml-9isD2blC.js → xml-CfWom5su.js} +1 -1
  106. package/gui/dist/assets/{xsl-chWKvWOz.js → xsl-BV2P-krQ.js} +1 -1
  107. package/gui/dist/assets/{yaml-DEY2M1js.js → yaml-C-iwSc7Q.js} +1 -1
  108. package/gui/dist/index.html +2 -2
  109. package/package.json +1 -1
  110. package/gui/dist/assets/index-CMjVOwnL.js +0 -87
  111. package/gui/dist/assets/index-CN9ojYe6.css +0 -1
package/README.md CHANGED
@@ -137,11 +137,11 @@ Quatorze outils. Les noms, descriptions et textes de résultat sont en anglais ;
137
137
  | Outil | Effet |
138
138
  |---|---|
139
139
  | `list_analyses` | Point d'entrée : les analyses regroupées par projet, chacune avec ses refs, ses constats ouverts par gravité, ses fichiers revus, son nombre de schémas et l'état de sa revue ; une revue soumise est « pending processing » tant que l'agent ne l'a pas marquée traitée, « processed at … » ensuite, avec la note du traitement ; une revue approuvée est « nothing to process ». `project` limite la liste à un projet (un nom inconnu liste les projets existants) ; `review_state` à un état de revue (`not_started`, `in_progress`, `files_reviewed`, `submitted`), aux revues soumises restant à traiter (`to_process`) ou qui n'attendent plus l'agent (`processed` : marquées traitées, ou approuvées). Invite l'agent à reprendre l'analyse existante d'une feature plutôt qu'à en créer une autre. Signale aussi les fichiers d'analyse illisibles. |
140
- | `create_analysis` | Calcule et fige le diff : `repo_path` (racine absolue du dépôt), `project` (projet ou feature, dossier de rangement, requis), `title`, `mode` (`branch` ou `working_tree`), `base` (requis en `branch`), `head` (en `branch`, défaut `HEAD`), `request_text` et `request_source` (la demande initiale : ticket, prompt). Renvoie l'identifiant et la liste des fichiers. |
141
- | `get_analysis` | Vue complète : projet, demande, vue d'ensemble, résumé, fichiers (revus ou non), constats avec statut et emplacement, remarques du relecteur, schémas (signalés quand leur tracé a des croisements), état de la revue. |
140
+ | `create_analysis` | Calcule et fige le diff : `repo_path` (racine absolue du dépôt), `project` (projet ou feature, dossier de rangement, requis), `title`, `mode` (`branch` ou `working_tree`), `base` (requis en `branch`), `head` (en `branch`, défaut `HEAD`), `request_text` et `request_source` (la demande initiale : ticket, prompt), `follow_up_of` (voir [Revues de suivi](#revues-de-suivi)). Renvoie l'identifiant et la liste des fichiers. |
141
+ | `get_analysis` | Vue complète : projet, demande, vue d'ensemble, résumé, fichiers (revus ou non), constats avec statut et emplacement, remarques du relecteur, schémas (signalés quand leur tracé a des croisements), état de la revue, et ses liens de suivi (analyse suivie avec le rang du tour, revue de suivi avec son état). |
142
142
  | `update_analysis` | Change `title`, `project`, la vue d'ensemble (`objective`, `approach`, `attention_points`), `summary` (les changements fonctionnels de la feature, une puce par changement, remplace toute la liste) ou la demande initiale (`request_text`, `request_source` ; `null` efface). Les champs absents sont conservés. |
143
143
  | `refresh_analysis` | Recalcule le diff avec le même dépôt, le même mode et les mêmes refs, après correction. Voir [Les deux modes de diff](#les-deux-modes-de-diff). |
144
- | `delete_analysis` | Supprime l'analyse et son diff figé. Le dépôt n'est pas touché. Supprime aussi une analyse listée comme illisible. |
144
+ | `delete_analysis` | Supprime l'analyse et son diff figé. Le dépôt n'est pas touché. Supprime aussi une analyse listée comme illisible. Sa revue de suivi, si elle en a une, est conservée et devient une première revue. |
145
145
 
146
146
  La **vue d'ensemble** est l'analyse globale de l'agent : l'objectif fonctionnel de la feature, l'approche retenue (architecture, flux principal) et les points d'attention à vérifier en priorité. Sans vue d'ensemble, `objective` et `approach` sont requis ensemble pour la créer (`attention_points` vaut alors une liste vide s'il est omis) ; ensuite, chaque champ donné remplace le précédent. `objective: null` retire toute la vue d'ensemble. L'objectif figure aussi en tête du prompt de retour.
147
147
 
@@ -197,7 +197,7 @@ Chaque nœud a un `id`, un `label`, une forme `shape` (`box` par défaut, `pill`
197
197
  | Outil | Effet |
198
198
  |---|---|
199
199
  | `get_review_feedback` | Une fois la revue soumise : décision (`approve`, `request_changes`, `reject`), état du traitement (à traiter, ou traité avec sa date et sa note), constats et remarques retenus avec leur détail, et le prompt généré, recopié tel quel. Après une nouvelle soumission, il liste aussi ce qui a changé depuis la version soumise précédente (décision, constats et remarques ajoutés, modifiés ou retirés) et rappelle comment celle-ci avait été traitée. Avant la soumission, ou pendant que le relecteur modifie une revue soumise : l'avancement seul (fichiers revus, constats ignorés, remarques). Lecture seule. |
200
- | `mark_feedback_processed` | Marque traité le retour soumis, avec la date et une `note` facultative (« corrigé par 12785d6e3 et 1ff5baaad », « réponses données en conversation »). Sert quand `refresh_analysis` ne convient pas : analyse d'un commit précis corrigée dans d'autres commits, remarques auxquelles l'agent a répondu dans la conversation. Le retour reste lisible avec `get_review_feedback`. Refusé tant que la revue n'est pas soumise ; un nouveau marquage remplace le précédent. |
200
+ | `mark_feedback_processed` | Marque traité le retour soumis, avec la date et une `note` facultative (« corrigé par 12785d6e3 et 1ff5baaad », « réponses données en conversation »). Sert quand `refresh_analysis` ne convient pas : analyse d'un commit précis corrigée dans d'autres commits, remarques auxquelles l'agent a répondu dans la conversation. Le retour reste lisible avec `get_review_feedback`. Refusé tant que la revue n'est pas soumise ; un nouveau marquage remplace le précédent. Le relecteur peut aussi marquer un retour traité depuis l'accueil de la GUI ; le traitement enregistre son auteur, et `get_review_feedback` l'indique (« by the agent » ou « by the reviewer »). |
201
201
 
202
202
  Le retour se lit **avant** `refresh_analysis`, qui le remplace par une revue vierge : le recalcul vaut traitement, puisqu'il relance la revue.
203
203
 
@@ -230,10 +230,22 @@ list_analyses { review_state: "to_process" } ← revues soumises restant
230
230
  get_review_feedback { analysis_id } ← décision, points retenus, prompt
231
231
  … l'agent corrige le code …
232
232
  refresh_analysis { analysis_id } ← nouveau diff, nouveau tour de revue
233
- ou, quand le diff reste tel quel (commit précis, réponses en conversation) :
234
- mark_feedback_processed { analysis_id, note: "corrigé par 12785d6e3" }
233
+ ou, quand les corrections sont dans d'autres commits (analyse d'un commit précis) :
234
+ create_analysis { …, base: "<sha>^", head: "<sha>", follow_up_of: analysis_id } ← revue de suivi
235
+ ou, quand rien n'est à relire (réponses en conversation) :
236
+ mark_feedback_processed { analysis_id, note: "réponses données en conversation" }
235
237
  ```
236
238
 
239
+ ### Revues de suivi
240
+
241
+ Une **revue de suivi** est une nouvelle analyse, créée avec `create_analysis` et `follow_up_of`, qui relit les corrections apportées au retour soumis d'une analyse précédente : typiquement les commits de correction d'une analyse qui porte sur un commit précis.
242
+
243
+ - Seule une analyse dont la revue est soumise peut être suivie, et elle a **au plus une** revue de suivi : les tours forment une chaîne (tour 1 → tour 2 → …), et le tour suivant suit le dernier.
244
+ - À la création du suivi, le retour de l'analyse suivie est marqué traité par l'agent s'il l'attendait encore, avec la note « Suivie par la revue « … » ». Une revue approuvée ou déjà traitée garde son marquage.
245
+ - `get_analysis`, `list_analyses` et `get_review_feedback` indiquent les liens dans les deux sens ; `get_review_feedback` propose de créer la revue de suivi, et `mark_feedback_processed` le rappelle.
246
+ - Dans la GUI, une analyse suivie quitte les filtres d'étape de l'accueil et ne reste visible que dans « Toutes ».
247
+ - Il n'y a pas d'outil pour lier deux analyses après coup.
248
+
237
249
  ---
238
250
 
239
251
  ## L'interface
@@ -242,11 +254,11 @@ Thème sombre. La barre supérieure porte la marque (un clic ramène à l'accuei
242
254
 
243
255
  ### Accueil (`#/`)
244
256
 
245
- Les analyses sont rangées par projet, dans des sections repliables. L'en-tête d'un projet compte ses analyses par état de revue. Chaque analyse est une carte séparée des autres. Son titre s'affiche en entier sur toute la largeur et passe à la ligne s'il le faut. Dessous, sur une seconde ligne : ses refs, la date de sa dernière modification, la progression des fichiers revus, ses constats ouverts par gravité (exigences manquantes comprises, ce que dit l'infobulle) et son état de revue : « Non commencée », « En cours », « Fichiers revus » ou « Soumise », suivi de la décision, puis « Traitée » une fois que l'agent a marqué le retour traité (date et note en infobulle) ; la pastille disparaît dès que le relecteur modifie la revue, qui repasse en cours. Quand l'agent a marqué le retour traité, une ligne sous les références donne sa note et la date du traitement. Une colonne à gauche filtre la liste, chaque filtre avec son icône et son nombre d'analyses : « À relire » (par défaut, revues pas encore soumises), « Traitées par l'agent » (retour traité, corrections à vérifier ; ce filtre ressort en sarcelle tant qu'il n'est pas vide), « À traiter par l'agent » (retour soumis que l'agent n'a pas encore traité), « Approuvées », et « Toutes ». Les quatre premiers se partagent les analyses sans se recouvrir. Sous 900 px de large, les filtres passent au-dessus de la liste. L'accueil n'ouvre aucune analyse d'office ; sans analyse, il explique que l'agent les crée avec `create_analysis`.
257
+ Les analyses sont rangées par projet, dans des sections repliables. L'en-tête d'un projet compte ses analyses par état de revue. Chaque analyse est une carte séparée des autres. Son titre s'affiche en entier sur toute la largeur et passe à la ligne s'il le faut. Dessous, sur une seconde ligne : ses refs, la date de sa dernière modification, la progression des fichiers revus, ses constats ouverts par gravité (exigences manquantes comprises, ce que dit l'infobulle) et son état de revue : « Non commencée », « En cours », « Fichiers revus » ou « Soumise », suivi de la décision, puis « Traitée » une fois que l'agent a marqué le retour traité (date et note en infobulle) ; la pastille disparaît dès que le relecteur modifie la revue, qui repasse en cours. Quand le retour est marqué traité, une ligne sous les références donne la note, l'auteur du marquage (l'agent, ou le relecteur) et la date. Dans le filtre « À traiter par l'agent », chaque carte porte à droite du titre un bouton « Marquer comme traité » : il ouvre une petite fenêtre avec une note facultative (Entrée confirme, Échap referme), et la revue passe dans « Traitées par l'agent », comme avec `mark_feedback_processed`. Le reste de la carte mène à l'analyse. Une analyse suivie porte la ligne « Nouvelle revue disponible : » avec un bouton au titre de sa revue de suivi et son état ; une revue de suivi porte « Revue faisant suite à : » avec un bouton au titre de l'analyse suivie et son rang (« tour 2 »). Une analyse suivie n'apparaît que dans « Toutes ». Une colonne à gauche filtre la liste, chaque filtre avec son icône et son nombre d'analyses : « À relire » (par défaut, revues pas encore soumises), « À traiter par l'agent » (retour soumis que l'agent n'a pas encore traité), « Traitées par l'agent » (retour traité, corrections à vérifier), « Approuvées », et « Toutes ». Les quatre premiers ne se recouvrent pas et laissent de côté les analyses qui ont une revue de suivi. Sous 900 px de large, les filtres passent au-dessus de la liste. L'accueil n'ouvre aucune analyse d'office ; sans analyse, il explique que l'agent les crée avec `create_analysis`.
246
258
 
247
259
  ### Écran Feature (`#/<id>`)
248
260
 
249
- Titre, mode et refs (un sha complet est abrégé à 7 caractères), nombre de fichiers, lignes ajoutées et supprimées, bouton « Commencer la revue » qui ouvre le premier fichier non revu. Quatre onglets, avec leur compteur : fichiers modifiés, constats **ouverts** (exigences manquantes comprises ; l'infobulle donne aussi le total), schémas :
261
+ Titre, mode et refs (un sha complet est abrégé à 7 caractères), nombre de fichiers, lignes ajoutées et supprimées, bouton « Commencer la revue » qui ouvre le premier fichier non revu. Quand l'analyse fait partie d'une chaîne de revues, un bandeau suit l'en-tête : sur une analyse suivie, « Nouvelle revue disponible : » et un bouton au titre de sa revue de suivi, avec son état ; sur une revue de suivi, « Revue faisant suite à : » et un bouton au titre de l'analyse suivie, le rappel de son retour (date, décision, traitement) et les « Points du retour précédent à vérifier » (constats et remarques qu'il retenait, en lecture seule). La chaîne des tours (« Tour 1 → Tour 2 »), un lien par tour, accompagne la première ligne. Quatre onglets, avec leur compteur : fichiers modifiés, constats **ouverts** (exigences manquantes comprises ; l'infobulle donne aussi le total), schémas :
250
262
 
251
263
  - **Résumé** — la carte « Vue d'ensemble » (objectif, approche, points d'attention) quand l'agent l'a rédigée, « Ce que l'agent a fait », la demande initiale et sa source, la section « Constats ouverts » (compteurs par gravité, constats ouverts du plus grave au moins grave, lien « Voir les N constats, tous statuts confondus ») et « Copier le prompt pour l'agent » (tous les constats ouverts).
252
264
  - **Fichiers** — chaque fichier avec son statut, ses `+`/`−`, ses constats ouverts par gravité et son état de revue.
@@ -259,7 +271,8 @@ L'ajustement à la fenêtre ne descend jamais sous l'échelle **0,85** : en dess
259
271
 
260
272
  ### Écran Revue (`#/<id>/review/<chemin>`)
261
273
 
262
- - **Panneau des fichiers** — progression, fichiers groupés par dossier, état revu, pastilles des constats ouverts par gravité et, en bout de ligne, la lettre du statut git à la manière de VS Code : `M` modifié, `A` ajouté, `D` supprimé, `R` renommé, `U` non suivi (libellé complet en infobulle). 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 »). Sa largeur se règle en glissant son bord droit (entre 200 et 600 px ; flèches gauche et droite quand la poignée a le focus, double-clic pour revenir à 280 px) et se conserve dans le navigateur d'un rechargement à l'autre. Le bouton en tête de l'en-tête du fichier masque ou réaffiche le panneau, pour toute la session : le diff s'élargit vers la gauche, la colonne de droite garde sa largeur. Il reste affiché quand le chemin demandé ne fait pas partie de l'analyse.
274
+ - **Progression** — dans la barre supérieure, à gauche de « Terminer la revue » : « 3/7 revus » et une barre étroite, visibles quel que soit le défilement et même panneau des fichiers masqué.
275
+ - **Panneau des fichiers** — nombre de fichiers, fichiers groupés par dossier, état revu, pastilles des constats ouverts par gravité et, en bout de ligne, la lettre du statut git à la manière de VS Code : `M` modifié, `A` ajouté, `D` supprimé, `R` renommé, `U` non suivi (libellé complet en infobulle). 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 »). Sa largeur se règle en glissant son bord droit (entre 200 et 600 px ; flèches gauche et droite quand la poignée a le focus, double-clic pour revenir à 280 px) et se conserve dans le navigateur d'un rechargement à l'autre. Le bouton en tête de l'en-tête du fichier masque ou réaffiche le panneau, pour toute la session : le diff s'élargit vers la gauche, la colonne de droite garde sa largeur. Il reste affiché quand le chemin demandé ne fait pas partie de l'analyse.
263
276
  - **En-tête du fichier** — bascule du panneau des fichiers, chemin, `+`/`−`, statut, fichier précédent et suivant, « Remarque sur le fichier », « Marquer comme revu ».
264
277
  - **Diff** — code coloré selon le langage du fichier, déduit de son extension ou de son nom (grammaires de [Shiki](https://shiki.style), chargées à la demande) : le côté nouveau à partir du contenu complet du fichier quand le snapshot le conserve, sinon bloc par bloc ; les lignes supprimées bloc par bloc ; chaque correctif proposé séparément ; les lignes barrées par un correctif restent sans couleur. Un fichier dont le langage n'est pas reconnu s'affiche sans coloration ; une grammaire qui ne se charge pas est signalée dans la barre du 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. La barre du diff porte trois réglages, communs à tous les fichiers et conservés dans le navigateur d'un rechargement à l'autre : chacun est un bouton compact qui affiche sa valeur courante, et un clic ouvre la liste de ses choix, chacun avec son icône et une description d'une ligne (un clic dehors, Échap ou un défilement la referme). « Afficher » choisit entre « Modifications » (les blocs du diff git, entourés de leurs lignes de contexte) et « Fichier entier » (tout le fichier en un seul bloc : les lignes inchangées, tirées du contenu conservé, s'intercalent entre les modifications, ce qui donne à lire en entier une fonction modifiée en son milieu ; les deux autres sélecteurs s'y appliquent de la même façon, un correctif dont les lignes chevauchaient deux blocs s'y affiche, et « Copier une demande d'explication » porte alors sur tout le fichier). En « Fichier entier », une mini-carte de 22 px longe le bord droit du diff et reste à l'écran pendant le défilement : toute sa hauteur représente tout le fichier affiché. Sa piste de gauche place les lignes ajoutées (vert), supprimées (rouge), supprimées et ajoutées en vis-à-vis en vue « Côte à côte » (moitié rouge, moitié verte) ou seulement réindentées (violet) ; sa piste de droite porte un point par constat, à la couleur de sa gravité (atténué s'il n'est plus ouvert), et un point par ligne portant une remarque. Un cadre bleuté suit la portion visible. Le survol d'un repère en donne la description (« 12 lignes ajoutées · lignes 120 à 131 », « Constat 3 · Majeur » et son titre) ; un clic ou un glissé sur la carte fait défiler le diff jusqu'à l'endroit visé. « Fichier entier » est inactif pour un fichier dont le contenu n'est pas conservé ; la liste en donne la raison à la place de sa description. « Code » choisit entre « Unifié » (une seule colonne : lignes supprimées sur fond rouge au-dessus des lignes ajoutées sur fond vert), « Côte à côte » (ancien code à gauche avec ses numéros du côté ancien, nouveau code à droite : lignes de contexte des deux côtés, lignes supprimées et ajoutées appariées ligne à ligne, case hachurée en face d'une ligne sans vis-à-vis ; remarques, repères et lignes proposées par un correctif sont du côté droit, le bandeau d'un correctif sur toute la largeur) et « Nouveau code » (le code du côté nouveau seul, dans les mêmes blocs : les lignes supprimées ne sont pas affichées et les lignes ajoutées portent un fin filet vert à la place de leur fond et de leur marque ; une explication de code supprimé s'aligne alors sur la ligne voisine du bloc). « Correctifs » choisit entre « Sans correctif », « Avant / après » et « Code corrigé ». 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. En « Modifications », quand le contenu du fichier est conservé, l'en-tête de chaque bloc porte un réglage « −5 · N lignes · +5 » : « +5 » ajoute cinq lignes de contexte au-dessus et cinq en dessous du bloc, « −5 » les retire, et le compteur donne le nombre de lignes de contexte au-dessus de la première modification. Les lignes ajoutées s'insèrent sous l'en-tête et après le bloc, si bien que le réglage ne bouge pas à l'écran ; elles se distinguent par un fond plus sombre et un filet gris (« Contexte ajouté » dans la légende). Les lignes entre deux blocs vont d'abord au bloc du dessus ; quand plus aucune ligne masquée ne les sépare, les deux blocs n'en forment plus qu'un, sous un seul en-tête dont « +5 » et « −5 » s'appliquent à chacun des blocs réunis. « +5 » s'éteint quand le bloc atteint les bords du fichier. Le contexte choisi est propre à chaque fichier et dure le temps de la session. L'en-tête de chaque bloc porte aussi « Copier une demande d'explication » (icône du robot) : 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 la vue et le mode affichent : « Ajouté par la feature » (fond ou filet selon la vue), « Supprimé par la feature » (Unifié et Côte à côte), « Lignes remplacées par le correctif » (Avant / après), « Correctif proposé » ou « Correctif appliqué », « Code décrit par une explication ». Quand une ligne supprimée et la ligne ajoutée qui la remplace se ressemblent (la moitié de leurs mots au moins en commun), seules leurs parties modifiées sont surlignées, en rouge sur l'ancienne ligne et en vert sur la nouvelle, sans fond sur le reste de la ligne ; dans chaque suite de lignes supprimées puis ajoutées, la i-ème ligne supprimée est comparée à la i-ème ligne ajoutée, mot à mot. Une ligne sans correspondante proche garde son fond entier (« Partie modifiée de la ligne » dans la légende). 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.
265
278
  - **Affichage des correctifs** — le sélecteur « Correctifs », au-dessus du diff, propose trois modes, conservés d'un fichier à l'autre et d'un rechargement à l'autre :
@@ -0,0 +1,36 @@
1
+ import type { Analysis, AnalysisLink, FindingLocation, FollowUpLink, NoteLocation, Processing, ReviewDecision, Severity } from "./schemas/analysis.schema.js";
2
+ /** Point retenu dans le retour d'une analyse, à vérifier dans sa revue de suivi. */
3
+ export type FeedbackPoint = {
4
+ kind: "finding";
5
+ id: string;
6
+ severity: Severity | null;
7
+ title: string;
8
+ location: FindingLocation | null;
9
+ } | {
10
+ kind: "note";
11
+ id: string;
12
+ text: string;
13
+ location: NoteLocation | null;
14
+ };
15
+ /** Analyse suivie, vue depuis sa revue de suivi : son retour et les points qu'il retenait. */
16
+ export interface PreviousReview extends AnalysisLink {
17
+ submittedAt: string | null;
18
+ decision: ReviewDecision | null;
19
+ processed: Processing | null;
20
+ points: FeedbackPoint[];
21
+ }
22
+ /** Liens de suivi d'une analyse. */
23
+ export interface AnalysisLinks {
24
+ /** Analyse dont celle-ci est la revue de suivi. */
25
+ previous: PreviousReview | null;
26
+ /** Revue de suivi de celle-ci. */
27
+ next: FollowUpLink | null;
28
+ /** Toutes les analyses de la chaîne, de la première revue à la dernière, celle-ci comprise. */
29
+ chain: AnalysisLink[];
30
+ }
31
+ /**
32
+ * Points retenus par le retour d'une analyse : constats (du plus grave au moins
33
+ * grave) puis remarques. Pour une revue rouverte par le relecteur, ceux de sa
34
+ * dernière version soumise ; aucun pour une revue jamais soumise.
35
+ */
36
+ export declare function feedbackPoints(analysis: Pick<Analysis, "review" | "findings" | "notes">): FeedbackPoint[];
@@ -0,0 +1,33 @@
1
+ import { sortBySeverity } from "./severity.js";
2
+ /**
3
+ * Points retenus par le retour d'une analyse : constats (du plus grave au moins
4
+ * grave) puis remarques. Pour une revue rouverte par le relecteur, ceux de sa
5
+ * dernière version soumise ; aucun pour une revue jamais soumise.
6
+ */
7
+ export function feedbackPoints(analysis) {
8
+ const review = analysis.review;
9
+ if (review.state === "submitted") {
10
+ const findingIds = new Set(review.selectedFindingIds);
11
+ const noteIds = new Set(review.selectedNoteIds);
12
+ return [
13
+ ...sortBySeverity(analysis.findings.filter((finding) => findingIds.has(finding.id))).map((finding) => ({ kind: "finding", id: finding.id, severity: finding.severity, title: finding.title, location: finding.location })),
14
+ ...analysis.notes
15
+ .filter((note) => noteIds.has(note.id))
16
+ .map((note) => ({ kind: "note", id: note.id, text: note.text, location: note.location })),
17
+ ];
18
+ }
19
+ const previous = review.previousSubmission;
20
+ if (!previous)
21
+ return [];
22
+ const findings = new Map(analysis.findings.map((finding) => [finding.id, finding]));
23
+ return [
24
+ ...previous.findings.map(({ id, title }) => ({
25
+ kind: "finding",
26
+ id,
27
+ severity: findings.get(id)?.severity ?? null,
28
+ title,
29
+ location: findings.get(id)?.location ?? null,
30
+ })),
31
+ ...previous.notes.map(({ id, text, location }) => ({ kind: "note", id, text, location })),
32
+ ];
33
+ }
@@ -191,10 +191,17 @@ export declare const DiagramSchema: z.ZodObject<{
191
191
  }, z.core.$strip>>;
192
192
  updatedAt: z.ZodString;
193
193
  }, z.core.$strip>;
194
- /** Traitement d'un retour par l'agent : date et note facultative (commits, réponses données…). */
194
+ /**
195
+ * Traitement d'un retour : date, note facultative (commits, réponses données…) et
196
+ * auteur du marquage — l'agent, ou le relecteur depuis la GUI.
197
+ */
195
198
  export declare const ProcessingSchema: z.ZodObject<{
196
199
  at: z.ZodString;
197
200
  note: z.ZodNullable<z.ZodString>;
201
+ by: z.ZodEnum<{
202
+ agent: "agent";
203
+ user: "user";
204
+ }>;
198
205
  }, z.core.$strip>;
199
206
  /**
200
207
  * Dernière version soumise d'une revue, conservée quand le relecteur la modifie
@@ -211,6 +218,10 @@ export declare const SubmissionRecordSchema: z.ZodObject<{
211
218
  processed: z.ZodNullable<z.ZodObject<{
212
219
  at: z.ZodString;
213
220
  note: z.ZodNullable<z.ZodString>;
221
+ by: z.ZodEnum<{
222
+ agent: "agent";
223
+ user: "user";
224
+ }>;
214
225
  }, z.core.$strip>>;
215
226
  findings: z.ZodArray<z.ZodObject<{
216
227
  id: z.ZodString;
@@ -244,6 +255,10 @@ export declare const ReviewSchema: z.ZodObject<{
244
255
  processed: z.ZodNullable<z.ZodObject<{
245
256
  at: z.ZodString;
246
257
  note: z.ZodNullable<z.ZodString>;
258
+ by: z.ZodEnum<{
259
+ agent: "agent";
260
+ user: "user";
261
+ }>;
247
262
  }, z.core.$strip>>;
248
263
  previousSubmission: z.ZodNullable<z.ZodObject<{
249
264
  decision: z.ZodEnum<{
@@ -255,6 +270,10 @@ export declare const ReviewSchema: z.ZodObject<{
255
270
  processed: z.ZodNullable<z.ZodObject<{
256
271
  at: z.ZodString;
257
272
  note: z.ZodNullable<z.ZodString>;
273
+ by: z.ZodEnum<{
274
+ agent: "agent";
275
+ user: "user";
276
+ }>;
258
277
  }, z.core.$strip>>;
259
278
  findings: z.ZodArray<z.ZodObject<{
260
279
  id: z.ZodString;
@@ -302,6 +321,7 @@ export declare const AnalysisSchema: z.ZodObject<{
302
321
  text: z.ZodString;
303
322
  source: z.ZodNullable<z.ZodString>;
304
323
  }, z.core.$strip>>;
324
+ followUpOf: z.ZodNullable<z.ZodString>;
305
325
  overview: z.ZodNullable<z.ZodObject<{
306
326
  objective: z.ZodString;
307
327
  approach: z.ZodString;
@@ -440,6 +460,10 @@ export declare const AnalysisSchema: z.ZodObject<{
440
460
  processed: z.ZodNullable<z.ZodObject<{
441
461
  at: z.ZodString;
442
462
  note: z.ZodNullable<z.ZodString>;
463
+ by: z.ZodEnum<{
464
+ agent: "agent";
465
+ user: "user";
466
+ }>;
443
467
  }, z.core.$strip>>;
444
468
  previousSubmission: z.ZodNullable<z.ZodObject<{
445
469
  decision: z.ZodEnum<{
@@ -451,6 +475,10 @@ export declare const AnalysisSchema: z.ZodObject<{
451
475
  processed: z.ZodNullable<z.ZodObject<{
452
476
  at: z.ZodString;
453
477
  note: z.ZodNullable<z.ZodString>;
478
+ by: z.ZodEnum<{
479
+ agent: "agent";
480
+ user: "user";
481
+ }>;
454
482
  }, z.core.$strip>>;
455
483
  findings: z.ZodArray<z.ZodObject<{
456
484
  id: z.ZodString;
@@ -521,6 +549,15 @@ export interface ReviewProgress {
521
549
  /** Revue soumise dont le retour attend l'agent (voir `needsProcessing`). */
522
550
  toProcess: boolean;
523
551
  }
552
+ /** Référence à une autre analyse. */
553
+ export interface AnalysisLink {
554
+ id: string;
555
+ title: string;
556
+ }
557
+ /** Revue de suivi d'une analyse, avec l'avancement de sa revue. */
558
+ export interface FollowUpLink extends AnalysisLink {
559
+ progress: ReviewProgress;
560
+ }
524
561
  /** Vue légère d'une analyse, pour les listes. */
525
562
  export interface AnalysisSummary {
526
563
  id: string;
@@ -535,6 +572,12 @@ export interface AnalysisSummary {
535
572
  openFindings: Record<Severity, number>;
536
573
  diagramCount: number;
537
574
  progress: ReviewProgress;
575
+ /** Analyse dont celle-ci est la revue de suivi. */
576
+ followUpOf: AnalysisLink | null;
577
+ /** Revue de suivi de celle-ci ; une analyse en a au plus une. */
578
+ followedBy: FollowUpLink | null;
579
+ /** Rang de l'analyse dans sa chaîne de revues : 1 pour une première revue. */
580
+ round: number;
538
581
  updatedAt: string;
539
582
  revision: number;
540
583
  }
@@ -122,10 +122,14 @@ export const DiagramSchema = z.object({
122
122
  links: z.array(DiagramLinkSchema),
123
123
  updatedAt: IsoDateSchema,
124
124
  });
125
- /** Traitement d'un retour par l'agent : date et note facultative (commits, réponses données…). */
125
+ /**
126
+ * Traitement d'un retour : date, note facultative (commits, réponses données…) et
127
+ * auteur du marquage — l'agent, ou le relecteur depuis la GUI.
128
+ */
126
129
  export const ProcessingSchema = z.object({
127
130
  at: IsoDateSchema,
128
131
  note: z.string().min(1).nullable(),
132
+ by: z.enum(EDITORS),
129
133
  });
130
134
  /**
131
135
  * Dernière version soumise d'une revue, conservée quand le relecteur la modifie
@@ -187,6 +191,8 @@ export const AnalysisSchema = z.object({
187
191
  headCommit: z.string().min(1).nullable(),
188
192
  snapshotAt: IsoDateSchema,
189
193
  request: RequestSchema.nullable(),
194
+ /** Analyse dont celle-ci est la revue de suivi ; null pour une première revue. */
195
+ followUpOf: z.string().regex(ANALYSIS_ID_PATTERN).nullable(),
190
196
  overview: OverviewSchema.nullable(),
191
197
  /** Changements fonctionnels de la feature, une puce par changement. */
192
198
  summary: z.array(z.string().min(1)),
@@ -40,7 +40,13 @@ export declare const SubmitReviewBodySchema: z.ZodObject<{
40
40
  noteIds: z.ZodArray<z.ZodString>;
41
41
  baseRevision: z.ZodNumber;
42
42
  }, z.core.$strict>;
43
+ /** Marquage du retour soumis comme traité par le relecteur, avec une note facultative. */
44
+ export declare const MarkProcessedBodySchema: z.ZodObject<{
45
+ note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
46
+ baseRevision: z.ZodNumber;
47
+ }, z.core.$strict>;
43
48
  export type SetFileReviewedBody = z.infer<typeof SetFileReviewedBodySchema>;
49
+ export type MarkProcessedBody = z.infer<typeof MarkProcessedBodySchema>;
44
50
  export type SetFindingStatusBody = z.infer<typeof SetFindingStatusBodySchema>;
45
51
  export type AddNoteBody = z.infer<typeof AddNoteBodySchema>;
46
52
  export type UpdateNoteBody = z.infer<typeof UpdateNoteBodySchema>;
@@ -108,3 +114,10 @@ export declare const SubmitReviewRequestSchema: z.ZodObject<{
108
114
  baseRevision: z.ZodNumber;
109
115
  }, z.core.$strict>;
110
116
  }, z.core.$strict>;
117
+ export declare const MarkProcessedRequestSchema: z.ZodObject<{
118
+ analysisId: z.ZodString;
119
+ body: z.ZodObject<{
120
+ note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
121
+ baseRevision: z.ZodNumber;
122
+ }, z.core.$strict>;
123
+ }, z.core.$strict>;
@@ -39,6 +39,11 @@ export const SubmitReviewBodySchema = z.strictObject({
39
39
  noteIds: z.array(z.string().min(1)),
40
40
  baseRevision: BaseRevisionSchema,
41
41
  });
42
+ /** Marquage du retour soumis comme traité par le relecteur, avec une note facultative. */
43
+ export const MarkProcessedBodySchema = z.strictObject({
44
+ note: z.string().trim().min(1, "the note is empty").nullable().optional(),
45
+ baseRevision: BaseRevisionSchema,
46
+ });
42
47
  /** Données IPC des requêtes de la GUI qui visent une analyse. */
43
48
  export const AnalysisRequestSchema = z.strictObject({ analysisId: z.string().min(1) });
44
49
  export const SetFileReviewedRequestSchema = AnalysisRequestSchema.extend({ body: SetFileReviewedBodySchema });
@@ -56,3 +61,4 @@ export const DeleteNoteRequestSchema = AnalysisRequestSchema.extend({
56
61
  body: DeleteNoteBodySchema,
57
62
  });
58
63
  export const SubmitReviewRequestSchema = AnalysisRequestSchema.extend({ body: SubmitReviewBodySchema });
64
+ export const MarkProcessedRequestSchema = AnalysisRequestSchema.extend({ body: MarkProcessedBodySchema });
@@ -40,5 +40,5 @@ export type ChangesResponse = {
40
40
  events: IPCMessage[];
41
41
  };
42
42
  /** Requêtes du GUI worker au maître ; chacune reçoit une réponse de même `correlationId`. */
43
- 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"];
43
+ 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", "MARK_PROCESSED"];
44
44
  export type GuiRequestType = (typeof GUI_REQUEST_TYPES)[number];
@@ -28,4 +28,5 @@ export const GUI_REQUEST_TYPES = [
28
28
  "UPDATE_NOTE",
29
29
  "DELETE_NOTE",
30
30
  "SUBMIT_REVIEW",
31
+ "MARK_PROCESSED",
31
32
  ];
@@ -7,6 +7,8 @@ export interface CreateAnalysisInput {
7
7
  title: string;
8
8
  request?: AnalysisRequest | null;
9
9
  summary?: string[];
10
+ /** Analyse dont la nouvelle analyse est la revue de suivi. */
11
+ followUpOf?: string | null;
10
12
  }
11
13
  export interface MutateOptions {
12
14
  /**
@@ -89,5 +91,12 @@ export declare class AnalysisStore {
89
91
  /** Supprime l'analyse et son snapshot de diff. */
90
92
  delete(id: string): void;
91
93
  }
92
- /** Vue légère d'une analyse. */
94
+ /**
95
+ * Renseigne les liens de suivi des résumés : `followUpOf` (analyse suivie),
96
+ * `followedBy` (revue de suivi) et `round` (rang dans la chaîne). `followUpOf`
97
+ * associe chaque revue de suivi à l'analyse qu'elle suit ; un lien vers une
98
+ * analyse absente de la liste est ignoré.
99
+ */
100
+ export declare function linkFollowUps(summaries: AnalysisSummary[], followUpOf: ReadonlyMap<string, string>): void;
101
+ /** Vue légère d'une analyse, sans ses liens de suivi (voir `linkFollowUps`). */
93
102
  export declare function summarize(analysis: Analysis): AnalysisSummary;
@@ -204,6 +204,7 @@ export class AnalysisStore {
204
204
  .readdirSync(this.analysesDir)
205
205
  .filter((file) => file.endsWith(".json"))
206
206
  .map((file) => file.slice(0, -".json".length));
207
+ const followUpOf = new Map();
207
208
  for (const id of ids) {
208
209
  let analysis;
209
210
  try {
@@ -215,7 +216,10 @@ export class AnalysisStore {
215
216
  continue;
216
217
  }
217
218
  analyses.push(summarize(analysis));
219
+ if (analysis.followUpOf !== null)
220
+ followUpOf.set(analysis.id, analysis.followUpOf);
218
221
  }
222
+ linkFollowUps(analyses, followUpOf);
219
223
  analyses.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
220
224
  return { analyses, unreadable };
221
225
  }
@@ -261,6 +265,7 @@ export class AnalysisStore {
261
265
  headCommit: snapshot.headCommit,
262
266
  snapshotAt: snapshot.snapshotAt,
263
267
  request: input.request ?? null,
268
+ followUpOf: input.followUpOf ?? null,
264
269
  overview: null,
265
270
  summary: input.summary ?? [],
266
271
  files: snapshot.files.map((file) => ({ ...file, reviewed: false })),
@@ -359,7 +364,35 @@ export class AnalysisStore {
359
364
  });
360
365
  }
361
366
  }
362
- /** Vue légère d'une analyse. */
367
+ /**
368
+ * Renseigne les liens de suivi des résumés : `followUpOf` (analyse suivie),
369
+ * `followedBy` (revue de suivi) et `round` (rang dans la chaîne). `followUpOf`
370
+ * associe chaque revue de suivi à l'analyse qu'elle suit ; un lien vers une
371
+ * analyse absente de la liste est ignoré.
372
+ */
373
+ export function linkFollowUps(summaries, followUpOf) {
374
+ const byId = new Map(summaries.map((summary) => [summary.id, summary]));
375
+ for (const [followUpId, originId] of followUpOf) {
376
+ const followUp = byId.get(followUpId);
377
+ const origin = byId.get(originId);
378
+ if (!followUp || !origin)
379
+ continue;
380
+ followUp.followUpOf = { id: origin.id, title: origin.title };
381
+ origin.followedBy = { id: followUp.id, title: followUp.title, progress: followUp.progress };
382
+ }
383
+ for (const summary of summaries) {
384
+ const seen = new Set([summary.id]);
385
+ let round = 1;
386
+ let previous = summary.followUpOf;
387
+ while (previous !== null && !seen.has(previous.id)) {
388
+ seen.add(previous.id);
389
+ round++;
390
+ previous = byId.get(previous.id)?.followUpOf ?? null;
391
+ }
392
+ summary.round = round;
393
+ }
394
+ }
395
+ /** Vue légère d'une analyse, sans ses liens de suivi (voir `linkFollowUps`). */
363
396
  export function summarize(analysis) {
364
397
  const openFindings = emptySeverityCounts();
365
398
  for (const finding of analysis.findings) {
@@ -378,6 +411,9 @@ export function summarize(analysis) {
378
411
  openFindings,
379
412
  diagramCount: analysis.diagrams.length,
380
413
  progress: reviewProgress(analysis),
414
+ followUpOf: null,
415
+ followedBy: null,
416
+ round: 1,
381
417
  updatedAt: analysis.updatedAt,
382
418
  revision: analysis.revision,
383
419
  };
@@ -0,0 +1,19 @@
1
+ import { type AnalysisLinks } from "../../shared/follow-ups.js";
2
+ import type { Analysis } from "../../shared/schemas/analysis.schema.js";
3
+ import type { AnalysisStore } from "./analysis-store.js";
4
+ /**
5
+ * Revues de suivi : une analyse peut déclarer, à sa création, celle dont elle est
6
+ * la suite (`followUpOf`). Une analyse a au plus une revue de suivi, et seule une
7
+ * analyse dont la revue est soumise peut en avoir une.
8
+ */
9
+ /** Vérifie que `originId` peut recevoir une revue de suivi et renvoie l'analyse. */
10
+ export declare function assertCanFollowUp(store: AnalysisStore, originId: string): Analysis;
11
+ /**
12
+ * Marque traitée l'analyse suivie quand son retour attendait encore l'agent, avec
13
+ * une note qui nomme la revue de suivi. Renvoie vrai si elle a été marquée.
14
+ */
15
+ export declare function markOriginFollowed(store: AnalysisStore, originId: string, followUp: Analysis): boolean;
16
+ /** Liens de suivi d'une analyse : analyse suivie avec son retour, revue de suivi et chaîne complète. */
17
+ export declare function analysisLinks(store: AnalysisStore, id: string): AnalysisLinks;
18
+ /** Détache la revue de suivi de `id`, s'il en a une, avant la suppression de `id`. Renvoie son id. */
19
+ export declare function detachFollowUp(store: AnalysisStore, id: string): string | null;
@@ -0,0 +1,85 @@
1
+ import { feedbackPoints } from "../../shared/follow-ups.js";
2
+ import { needsProcessing } from "../../shared/review-state.js";
3
+ import { AnalysisError } from "./errors.js";
4
+ import { markFeedbackProcessed } from "./review-actions.js";
5
+ /**
6
+ * Revues de suivi : une analyse peut déclarer, à sa création, celle dont elle est
7
+ * la suite (`followUpOf`). Une analyse a au plus une revue de suivi, et seule une
8
+ * analyse dont la revue est soumise peut en avoir une.
9
+ */
10
+ /** Vérifie que `originId` peut recevoir une revue de suivi et renvoie l'analyse. */
11
+ export function assertCanFollowUp(store, originId) {
12
+ const origin = store.get(originId);
13
+ if (origin.review.state !== "submitted") {
14
+ throw new AnalysisError(`Analysis "${originId}" cannot get a follow-up review: its review is not submitted. ` +
15
+ `A follow-up answers submitted feedback; read it with get_review_feedback once the reviewer has submitted it.`);
16
+ }
17
+ const existing = store.list().analyses.find((summary) => summary.id === originId)?.followedBy;
18
+ if (existing) {
19
+ throw new AnalysisError(`Analysis "${originId}" already has a follow-up review: "${existing.title}" (${existing.id}). ` +
20
+ `An analysis has at most one follow-up; follow up "${existing.id}" instead to start the next round.`);
21
+ }
22
+ return origin;
23
+ }
24
+ /**
25
+ * Marque traitée l'analyse suivie quand son retour attendait encore l'agent, avec
26
+ * une note qui nomme la revue de suivi. Renvoie vrai si elle a été marquée.
27
+ */
28
+ export function markOriginFollowed(store, originId, followUp) {
29
+ if (!needsProcessing(store.get(originId).review))
30
+ return false;
31
+ markFeedbackProcessed(store, originId, "agent", `Suivie par la revue « ${followUp.title} »`);
32
+ return true;
33
+ }
34
+ /** Résumé de `id` dans la liste, avec ses liens de suivi. */
35
+ function summaryOf(summaries, id) {
36
+ const summary = summaries.find((entry) => entry.id === id);
37
+ if (!summary)
38
+ throw new AnalysisError(`Analysis "${id}" is missing from the listing.`);
39
+ return summary;
40
+ }
41
+ /** Liens de suivi d'une analyse : analyse suivie avec son retour, revue de suivi et chaîne complète. */
42
+ export function analysisLinks(store, id) {
43
+ const summaries = store.list().analyses;
44
+ const byId = new Map(summaries.map((summary) => [summary.id, summary]));
45
+ const summary = summaryOf(summaries, id);
46
+ const backwards = [];
47
+ const seen = new Set([id]);
48
+ for (let link = summary.followUpOf; link !== null && !seen.has(link.id); link = byId.get(link.id)?.followUpOf ?? null) {
49
+ seen.add(link.id);
50
+ backwards.unshift({ id: link.id, title: link.title });
51
+ }
52
+ const forwards = [];
53
+ for (let link = summary.followedBy; link !== null && !seen.has(link.id); link = byId.get(link.id)?.followedBy ?? null) {
54
+ seen.add(link.id);
55
+ forwards.push({ id: link.id, title: link.title });
56
+ }
57
+ let previous = null;
58
+ if (summary.followUpOf !== null) {
59
+ const origin = store.get(summary.followUpOf.id);
60
+ const submitted = origin.review.state === "submitted" ? origin.review : origin.review.previousSubmission;
61
+ previous = {
62
+ id: origin.id,
63
+ title: origin.title,
64
+ submittedAt: submitted?.submittedAt ?? null,
65
+ decision: submitted?.decision ?? null,
66
+ processed: submitted?.processed ?? null,
67
+ points: feedbackPoints(origin),
68
+ };
69
+ }
70
+ return {
71
+ previous,
72
+ next: summary.followedBy,
73
+ chain: [...backwards, { id: summary.id, title: summary.title }, ...forwards],
74
+ };
75
+ }
76
+ /** Détache la revue de suivi de `id`, s'il en a une, avant la suppression de `id`. Renvoie son id. */
77
+ export function detachFollowUp(store, id) {
78
+ const followUp = store.list().analyses.find((summary) => summary.id === id)?.followedBy;
79
+ if (!followUp)
80
+ return null;
81
+ store.mutate(followUp.id, "agent", (draft) => {
82
+ draft.followUpOf = null;
83
+ });
84
+ return followUp.id;
85
+ }
@@ -21,6 +21,13 @@ export declare const addNoteFindingIds: MigrationStep;
21
21
  * avant ce format est à traiter, sans version précédente.
22
22
  */
23
23
  export declare const addReviewProcessing: MigrationStep;
24
+ /**
25
+ * Ajoute `by: "agent"` aux traitements enregistrés sans auteur, dans la revue et
26
+ * dans sa version soumise précédente : seul l'agent pouvait alors les poser.
27
+ */
28
+ export declare const addProcessingAuthor: MigrationStep;
29
+ /** Ajoute `followUpOf` à null : l'analyse n'est la revue de suivi d'aucune autre. */
30
+ export declare const addFollowUpOf: MigrationStep;
24
31
  /** Étapes appliquées aux analyses (`analyses/<id>.json`), dans l'ordre. */
25
32
  export declare const ANALYSIS_MIGRATIONS: readonly MigrationStep[];
26
33
  /** Étapes appliquées aux snapshots de diff (`diffs/<id>.json`), dans l'ordre. */
@@ -49,8 +49,48 @@ export const addReviewProcessing = (doc) => {
49
49
  return null;
50
50
  return { ...doc, review: { ...raw, processed: raw.processed ?? null, previousSubmission: raw.previousSubmission ?? null } };
51
51
  };
52
+ /** `processed` augmenté de `by: "agent"` quand il n'a pas d'auteur ; null quand il n'y a rien à changer. */
53
+ function withProcessingAuthor(processed) {
54
+ if (!processed || typeof processed !== "object" || Array.isArray(processed))
55
+ return null;
56
+ const raw = processed;
57
+ return raw.by !== undefined ? null : { ...raw, by: "agent" };
58
+ }
59
+ /**
60
+ * Ajoute `by: "agent"` aux traitements enregistrés sans auteur, dans la revue et
61
+ * dans sa version soumise précédente : seul l'agent pouvait alors les poser.
62
+ */
63
+ export const addProcessingAuthor = (doc) => {
64
+ const review = doc.review;
65
+ if (!review || typeof review !== "object" || Array.isArray(review))
66
+ return null;
67
+ const raw = review;
68
+ const processed = withProcessingAuthor(raw.processed);
69
+ const previous = raw.previousSubmission;
70
+ const previousProcessed = previous && typeof previous === "object" ? withProcessingAuthor(previous.processed) : null;
71
+ if (processed === null && previousProcessed === null)
72
+ return null;
73
+ return {
74
+ ...doc,
75
+ review: {
76
+ ...raw,
77
+ ...(processed === null ? {} : { processed }),
78
+ ...(previousProcessed === null ? {} : { previousSubmission: { ...previous, processed: previousProcessed } }),
79
+ },
80
+ };
81
+ };
82
+ /** Ajoute `followUpOf` à null : l'analyse n'est la revue de suivi d'aucune autre. */
83
+ export const addFollowUpOf = (doc) => (doc.followUpOf !== undefined ? null : { ...doc, followUpOf: null });
52
84
  /** Étapes appliquées aux analyses (`analyses/<id>.json`), dans l'ordre. */
53
- export const ANALYSIS_MIGRATIONS = [addProject, addOverview, addExplanations, addNoteFindingIds, addReviewProcessing];
85
+ export const ANALYSIS_MIGRATIONS = [
86
+ addProject,
87
+ addOverview,
88
+ addExplanations,
89
+ addNoteFindingIds,
90
+ addReviewProcessing,
91
+ addProcessingAuthor,
92
+ addFollowUpOf,
93
+ ];
54
94
  /** Étapes appliquées aux snapshots de diff (`diffs/<id>.json`), dans l'ordre. */
55
95
  export const SNAPSHOT_MIGRATIONS = [];
56
96
  /** Applique `steps` à la suite. Renvoie null si aucune étape n'a modifié le document. */