@imenam/mcp-feature-analyzer 1.0.19 → 1.0.20

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 (96) hide show
  1. package/README.md +4 -3
  2. package/dist/shared/schemas/analysis.schema.d.ts +28 -1
  3. package/dist/shared/schemas/analysis.schema.js +5 -1
  4. package/dist/shared/schemas/api.schema.d.ts +13 -0
  5. package/dist/shared/schemas/api.schema.js +6 -0
  6. package/dist/shared/schemas/ipc.schema.d.ts +1 -1
  7. package/dist/shared/schemas/ipc.schema.js +1 -0
  8. package/dist/src/core/migrate.d.ts +5 -0
  9. package/dist/src/core/migrate.js +38 -1
  10. package/dist/src/core/review-actions.d.ts +16 -3
  11. package/dist/src/core/review-actions.js +25 -0
  12. package/dist/src/gui-worker.js +7 -1
  13. package/dist/src/index.js +8 -2
  14. package/dist/src/tools/format.js +1 -1
  15. package/dist/src/tools/get-review-feedback.js +5 -3
  16. package/dist/src/tools/mark-feedback-processed.d.ts +2 -3
  17. package/dist/src/tools/mark-feedback-processed.js +4 -17
  18. package/gui/dist/assets/{angular-html-BG32ZSEg.js → angular-html-DjjSiqQZ.js} +1 -1
  19. package/gui/dist/assets/{angular-ts-W997xU6j.js → angular-ts-BTRcXEj8.js} +1 -1
  20. package/gui/dist/assets/{apl-6ogEEQiK.js → apl-By3131Sn.js} +1 -1
  21. package/gui/dist/assets/{astro-DBxTz6gT.js → astro-GOx0I6tH.js} +1 -1
  22. package/gui/dist/assets/{blade-PbZvkn0I.js → blade-SxDzr2ab.js} +1 -1
  23. package/gui/dist/assets/{c-C5-3wfrB.js → c-rXSNR5kl.js} +1 -1
  24. package/gui/dist/assets/{chapel-KHYeKJD5.js → chapel-nBfsanOz.js} +1 -1
  25. package/gui/dist/assets/{cobol-D51AxaJB.js → cobol-BiZycENC.js} +1 -1
  26. package/gui/dist/assets/{coffee-4h6_4dhl.js → coffee-BlljDtvu.js} +1 -1
  27. package/gui/dist/assets/{cpp-B63oWnHV.js → cpp-B_sYZ92I.js} +1 -1
  28. package/gui/dist/assets/{crystal-OG_TkIE7.js → crystal-Cg0a9qqm.js} +1 -1
  29. package/gui/dist/assets/{css-Csea3soR.js → css-LHykI9SM.js} +1 -1
  30. package/gui/dist/assets/{edge-HBaaYuuJ.js → edge-5wEkHYNr.js} +1 -1
  31. package/gui/dist/assets/{elixir-DZlxxd9b.js → elixir-B2Ez6C_y.js} +1 -1
  32. package/gui/dist/assets/{elm-DZAjSs9M.js → elm-CnwUpgFN.js} +1 -1
  33. package/gui/dist/assets/{erb-BurFBvpV.js → erb-9ibbHo7M.js} +1 -1
  34. package/gui/dist/assets/{git-rebase-B7wPwCu-.js → git-rebase-DffAuf0G.js} +1 -1
  35. package/gui/dist/assets/{glimmer-js-Bgk8Z51T.js → glimmer-js-VAknUS9x.js} +1 -1
  36. package/gui/dist/assets/{glimmer-ts-ldZtmANh.js → glimmer-ts-DcrYlRqt.js} +1 -1
  37. package/gui/dist/assets/{glsl-7SImpJuS.js → glsl-CYGBzxMW.js} +1 -1
  38. package/gui/dist/assets/{graphql-CJ0BLjCq.js → graphql-Bv55IETC.js} +1 -1
  39. package/gui/dist/assets/{hack-D50FsJQB.js → hack-D8V-_7lb.js} +1 -1
  40. package/gui/dist/assets/{haml-xkw_a2ii.js → haml-Bp-WEz3w.js} +1 -1
  41. package/gui/dist/assets/{handlebars-C0upK5dG.js → handlebars-B5v0_vx8.js} +1 -1
  42. package/gui/dist/assets/{html-CerDkvKq.js → html-BudjcI9P.js} +1 -1
  43. package/gui/dist/assets/{html-derivative-BfB3QMj5.js → html-derivative-BVPFhHTi.js} +1 -1
  44. package/gui/dist/assets/{http-pXaTY6-R.js → http-BNWhTfh4.js} +1 -1
  45. package/gui/dist/assets/{hurl-BNtMT88T.js → hurl-BQXVMX2w.js} +1 -1
  46. package/gui/dist/assets/{index-CN9ojYe6.css → index-DVca12tD.css} +1 -1
  47. package/gui/dist/assets/{index-CMjVOwnL.js → index-TuSnqzFd.js} +17 -17
  48. package/gui/dist/assets/{java-B3e3IyVz.js → java-U-bszQtz.js} +1 -1
  49. package/gui/dist/assets/{javascript-DgggYE1f.js → javascript-BOp_I2j_.js} +1 -1
  50. package/gui/dist/assets/{jinja-BHtm4gvT.js → jinja-CJ8_uK8s.js} +1 -1
  51. package/gui/dist/assets/{jison-B2YK1Y49.js → jison-CsIefIrr.js} +1 -1
  52. package/gui/dist/assets/{json-7jbWqmUr.js → json-BwmgqzNJ.js} +1 -1
  53. package/gui/dist/assets/{jsx-BFPQ2oqh.js → jsx-BLTVsDXL.js} +1 -1
  54. package/gui/dist/assets/{julia-DX1Bsm0x.js → julia-BEeV8R5w.js} +1 -1
  55. package/gui/dist/assets/{just-D524YAex.js → just-BwQDH9rp.js} +1 -1
  56. package/gui/dist/assets/{latex-Dc0dzAGl.js → latex-BJn4gKgg.js} +1 -1
  57. package/gui/dist/assets/{liquid-DXohsHev.js → liquid-B5SMgGD6.js} +1 -1
  58. package/gui/dist/assets/{lua-G_rux6Ay.js → lua-CVTScK8G.js} +1 -1
  59. package/gui/dist/assets/{marko-BdidjG96.js → marko-BneKfb4h.js} +1 -1
  60. package/gui/dist/assets/{mdc-DxAZb3tM.js → mdc-DHkkf3Ef.js} +1 -1
  61. package/gui/dist/assets/{nginx-CuvNT-q8.js → nginx-DxGETPm7.js} +1 -1
  62. package/gui/dist/assets/{nim-DuDgLkQa.js → nim-DV7N6lGB.js} +1 -1
  63. package/gui/dist/assets/{org-BJKUhgoL.js → org-D9idAuxR.js} +1 -1
  64. package/gui/dist/assets/{perl-BdJ-pZH6.js → perl-itV17GnJ.js} +1 -1
  65. package/gui/dist/assets/{php-DUOUy2DX.js → php-CCAkcfa5.js} +1 -1
  66. package/gui/dist/assets/{pug-WUnoGP-C.js → pug-C5resLq0.js} +1 -1
  67. package/gui/dist/assets/{qml-Dl8tY0zd.js → qml-BkHobuTC.js} +1 -1
  68. package/gui/dist/assets/{r-Ci9Hbbcn.js → r-BqWX-dEx.js} +1 -1
  69. package/gui/dist/assets/{razor-0bx4DTys.js → razor-BpA2ENLe.js} +1 -1
  70. package/gui/dist/assets/{regexp-C0ZlJfpq.js → regexp-C-XnPPLY.js} +1 -1
  71. package/gui/dist/assets/{rst-BC_vqA0U.js → rst-CUTbSAax.js} +1 -1
  72. package/gui/dist/assets/{ruby-DPPk42YS.js → ruby-D7bW5EZD.js} +1 -1
  73. package/gui/dist/assets/{sas-B8jfw3hR.js → sas-w8fOFl4u.js} +1 -1
  74. package/gui/dist/assets/{scss-7mTWL496.js → scss-BNjT1ImJ.js} +1 -1
  75. package/gui/dist/assets/{shellscript-CYNcJqc-.js → shellscript-CMWcepnG.js} +1 -1
  76. package/gui/dist/assets/{shellsession-Dh2E3t_B.js → shellsession-D5YeFBMB.js} +1 -1
  77. package/gui/dist/assets/{soy-BVR-TmA2.js → soy-CaNGX-mM.js} +1 -1
  78. package/gui/dist/assets/{sql-D3S51lwn.js → sql-DxGfvOCg.js} +1 -1
  79. package/gui/dist/assets/{stata-BjuRTlAG.js → stata-Bp4oSjfv.js} +1 -1
  80. package/gui/dist/assets/{surrealql-BKRHdvyc.js → surrealql-C8Utj_WH.js} +1 -1
  81. package/gui/dist/assets/{svelte-DIKo_e2T.js → svelte-BFHeACD2.js} +1 -1
  82. package/gui/dist/assets/{templ-DC42iqks.js → templ-CxVazkuY.js} +1 -1
  83. package/gui/dist/assets/{tex-BZ2lfoiV.js → tex-CSoJ8Afo.js} +1 -1
  84. package/gui/dist/assets/{ts-tags-o8CUeNFk.js → ts-tags-BUTYhDKV.js} +1 -1
  85. package/gui/dist/assets/{tsx-DeMh4Cat.js → tsx-BvqayBnu.js} +1 -1
  86. package/gui/dist/assets/{twig-BUr_lvSK.js → twig-DWygjTq9.js} +1 -1
  87. package/gui/dist/assets/{typescript-DeKHrHpS.js → typescript-C7DqIINO.js} +1 -1
  88. package/gui/dist/assets/{typst-CcbUABTI.js → typst-Dp1YSMkC.js} +1 -1
  89. package/gui/dist/assets/{vue-JHDlnmNz.js → vue-C6O_bBVG.js} +1 -1
  90. package/gui/dist/assets/{vue-html-soMMIiuc.js → vue-html-CMX7hzAZ.js} +1 -1
  91. package/gui/dist/assets/{vue-vine-D8TZS6Lm.js → vue-vine-DnhOPozF.js} +1 -1
  92. package/gui/dist/assets/{xml-9isD2blC.js → xml-03xiog6c.js} +1 -1
  93. package/gui/dist/assets/{xsl-chWKvWOz.js → xsl-Di6xLqaY.js} +1 -1
  94. package/gui/dist/assets/{yaml-DEY2M1js.js → yaml-BfGI9DCW.js} +1 -1
  95. package/gui/dist/index.html +2 -2
  96. package/package.json +1 -1
package/README.md CHANGED
@@ -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
 
@@ -242,7 +242,7 @@ Thème sombre. La barre supérieure porte la marque (un clic ramène à l'accuei
242
242
 
243
243
  ### Accueil (`#/`)
244
244
 
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`.
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 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 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 ; ce filtre ressort en sarcelle tant qu'il n'est pas vide), « 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`.
246
246
 
247
247
  ### Écran Feature (`#/<id>`)
248
248
 
@@ -259,7 +259,8 @@ L'ajustement à la fenêtre ne descend jamais sous l'échelle **0,85** : en dess
259
259
 
260
260
  ### Écran Revue (`#/<id>/review/<chemin>`)
261
261
 
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.
262
+ - **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é.
263
+ - **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
264
  - **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
265
  - **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
266
  - **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 :
@@ -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;
@@ -440,6 +459,10 @@ export declare const AnalysisSchema: z.ZodObject<{
440
459
  processed: z.ZodNullable<z.ZodObject<{
441
460
  at: z.ZodString;
442
461
  note: z.ZodNullable<z.ZodString>;
462
+ by: z.ZodEnum<{
463
+ agent: "agent";
464
+ user: "user";
465
+ }>;
443
466
  }, z.core.$strip>>;
444
467
  previousSubmission: z.ZodNullable<z.ZodObject<{
445
468
  decision: z.ZodEnum<{
@@ -451,6 +474,10 @@ export declare const AnalysisSchema: z.ZodObject<{
451
474
  processed: z.ZodNullable<z.ZodObject<{
452
475
  at: z.ZodString;
453
476
  note: z.ZodNullable<z.ZodString>;
477
+ by: z.ZodEnum<{
478
+ agent: "agent";
479
+ user: "user";
480
+ }>;
454
481
  }, z.core.$strip>>;
455
482
  findings: z.ZodArray<z.ZodObject<{
456
483
  id: z.ZodString;
@@ -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
@@ -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
  ];
@@ -21,6 +21,11 @@ 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;
24
29
  /** Étapes appliquées aux analyses (`analyses/<id>.json`), dans l'ordre. */
25
30
  export declare const ANALYSIS_MIGRATIONS: readonly MigrationStep[];
26
31
  /** Étapes appliquées aux snapshots de diff (`diffs/<id>.json`), dans l'ordre. */
@@ -49,8 +49,45 @@ 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
+ };
52
82
  /** Étapes appliquées aux analyses (`analyses/<id>.json`), dans l'ordre. */
53
- export const ANALYSIS_MIGRATIONS = [addProject, addOverview, addExplanations, addNoteFindingIds, addReviewProcessing];
83
+ export const ANALYSIS_MIGRATIONS = [
84
+ addProject,
85
+ addOverview,
86
+ addExplanations,
87
+ addNoteFindingIds,
88
+ addReviewProcessing,
89
+ addProcessingAuthor,
90
+ ];
54
91
  /** Étapes appliquées aux snapshots de diff (`diffs/<id>.json`), dans l'ordre. */
55
92
  export const SNAPSHOT_MIGRATIONS = [];
56
93
  /** Applique `steps` à la suite. Renvoie null si aucune étape n'a modifié le document. */
@@ -1,6 +1,6 @@
1
- import type { Analysis } from "../../shared/schemas/analysis.schema.js";
2
- import type { AddNoteBody, DeleteNoteBody, SetFileReviewedBody, SetFindingStatusBody, SubmitReviewBody, UpdateNoteBody } from "../../shared/schemas/api.schema.js";
3
- import type { AnalysisStore } from "./analysis-store.js";
1
+ import type { Analysis, Editor } from "../../shared/schemas/analysis.schema.js";
2
+ import type { AddNoteBody, DeleteNoteBody, MarkProcessedBody, SetFileReviewedBody, SetFindingStatusBody, SubmitReviewBody, UpdateNoteBody } from "../../shared/schemas/api.schema.js";
3
+ import type { AnalysisStore, MutateOptions } from "./analysis-store.js";
4
4
  /**
5
5
  * Modifications faites par le relecteur depuis la GUI. Chacune passe par
6
6
  * `store.mutate(id, "user", …, { baseRevision })` et renvoie l'analyse écrite.
@@ -25,6 +25,19 @@ export declare function addNote(store: AnalysisStore, analysisId: string, body:
25
25
  export declare function updateNote(store: AnalysisStore, analysisId: string, noteId: string, body: UpdateNoteBody): Analysis;
26
26
  /** Supprime une remarque et la retire de la sélection de la revue. */
27
27
  export declare function deleteNote(store: AnalysisStore, analysisId: string, noteId: string, body: DeleteNoteBody): Analysis;
28
+ /** Analyse marquée traitée, et date du marquage qu'elle remplace s'il y en avait un. */
29
+ export interface MarkProcessedResult {
30
+ analysis: Analysis;
31
+ replaced: string | null;
32
+ }
33
+ /**
34
+ * Marque traité le retour soumis d'une analyse : date, note facultative et
35
+ * auteur (`agent` par l'outil MCP, `user` depuis la GUI). Refusé tant que la
36
+ * revue n'est pas soumise ; un nouveau marquage remplace le précédent.
37
+ */
38
+ export declare function markFeedbackProcessed(store: AnalysisStore, analysisId: string, editor: Editor, note: string | null, options?: MutateOptions): MarkProcessedResult;
39
+ /** Marque traité, depuis la GUI, le retour soumis d'une analyse. */
40
+ export declare function markProcessedByReviewer(store: AnalysisStore, analysisId: string, body: MarkProcessedBody): Analysis;
28
41
  /**
29
42
  * Soumet la revue : décision, points retenus et prompt calculé ici. La revue
30
43
  * soumise est à traiter par l'agent. Une nouvelle soumission remplace la
@@ -86,6 +86,31 @@ export function deleteNote(store, analysisId, noteId, body) {
86
86
  draft.review.selectedNoteIds = draft.review.selectedNoteIds.filter((id) => id !== noteId);
87
87
  }, { baseRevision: body.baseRevision });
88
88
  }
89
+ /**
90
+ * Marque traité le retour soumis d'une analyse : date, note facultative et
91
+ * auteur (`agent` par l'outil MCP, `user` depuis la GUI). Refusé tant que la
92
+ * revue n'est pas soumise ; un nouveau marquage remplace le précédent.
93
+ */
94
+ export function markFeedbackProcessed(store, analysisId, editor, note, options = {}) {
95
+ let replaced = null;
96
+ const analysis = store.mutate(analysisId, editor, (draft) => {
97
+ const review = draft.review;
98
+ if (review.state !== "submitted") {
99
+ const reopened = review.previousSubmission;
100
+ throw new AnalysisError(reopened
101
+ ? `The review of analysis "${analysisId}" is not submitted: the reviewer reopened the feedback submitted at ${reopened.submittedAt} and is changing it. ` +
102
+ `Wait until it is submitted again, then read it with get_review_feedback.`
103
+ : `The review of analysis "${analysisId}" is not submitted yet: there is no feedback to mark processed.`);
104
+ }
105
+ replaced = review.processed?.at ?? null;
106
+ review.processed = { at: new Date().toISOString(), note, by: editor };
107
+ }, options);
108
+ return { analysis, replaced };
109
+ }
110
+ /** Marque traité, depuis la GUI, le retour soumis d'une analyse. */
111
+ export function markProcessedByReviewer(store, analysisId, body) {
112
+ return markFeedbackProcessed(store, analysisId, "user", body.note ?? null, { baseRevision: body.baseRevision }).analysis;
113
+ }
89
114
  function assertKnownIds(label, ids, known) {
90
115
  const knownSet = new Set(known);
91
116
  const unknown = ids.filter((id) => !knownSet.has(id));
@@ -7,7 +7,7 @@ import fs from "fs";
7
7
  import { fileURLToPath } from "url";
8
8
  import { randomUUID } from "crypto";
9
9
  import { IPCMessageSchema, PUSHED_EVENT_TYPES } from "../shared/schemas/ipc.schema.js";
10
- import { AddNoteBodySchema, DeleteNoteBodySchema, SetFileReviewedBodySchema, SetFindingStatusBodySchema, SubmitReviewBodySchema, UpdateNoteBodySchema, } from "../shared/schemas/api.schema.js";
10
+ import { AddNoteBodySchema, DeleteNoteBodySchema, MarkProcessedBodySchema, SetFileReviewedBodySchema, SetFindingStatusBodySchema, SubmitReviewBodySchema, UpdateNoteBodySchema, } from "../shared/schemas/api.schema.js";
11
11
  import { resolveLogDir } from "./core/log-dir.js";
12
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
13
  function findProjectRoot(startDir) {
@@ -276,6 +276,12 @@ app.post("/api/analyses/:id/review", async (c) => {
276
276
  return read.response;
277
277
  return forward(c, "SUBMIT_REVIEW", { analysisId: c.req.param("id"), body: read.body });
278
278
  });
279
+ app.post("/api/analyses/:id/review/processed", async (c) => {
280
+ const read = await readBody(c, MarkProcessedBodySchema);
281
+ if ("response" in read)
282
+ return read.response;
283
+ return forward(c, "MARK_PROCESSED", { analysisId: c.req.param("id"), body: read.body });
284
+ });
279
285
  /** Route d'API inconnue : 404 explicite plutôt que la page de la SPA. */
280
286
  app.all("/api/*", (c) => c.json({ error: `Unknown API route: ${c.req.method} ${c.req.path}` }, 404));
281
287
  // Fichiers statiques de la SPA
package/dist/src/index.js CHANGED
@@ -16,9 +16,9 @@ import { DEFAULT_HEAD_REF, localRepo } from "./core/git.js";
16
16
  import { agentRepo } from "./core/agent-repo.js";
17
17
  import { EXEC_ARG, handleAgentExec } from "./core/gateway-exec.js";
18
18
  import { resolveRole } from "./core/role.js";
19
- import { addNote, deleteNote, setFileReviewed, setFindingStatus, submitReview, updateNote } from "./core/review-actions.js";
19
+ import { addNote, deleteNote, markProcessedByReviewer, setFileReviewed, setFindingStatus, submitReview, updateNote, } from "./core/review-actions.js";
20
20
  import { GUI_REQUEST_TYPES, IPCMessageSchema } from "../shared/schemas/ipc.schema.js";
21
- import { AddNoteRequestSchema, AnalysisRequestSchema, DeleteNoteRequestSchema, SetFileReviewedRequestSchema, SetFindingStatusRequestSchema, SubmitReviewRequestSchema, UpdateNoteRequestSchema, } from "../shared/schemas/api.schema.js";
21
+ import { AddNoteRequestSchema, AnalysisRequestSchema, DeleteNoteRequestSchema, SetFileReviewedRequestSchema, SetFindingStatusRequestSchema, SubmitReviewRequestSchema, MarkProcessedRequestSchema, UpdateNoteRequestSchema, } from "../shared/schemas/api.schema.js";
22
22
  import { ANALYSIS_MODES, DIAGRAM_KINDS, EXPLANATION_SIDES, FINDING_KINDS, NODE_SHAPES, NODE_STATUSES, SEVERITIES, } from "../shared/schemas/analysis.schema.js";
23
23
  import { errorResult, textResult } from "./tools/types.js";
24
24
  import { createAnalysis } from "./tools/create-analysis.js";
@@ -223,6 +223,12 @@ function handleGuiRequest(msg) {
223
223
  afterUserMutation(analysis.id);
224
224
  return { analysis };
225
225
  }
226
+ case "MARK_PROCESSED": {
227
+ const request = parseRequest(MarkProcessedRequestSchema, msg.data);
228
+ const analysis = markProcessedByReviewer(store, request.analysisId, request.body);
229
+ afterUserMutation(analysis.id);
230
+ return { analysis };
231
+ }
226
232
  default:
227
233
  throw new Error(`GUI request ${msg.type} has no handler.`);
228
234
  }
@@ -83,7 +83,7 @@ export function progressLabel(progress) {
83
83
  ? "pending processing"
84
84
  : progress.processed === null
85
85
  ? "nothing to process"
86
- : `processed at ${progress.processed.at}`;
86
+ : `processed at ${progress.processed.at}${progress.processed.by === "user" ? " by the reviewer" : ""}`;
87
87
  const decision = progress.state === "submitted" ? `, decision ${progress.decision}, ${processing}` : "";
88
88
  return `${REVIEW_PROGRESS_TEXT[progress.state]}${decision} (${progress.reviewedFiles}/${progress.totalFiles} files reviewed)`;
89
89
  }
@@ -2,10 +2,12 @@ import { hasNoChanges, needsProcessing, recordSubmission, reviewProgress, submis
2
2
  import { sortBySeverity } from "../../shared/severity.js";
3
3
  import { findingLine, indentBlock, noteLocationLabel, overviewLines, progressLabel } from "./format.js";
4
4
  import { rejectUnknownFields, requireString, textResult } from "./types.js";
5
- /** `processed at <date> (note: …)`, `nothing to process (approved)` ou `not processed`. */
5
+ /** `processed at <date> by the agent (note: …)`, `nothing to process (approved)` ou `not processed`. */
6
6
  function processingLabel(decision, processed) {
7
- if (processed !== null)
8
- return `processed at ${processed.at}${processed.note === null ? "" : ` (note: ${processed.note})`}`;
7
+ if (processed !== null) {
8
+ const by = processed.by === "user" ? "the reviewer" : "the agent";
9
+ return `processed at ${processed.at} by ${by}${processed.note === null ? "" : ` (note: ${processed.note})`}`;
10
+ }
9
11
  return decision === "approve" ? "nothing to process (approved)" : "not processed";
10
12
  }
11
13
  /** `submitted at <date>, decision <d>, processed at <date>…` : rappel d'une version soumise. */
@@ -1,8 +1,7 @@
1
1
  import type { AnalysisStore } from "../core/analysis-store.js";
2
2
  import { type Args, type MutationResult } from "./types.js";
3
3
  /**
4
- * Marque traité le retour soumis d'une analyse, avec la date et une note
5
- * facultative. Le retour reste lisible avec get_review_feedback. Refusé tant
6
- * que la revue n'est pas soumise ; un nouveau marquage remplace le précédent.
4
+ * Marque traité, au nom de l'agent, le retour soumis d'une analyse, avec la date
5
+ * et une note facultative. Le retour reste lisible avec get_review_feedback.
7
6
  */
8
7
  export declare function markFeedbackProcessed(store: AnalysisStore, args: Args): MutationResult;
@@ -1,27 +1,14 @@
1
- import { AnalysisError } from "../core/errors.js";
1
+ import { markFeedbackProcessed as markProcessed } from "../core/review-actions.js";
2
2
  import { optionalString, rejectUnknownFields, requireString, textResult } from "./types.js";
3
3
  /**
4
- * Marque traité le retour soumis d'une analyse, avec la date et une note
5
- * facultative. Le retour reste lisible avec get_review_feedback. Refusé tant
6
- * que la revue n'est pas soumise ; un nouveau marquage remplace le précédent.
4
+ * Marque traité, au nom de l'agent, le retour soumis d'une analyse, avec la date
5
+ * et une note facultative. Le retour reste lisible avec get_review_feedback.
7
6
  */
8
7
  export function markFeedbackProcessed(store, args) {
9
8
  rejectUnknownFields(args, ["analysis_id", "note"]);
10
9
  const id = requireString(args, "analysis_id");
11
10
  const note = optionalString(args, "note") ?? null;
12
- let replaced = null;
13
- const updated = store.mutate(id, "agent", (draft) => {
14
- const review = draft.review;
15
- if (review.state !== "submitted") {
16
- const reopened = review.previousSubmission;
17
- throw new AnalysisError(reopened
18
- ? `The review of analysis "${id}" is not submitted: the reviewer reopened the feedback submitted at ${reopened.submittedAt} and is changing it. ` +
19
- `Wait until it is submitted again, then read it with get_review_feedback.`
20
- : `The review of analysis "${id}" is not submitted yet: there is no feedback to mark processed.`);
21
- }
22
- replaced = review.processed?.at ?? null;
23
- review.processed = { at: new Date().toISOString(), note };
24
- });
11
+ const { analysis: updated, replaced } = markProcessed(store, id, "agent", note);
25
12
  const processed = updated.review.processed;
26
13
  const lines = [
27
14
  `Marked the feedback of "${updated.title}" (${updated.id}), submitted at ${updated.review.submittedAt}, as processed at ${processed.at}.`,