@imenam/mcp-feature-analyzer 1.0.20 → 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.
- package/README.md +19 -7
- package/dist/shared/follow-ups.d.ts +36 -0
- package/dist/shared/follow-ups.js +33 -0
- package/dist/shared/schemas/analysis.schema.d.ts +16 -0
- package/dist/shared/schemas/analysis.schema.js +2 -0
- package/dist/src/core/analysis-store.d.ts +10 -1
- package/dist/src/core/analysis-store.js +37 -1
- package/dist/src/core/follow-ups.d.ts +19 -0
- package/dist/src/core/follow-ups.js +85 -0
- package/dist/src/core/migrate.d.ts +2 -0
- package/dist/src/core/migrate.js +3 -0
- package/dist/src/index.js +15 -3
- package/dist/src/tools/create-analysis.d.ts +5 -1
- package/dist/src/tools/create-analysis.js +22 -4
- package/dist/src/tools/delete-analysis.d.ts +4 -1
- package/dist/src/tools/delete-analysis.js +11 -5
- package/dist/src/tools/format.d.ts +6 -0
- package/dist/src/tools/format.js +15 -0
- package/dist/src/tools/get-analysis.js +3 -1
- package/dist/src/tools/get-review-feedback.js +7 -2
- package/dist/src/tools/list-analyses.js +5 -0
- package/dist/src/tools/mark-feedback-processed.js +1 -1
- package/gui/dist/assets/{angular-html-DjjSiqQZ.js → angular-html-Cek_sXrk.js} +1 -1
- package/gui/dist/assets/{angular-ts-BTRcXEj8.js → angular-ts-UDyn2p3m.js} +1 -1
- package/gui/dist/assets/{apl-By3131Sn.js → apl-x_xlCIBP.js} +1 -1
- package/gui/dist/assets/{astro-GOx0I6tH.js → astro-DmQALJnP.js} +1 -1
- package/gui/dist/assets/{blade-SxDzr2ab.js → blade-pIl9LNwT.js} +1 -1
- package/gui/dist/assets/{c-rXSNR5kl.js → c-D0w4WEPA.js} +1 -1
- package/gui/dist/assets/{chapel-nBfsanOz.js → chapel-DYhi5M_6.js} +1 -1
- package/gui/dist/assets/{cobol-BiZycENC.js → cobol-Bi9NY2Z2.js} +1 -1
- package/gui/dist/assets/{coffee-BlljDtvu.js → coffee-BW5DOyKl.js} +1 -1
- package/gui/dist/assets/{cpp-B_sYZ92I.js → cpp-sz7PwxJX.js} +1 -1
- package/gui/dist/assets/{crystal-Cg0a9qqm.js → crystal-d2FoewBA.js} +1 -1
- package/gui/dist/assets/{css-LHykI9SM.js → css-BELRmmks.js} +1 -1
- package/gui/dist/assets/{edge-5wEkHYNr.js → edge-mQnFjIkx.js} +1 -1
- package/gui/dist/assets/{elixir-B2Ez6C_y.js → elixir-Dy83Pfni.js} +1 -1
- package/gui/dist/assets/{elm-CnwUpgFN.js → elm-CbWJN26n.js} +1 -1
- package/gui/dist/assets/{erb-9ibbHo7M.js → erb-YoUGVS38.js} +1 -1
- package/gui/dist/assets/{git-rebase-DffAuf0G.js → git-rebase-B1jMLAq6.js} +1 -1
- package/gui/dist/assets/{glimmer-js-VAknUS9x.js → glimmer-js-CEmA0NTT.js} +1 -1
- package/gui/dist/assets/{glimmer-ts-DcrYlRqt.js → glimmer-ts-kGsWetmY.js} +1 -1
- package/gui/dist/assets/{glsl-CYGBzxMW.js → glsl-BeGzr9bJ.js} +1 -1
- package/gui/dist/assets/{graphql-Bv55IETC.js → graphql-C6NIC2e-.js} +1 -1
- package/gui/dist/assets/{hack-D8V-_7lb.js → hack-KEwBG4mT.js} +1 -1
- package/gui/dist/assets/{haml-Bp-WEz3w.js → haml-CkZmvo0g.js} +1 -1
- package/gui/dist/assets/{handlebars-B5v0_vx8.js → handlebars-Auwsmq4s.js} +1 -1
- package/gui/dist/assets/{html-derivative-BVPFhHTi.js → html-derivative-vuqQ6-H_.js} +1 -1
- package/gui/dist/assets/{html-BudjcI9P.js → html-oCOc0XdA.js} +1 -1
- package/gui/dist/assets/{http-BNWhTfh4.js → http--nPEUAue.js} +1 -1
- package/gui/dist/assets/{hurl-BQXVMX2w.js → hurl-CtpqLncF.js} +1 -1
- package/gui/dist/assets/index-A6BLowRB.css +1 -0
- package/gui/dist/assets/{index-TuSnqzFd.js → index-B8qqkmWn.js} +18 -18
- package/gui/dist/assets/{java-U-bszQtz.js → java-DYzsoje1.js} +1 -1
- package/gui/dist/assets/{javascript-BOp_I2j_.js → javascript-C9DLMJzT.js} +1 -1
- package/gui/dist/assets/{jinja-CJ8_uK8s.js → jinja-bSnL04FA.js} +1 -1
- package/gui/dist/assets/{jison-CsIefIrr.js → jison-Cn8E8V5z.js} +1 -1
- package/gui/dist/assets/{json-BwmgqzNJ.js → json-d-QlpP_z.js} +1 -1
- package/gui/dist/assets/{jsx-BLTVsDXL.js → jsx-CIpU_P-t.js} +1 -1
- package/gui/dist/assets/{julia-BEeV8R5w.js → julia-DkcJm8gB.js} +1 -1
- package/gui/dist/assets/{just-BwQDH9rp.js → just-DIrw6CS3.js} +1 -1
- package/gui/dist/assets/{latex-BJn4gKgg.js → latex-BhxpsHBQ.js} +1 -1
- package/gui/dist/assets/{liquid-B5SMgGD6.js → liquid-C2rHX9Ca.js} +1 -1
- package/gui/dist/assets/{lua-CVTScK8G.js → lua-7VPHhWYZ.js} +1 -1
- package/gui/dist/assets/{marko-BneKfb4h.js → marko-CyYT4p0Z.js} +1 -1
- package/gui/dist/assets/{mdc-DHkkf3Ef.js → mdc-BabtknzF.js} +1 -1
- package/gui/dist/assets/{nginx-DxGETPm7.js → nginx-CFQwDuks.js} +1 -1
- package/gui/dist/assets/{nim-DV7N6lGB.js → nim-DpxcgIHl.js} +1 -1
- package/gui/dist/assets/{org-D9idAuxR.js → org-Di1bzXp6.js} +1 -1
- package/gui/dist/assets/{perl-itV17GnJ.js → perl-CiesRDJJ.js} +1 -1
- package/gui/dist/assets/{php-CCAkcfa5.js → php-XqsiYIMC.js} +1 -1
- package/gui/dist/assets/{pug-C5resLq0.js → pug-aREWfnPz.js} +1 -1
- package/gui/dist/assets/{qml-BkHobuTC.js → qml-DW8nc6nh.js} +1 -1
- package/gui/dist/assets/{r-BqWX-dEx.js → r-D_yTXRZ0.js} +1 -1
- package/gui/dist/assets/{razor-BpA2ENLe.js → razor-CyRfgle5.js} +1 -1
- package/gui/dist/assets/{regexp-C-XnPPLY.js → regexp-Iw19WR-v.js} +1 -1
- package/gui/dist/assets/{rst-CUTbSAax.js → rst-B_-k2S-N.js} +1 -1
- package/gui/dist/assets/{ruby-D7bW5EZD.js → ruby-CuA36vF6.js} +1 -1
- package/gui/dist/assets/{sas-w8fOFl4u.js → sas-D1kb68o2.js} +1 -1
- package/gui/dist/assets/{scss-BNjT1ImJ.js → scss-CqVwaT6R.js} +1 -1
- package/gui/dist/assets/{shellscript-CMWcepnG.js → shellscript-ZH1qXABi.js} +1 -1
- package/gui/dist/assets/{shellsession-D5YeFBMB.js → shellsession-C6Na6TIE.js} +1 -1
- package/gui/dist/assets/{soy-CaNGX-mM.js → soy-ByQ4bEBr.js} +1 -1
- package/gui/dist/assets/{sql-DxGfvOCg.js → sql-CZ6S4qI7.js} +1 -1
- package/gui/dist/assets/{stata-Bp4oSjfv.js → stata-BRy0gI05.js} +1 -1
- package/gui/dist/assets/{surrealql-C8Utj_WH.js → surrealql-lzF2k3Nd.js} +1 -1
- package/gui/dist/assets/{svelte-BFHeACD2.js → svelte-kK73PgnC.js} +1 -1
- package/gui/dist/assets/{templ-CxVazkuY.js → templ-6OIvTPYb.js} +1 -1
- package/gui/dist/assets/{tex-CSoJ8Afo.js → tex-CInwFac_.js} +1 -1
- package/gui/dist/assets/{ts-tags-BUTYhDKV.js → ts-tags-FsaaGgNy.js} +1 -1
- package/gui/dist/assets/{tsx-BvqayBnu.js → tsx-PUBs9-s-.js} +1 -1
- package/gui/dist/assets/{twig-DWygjTq9.js → twig-Bod5HEgw.js} +1 -1
- package/gui/dist/assets/{typescript-C7DqIINO.js → typescript-DG8BYJBt.js} +1 -1
- package/gui/dist/assets/{typst-Dp1YSMkC.js → typst-D2jY-D59.js} +1 -1
- package/gui/dist/assets/{vue-html-CMX7hzAZ.js → vue-html-zYeGUz6E.js} +1 -1
- package/gui/dist/assets/{vue-C6O_bBVG.js → vue-r2mYghHk.js} +1 -1
- package/gui/dist/assets/{vue-vine-DnhOPozF.js → vue-vine-DWGog3Vg.js} +1 -1
- package/gui/dist/assets/{xml-03xiog6c.js → xml-CfWom5su.js} +1 -1
- package/gui/dist/assets/{xsl-Di6xLqaY.js → xsl-BV2P-krQ.js} +1 -1
- package/gui/dist/assets/{yaml-BfGI9DCW.js → yaml-C-iwSc7Q.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/gui/dist/assets/index-DVca12tD.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
|
|
|
@@ -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
|
|
234
|
-
|
|
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 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
|
|
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.
|
|
@@ -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
|
+
}
|
|
@@ -321,6 +321,7 @@ export declare const AnalysisSchema: z.ZodObject<{
|
|
|
321
321
|
text: z.ZodString;
|
|
322
322
|
source: z.ZodNullable<z.ZodString>;
|
|
323
323
|
}, z.core.$strip>>;
|
|
324
|
+
followUpOf: z.ZodNullable<z.ZodString>;
|
|
324
325
|
overview: z.ZodNullable<z.ZodObject<{
|
|
325
326
|
objective: z.ZodString;
|
|
326
327
|
approach: z.ZodString;
|
|
@@ -548,6 +549,15 @@ export interface ReviewProgress {
|
|
|
548
549
|
/** Revue soumise dont le retour attend l'agent (voir `needsProcessing`). */
|
|
549
550
|
toProcess: boolean;
|
|
550
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
|
+
}
|
|
551
561
|
/** Vue légère d'une analyse, pour les listes. */
|
|
552
562
|
export interface AnalysisSummary {
|
|
553
563
|
id: string;
|
|
@@ -562,6 +572,12 @@ export interface AnalysisSummary {
|
|
|
562
572
|
openFindings: Record<Severity, number>;
|
|
563
573
|
diagramCount: number;
|
|
564
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;
|
|
565
581
|
updatedAt: string;
|
|
566
582
|
revision: number;
|
|
567
583
|
}
|
|
@@ -191,6 +191,8 @@ export const AnalysisSchema = z.object({
|
|
|
191
191
|
headCommit: z.string().min(1).nullable(),
|
|
192
192
|
snapshotAt: IsoDateSchema,
|
|
193
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(),
|
|
194
196
|
overview: OverviewSchema.nullable(),
|
|
195
197
|
/** Changements fonctionnels de la feature, une puce par changement. */
|
|
196
198
|
summary: z.array(z.string().min(1)),
|
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
+
}
|
|
@@ -26,6 +26,8 @@ export declare const addReviewProcessing: MigrationStep;
|
|
|
26
26
|
* dans sa version soumise précédente : seul l'agent pouvait alors les poser.
|
|
27
27
|
*/
|
|
28
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;
|
|
29
31
|
/** Étapes appliquées aux analyses (`analyses/<id>.json`), dans l'ordre. */
|
|
30
32
|
export declare const ANALYSIS_MIGRATIONS: readonly MigrationStep[];
|
|
31
33
|
/** Étapes appliquées aux snapshots de diff (`diffs/<id>.json`), dans l'ordre. */
|
package/dist/src/core/migrate.js
CHANGED
|
@@ -79,6 +79,8 @@ export const addProcessingAuthor = (doc) => {
|
|
|
79
79
|
},
|
|
80
80
|
};
|
|
81
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 });
|
|
82
84
|
/** Étapes appliquées aux analyses (`analyses/<id>.json`), dans l'ordre. */
|
|
83
85
|
export const ANALYSIS_MIGRATIONS = [
|
|
84
86
|
addProject,
|
|
@@ -87,6 +89,7 @@ export const ANALYSIS_MIGRATIONS = [
|
|
|
87
89
|
addNoteFindingIds,
|
|
88
90
|
addReviewProcessing,
|
|
89
91
|
addProcessingAuthor,
|
|
92
|
+
addFollowUpOf,
|
|
90
93
|
];
|
|
91
94
|
/** Étapes appliquées aux snapshots de diff (`diffs/<id>.json`), dans l'ordre. */
|
|
92
95
|
export const SNAPSHOT_MIGRATIONS = [];
|
package/dist/src/index.js
CHANGED
|
@@ -37,6 +37,7 @@ import { DEFAULT_NODE_SHAPE, DEFAULT_NODE_STATUS, setDiagram } from "./tools/set
|
|
|
37
37
|
import { deleteDiagram } from "./tools/delete-diagram.js";
|
|
38
38
|
import { getReviewFeedback } from "./tools/get-review-feedback.js";
|
|
39
39
|
import { markFeedbackProcessed } from "./tools/mark-feedback-processed.js";
|
|
40
|
+
import { analysisLinks } from "./core/follow-ups.js";
|
|
40
41
|
import { deleteAnalysis } from "./tools/delete-analysis.js";
|
|
41
42
|
const __filename = fileURLToPath(import.meta.url);
|
|
42
43
|
const __dirname = path.dirname(__filename);
|
|
@@ -95,6 +96,7 @@ Concepts:
|
|
|
95
96
|
- An **explanation** describes what a block of added or removed code does and how it works, so that the reviewer understands it before judging it. It is not a finding: it judges nothing and asks for nothing. It is anchored on new-side lines (added code) or old-side lines (removed code), and shown in the diff above the lines it describes.
|
|
96
97
|
- A **diagram** is a flow, layers or mindmap picture of the feature. You give nodes and links only; the GUI lays them out.
|
|
97
98
|
- The **review** belongs to the reviewer: files marked reviewed, findings ignored, notes, and a submitted decision with a prompt for you.
|
|
99
|
+
- A **follow-up review** is a new analysis created with follow_up_of: it reviews the fixes for the submitted feedback of an earlier analysis. Each analysis has at most one follow-up, so the rounds form a chain.
|
|
98
100
|
|
|
99
101
|
Workflow:
|
|
100
102
|
1. list_analyses (with project) to find an analysis of the same feature: reuse it with update_analysis and refresh_analysis instead of creating a duplicate. Otherwise create_analysis with its project once the feature is developed.
|
|
@@ -105,7 +107,7 @@ Workflow:
|
|
|
105
107
|
6. set_diagram when a picture helps (impacted layers, process flow, concept tree).
|
|
106
108
|
7. Tell the user to review the analysis in the GUI (the link is in the tool results).
|
|
107
109
|
8. get_review_feedback to read the decision, the selected points and the prompt. list_analyses with review_state "to_process" lists the submitted reviews you have not handled yet; an approved review has nothing to process.
|
|
108
|
-
9. Fix the code, then refresh_analysis to recompute the diff for a new review round; re-anchor or rewrite the outdated findings and explanations. When the
|
|
110
|
+
9. Fix the code, then refresh_analysis to recompute the diff for a new review round; re-anchor or rewrite the outdated findings and explanations. When the fixes are in other commits (typically an analysis of one commit), create their follow-up review instead: create_analysis on the fix commits with follow_up_of set to the analysis you fixed; it marks that feedback processed and shows its points to the reviewer. When nothing needs a new review (answers given in the conversation), call mark_feedback_processed with a note saying how the feedback was handled.
|
|
109
111
|
|
|
110
112
|
Explanation rules:
|
|
111
113
|
- Write one for every added or removed block that is long (a function or a block of about 30 lines or more) or complex (non-trivial algorithm, state machine, concurrency, recursion, tricky regular expression or data transformation). A 100-line function always gets one.
|
|
@@ -183,8 +185,10 @@ function handleGuiRequest(msg) {
|
|
|
183
185
|
return { dataDir, version: APP_VERSION };
|
|
184
186
|
case "LIST_ANALYSES":
|
|
185
187
|
return store.list();
|
|
186
|
-
case "GET_ANALYSIS":
|
|
187
|
-
|
|
188
|
+
case "GET_ANALYSIS": {
|
|
189
|
+
const analysisId = parseRequest(AnalysisRequestSchema, msg.data).analysisId;
|
|
190
|
+
return { analysis: store.get(analysisId), links: analysisLinks(store, analysisId) };
|
|
191
|
+
}
|
|
188
192
|
case "GET_DIFF":
|
|
189
193
|
return { diff: store.getSnapshot(parseRequest(AnalysisRequestSchema, msg.data).analysisId) };
|
|
190
194
|
case "SET_FILE_REVIEWED": {
|
|
@@ -407,6 +411,12 @@ const TOOLS = [
|
|
|
407
411
|
},
|
|
408
412
|
request_text: { type: "string", description: REQUEST_TEXT_DOC },
|
|
409
413
|
request_source: { type: "string", description: `${REQUEST_SOURCE_DOC} Needs request_text.` },
|
|
414
|
+
follow_up_of: {
|
|
415
|
+
type: "string",
|
|
416
|
+
description: "Id of the analysis whose submitted feedback this new analysis follows up, typically an analysis of the fix commits. " +
|
|
417
|
+
"Its feedback is marked processed if it was still waiting for you, and the reviewer sees its points to check in the new review. " +
|
|
418
|
+
"Refused when that review is not submitted or already has a follow-up (one follow-up per analysis; follow up the latest round instead).",
|
|
419
|
+
},
|
|
410
420
|
},
|
|
411
421
|
required: ["repo_path", "project", "title", "mode"],
|
|
412
422
|
},
|
|
@@ -751,6 +761,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
751
761
|
return await withRepoAccess(exec, async (repoFor) => {
|
|
752
762
|
const mutation = await createAnalysis(store, params, repoFor);
|
|
753
763
|
notifyAnalysesListChanged();
|
|
764
|
+
if (typeof params.follow_up_of === "string")
|
|
765
|
+
notifyAnalysisChanged(params.follow_up_of.trim());
|
|
754
766
|
return afterMutation(mutation);
|
|
755
767
|
});
|
|
756
768
|
case "update_analysis":
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { AnalysisStore } from "../core/analysis-store.js";
|
|
2
2
|
import { type RepoAccess } from "../core/git.js";
|
|
3
3
|
import { type Args, type MutationResult } from "./types.js";
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Calcule et fige le diff du dépôt, lu par `repoFor(repo_path)` (git local par
|
|
6
|
+
* défaut), puis crée l'analyse. Avec `follow_up_of`, la nouvelle analyse est la
|
|
7
|
+
* revue de suivi de celle-ci, dont le retour est marqué traité s'il attendait l'agent.
|
|
8
|
+
*/
|
|
5
9
|
export declare function createAnalysis(store: AnalysisStore, args: Args, repoFor?: (repoPath: string) => RepoAccess): Promise<MutationResult>;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { assertCanFollowUp, markOriginFollowed } from "../core/follow-ups.js";
|
|
1
2
|
import { computeSnapshot, localRepo } from "../core/git.js";
|
|
2
3
|
import { ANALYSIS_MODES } from "../../shared/schemas/analysis.schema.js";
|
|
3
4
|
import { fileLine, fileTotals, snapshotLabel } from "./format.js";
|
|
4
5
|
import { optionalString, rejectUnknownFields, requireEnum, requireString, textResult, } from "./types.js";
|
|
5
|
-
const FIELDS = ["repo_path", "project", "title", "mode", "base", "head", "request_text", "request_source"];
|
|
6
|
-
/**
|
|
6
|
+
const FIELDS = ["repo_path", "project", "title", "mode", "base", "head", "request_text", "request_source", "follow_up_of"];
|
|
7
|
+
/**
|
|
8
|
+
* Calcule et fige le diff du dépôt, lu par `repoFor(repo_path)` (git local par
|
|
9
|
+
* défaut), puis crée l'analyse. Avec `follow_up_of`, la nouvelle analyse est la
|
|
10
|
+
* revue de suivi de celle-ci, dont le retour est marqué traité s'il attendait l'agent.
|
|
11
|
+
*/
|
|
7
12
|
export async function createAnalysis(store, args, repoFor = localRepo) {
|
|
8
13
|
rejectUnknownFields(args, FIELDS);
|
|
9
14
|
const repoPath = requireString(args, "repo_path");
|
|
@@ -17,14 +22,27 @@ export async function createAnalysis(store, args, repoFor = localRepo) {
|
|
|
17
22
|
if (requestSource !== undefined && requestText === undefined) {
|
|
18
23
|
throw new Error(`"request_source" describes the initial request: give "request_text" too.`);
|
|
19
24
|
}
|
|
25
|
+
const followUpOf = optionalString(args, "follow_up_of");
|
|
26
|
+
const origin = followUpOf === undefined ? null : assertCanFollowUp(store, followUpOf);
|
|
20
27
|
const computed = await computeSnapshot({ repoPath, mode, base, head }, repoFor(repoPath));
|
|
21
|
-
const analysis = store.create({
|
|
28
|
+
const analysis = store.create({
|
|
29
|
+
project,
|
|
30
|
+
title,
|
|
31
|
+
request: requestText === undefined ? null : { text: requestText, source: requestSource ?? null },
|
|
32
|
+
followUpOf: origin?.id ?? null,
|
|
33
|
+
}, computed);
|
|
22
34
|
const lines = [
|
|
23
35
|
`Created analysis "${analysis.title}" (id: ${analysis.id}) in project "${analysis.project}".`,
|
|
24
36
|
`Repository: ${analysis.repoPath}`,
|
|
25
37
|
`Diff: ${snapshotLabel(analysis)}`,
|
|
26
|
-
"",
|
|
27
38
|
];
|
|
39
|
+
if (origin !== null) {
|
|
40
|
+
const marked = markOriginFollowed(store, origin.id, analysis);
|
|
41
|
+
lines.push(`Follow-up of: "${origin.title}" (${origin.id}). The reviewer sees the points of its feedback to check in this new review.`, marked
|
|
42
|
+
? `Its feedback was waiting for you: it is now marked processed, with a note naming this follow-up.`
|
|
43
|
+
: `Its feedback was already processed or approved: its processing mark is unchanged.`);
|
|
44
|
+
}
|
|
45
|
+
lines.push("");
|
|
28
46
|
if (analysis.files.length === 0) {
|
|
29
47
|
lines.push("Changed files: none — the diff is empty. Check the mode and refs; delete this analysis with delete_analysis and create it again with the right ones.");
|
|
30
48
|
}
|
|
@@ -1,4 +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
|
+
* Supprime une analyse et son snapshot de diff. Sa revue de suivi, si elle en a
|
|
5
|
+
* une, est conservée et devient une première revue.
|
|
6
|
+
*/
|
|
4
7
|
export declare function deleteAnalysis(store: AnalysisStore, args: Args): MutationResult;
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import { detachFollowUp } from "../core/follow-ups.js";
|
|
1
2
|
import { rejectUnknownFields, requireString, textResult } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Supprime une analyse et son snapshot de diff. Sa revue de suivi, si elle en a
|
|
5
|
+
* une, est conservée et devient une première revue.
|
|
6
|
+
*/
|
|
3
7
|
export function deleteAnalysis(store, args) {
|
|
4
8
|
rejectUnknownFields(args, ["analysis_id"]);
|
|
5
9
|
const id = requireString(args, "analysis_id");
|
|
10
|
+
store.get(id);
|
|
11
|
+
const detached = detachFollowUp(store, id);
|
|
6
12
|
store.delete(id);
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
13
|
+
const lines = [`Deleted analysis ${id} and its diff snapshot. The repository is untouched.`];
|
|
14
|
+
if (detached !== null)
|
|
15
|
+
lines.push(`Its follow-up review ${detached} is kept and no longer refers to it.`);
|
|
16
|
+
return { result: textResult(lines.join("\n")), analysisId: id };
|
|
11
17
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Analysis, type Explanation, type FileEntry, type Finding, type Note, type Overview, type ReviewProgress, type ReviewProgressState, type Severity } from "../../shared/schemas/analysis.schema.js";
|
|
2
|
+
import type { AnalysisLinks } from "../../shared/follow-ups.js";
|
|
2
3
|
/** Sha abrégé à 7 caractères. */
|
|
3
4
|
export declare function shortSha(sha: string | null): string;
|
|
4
5
|
/** Refs d'une analyse : `branch master...HEAD` ou `working_tree (changes against HEAD)`. */
|
|
@@ -29,5 +30,10 @@ export declare function progressStateLabel(state: ReviewProgressState): string;
|
|
|
29
30
|
* ou `submitted, decision approve, nothing to process (7/7 files reviewed)`.
|
|
30
31
|
*/
|
|
31
32
|
export declare function progressLabel(progress: ReviewProgress): string;
|
|
33
|
+
/**
|
|
34
|
+
* Lignes de suivi d'une analyse : analyse suivie et revue de suivi, avec le rang
|
|
35
|
+
* dans la chaîne. Aucune ligne pour une analyse sans lien.
|
|
36
|
+
*/
|
|
37
|
+
export declare function followUpLines(links: Pick<AnalysisLinks, "previous" | "next" | "chain">, id: string): string[];
|
|
32
38
|
/** Bloc « Overview » : objectif, approche et points d'attention ; une ligne d'invitation quand il n'y en a pas. */
|
|
33
39
|
export declare function overviewLines(overview: Overview | null): string[];
|
package/dist/src/tools/format.js
CHANGED
|
@@ -87,6 +87,21 @@ export function progressLabel(progress) {
|
|
|
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
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Lignes de suivi d'une analyse : analyse suivie et revue de suivi, avec le rang
|
|
92
|
+
* dans la chaîne. Aucune ligne pour une analyse sans lien.
|
|
93
|
+
*/
|
|
94
|
+
export function followUpLines(links, id) {
|
|
95
|
+
const lines = [];
|
|
96
|
+
const round = links.chain.findIndex((link) => link.id === id) + 1;
|
|
97
|
+
if (links.previous) {
|
|
98
|
+
lines.push(`Follow-up of: "${links.previous.title}" (${links.previous.id}) — round ${round} of ${links.chain.length}.`);
|
|
99
|
+
}
|
|
100
|
+
if (links.next) {
|
|
101
|
+
lines.push(`Followed by: "${links.next.title}" (${links.next.id}), review ${progressLabel(links.next.progress)}.`);
|
|
102
|
+
}
|
|
103
|
+
return lines;
|
|
104
|
+
}
|
|
90
105
|
/** Bloc « Overview » : objectif, approche et points d'attention ; une ligne d'invitation quand il n'y en a pas. */
|
|
91
106
|
export function overviewLines(overview) {
|
|
92
107
|
if (overview === null)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { summarize } from "../core/analysis-store.js";
|
|
2
|
+
import { analysisLinks } from "../core/follow-ups.js";
|
|
2
3
|
import { diagramQuality } from "../../shared/diagram-quality.js";
|
|
3
4
|
import { reviewProgress } from "../../shared/review-state.js";
|
|
4
5
|
import { sortBySeverity } from "../../shared/severity.js";
|
|
5
|
-
import { fileLine, explanationLine, fileTotals, findingLine, noteLocationLabel, overviewLines, progressLabel, severityCountsLabel, snapshotLabel, } from "./format.js";
|
|
6
|
+
import { fileLine, explanationLine, fileTotals, findingLine, followUpLines, noteLocationLabel, overviewLines, progressLabel, severityCountsLabel, snapshotLabel, } from "./format.js";
|
|
6
7
|
import { rejectUnknownFields, requireString, textResult } from "./types.js";
|
|
7
8
|
function noteLine(note) {
|
|
8
9
|
return `${note.id} — ${noteLocationLabel(note)} — ${note.text.replace(/\n/g, " / ")}`;
|
|
@@ -48,6 +49,7 @@ export function getAnalysis(store, args) {
|
|
|
48
49
|
`Repository: ${analysis.repoPath}`,
|
|
49
50
|
`Diff: ${snapshotLabel(analysis)}`,
|
|
50
51
|
`Revision ${analysis.revision}, last edited by ${analysis.lastEditor} at ${analysis.updatedAt}`,
|
|
52
|
+
...followUpLines(analysisLinks(store, analysis.id), analysis.id),
|
|
51
53
|
"",
|
|
52
54
|
];
|
|
53
55
|
if (analysis.request) {
|