@imenam/mcp-feature-analyzer 1.0.3 → 1.0.4
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 +1 -1
- package/dist/shared/schemas/analysis.schema.d.ts +61 -0
- package/dist/shared/schemas/analysis.schema.js +26 -0
- package/dist/shared/schemas/api.schema.d.ts +13 -0
- package/dist/shared/schemas/api.schema.js +8 -0
- package/dist/shared/schemas/ipc.schema.d.ts +1 -1
- package/dist/shared/schemas/ipc.schema.js +1 -0
- package/dist/src/core/analysis-store.js +1 -0
- package/dist/src/core/ids.d.ts +1 -0
- package/dist/src/core/ids.js +1 -0
- package/dist/src/core/invariants.d.ts +3 -1
- package/dist/src/core/invariants.js +27 -1
- package/dist/src/core/locations.d.ts +22 -4
- package/dist/src/core/locations.js +88 -4
- package/dist/src/core/migrate.d.ts +2 -0
- package/dist/src/core/migrate.js +3 -1
- package/dist/src/core/refresh.d.ts +8 -2
- package/dist/src/core/refresh.js +19 -1
- package/dist/src/core/review-actions.d.ts +6 -1
- package/dist/src/core/review-actions.js +20 -0
- package/dist/src/gui-worker.js +7 -1
- package/dist/src/index.js +100 -9
- package/dist/src/tools/add-explanations.d.ts +9 -0
- package/dist/src/tools/add-explanations.js +73 -0
- package/dist/src/tools/create-analysis.js +1 -1
- package/dist/src/tools/delete-explanations.d.ts +4 -0
- package/dist/src/tools/delete-explanations.js +23 -0
- package/dist/src/tools/format.d.ts +3 -1
- package/dist/src/tools/format.js +5 -0
- package/dist/src/tools/get-analysis.js +6 -1
- package/dist/src/tools/get-diff.js +2 -1
- package/dist/src/tools/inputs.d.ts +14 -0
- package/dist/src/tools/inputs.js +20 -2
- package/dist/src/tools/refresh-analysis.js +8 -2
- package/dist/src/tools/update-explanation.d.ts +7 -0
- package/dist/src/tools/update-explanation.js +57 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/gui/dist/assets/index-BLyQW-EW.js +72 -0
- package/gui/dist/assets/index-CmSZOGwc.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/gui/dist/assets/index-Db7Hu3tM.js +0 -72
- package/gui/dist/assets/index-Du5eL78F.css +0 -1
package/README.md
CHANGED
|
@@ -249,7 +249,7 @@ L'ajustement à la fenêtre ne descend jamais sous l'échelle **0,85** : en dess
|
|
|
249
249
|
- « Code corrigé » : le code tel qu'il se lirait avec les correctifs appliqués. Les lignes visées disparaissent ; les lignes proposées prennent leur place, sur fond violet avec la marque « › » et sans numéro, sous un bandeau « Correctif N appliqué — remplace les lignes X à Y ». Un lien `?line=N` vers une ligne retirée met ce bandeau en évidence ; les remarques ne se posent que sur les lignes du diff, pas sur les lignes proposées.
|
|
250
250
|
|
|
251
251
|
Dans les deux derniers modes, l'interrupteur de chaque carte (« Afficher le correctif dans le code » ou « Appliquer le correctif dans le code ») choisit les correctifs dessinés.
|
|
252
|
-
- **Colonne « Constats de ce fichier »** — l'en-tête compte les constats ouverts, et le total quand il diffère (« 4 ouverts · 5 au total »). Dessous, un index compact des constats (numéro à la couleur de la gravité et première ligne, ignorés et obsolètes atténués) : un clic fait défiler jusqu'aux lignes et à la carte, mises en évidence comme avec `?line`. Tant que des cartes de constat sont sous la partie visible, une indication collée en bas de la colonne les compte (« 3 constats plus bas ↓ ») ; un clic amène la suivante. Les constats, numérotés dans l'ordre de leur première ligne, sont des cartes alignées sur leur ligne et reliées au diff ; des cartes voisines s'empilent sans se chevaucher et défilent avec le diff. Chaque carte montre numéro, gravité, lignes, titre, explication, l'interrupteur « Afficher le correctif dans le code » (constat avec correctif) et les boutons « Copier le prompt », « Ignorer » / « Rouvrir », « Remarque ». Les constats ignorés ou obsolètes restent visibles, atténués. Les remarques de ligne sont des cartes alignées de la même façon, datées au format court (date complète en infobulle) ; les remarques sur le fichier entier et ce qui vise des lignes hors du diff affiché sont en tête de colonne. Sous 1200 px de large, la colonne passe sous le diff et ses cartes s'empilent.
|
|
252
|
+
- **Colonne « Constats de ce fichier »** — l'en-tête compte les constats ouverts, et le total quand il diffère (« 4 ouverts · 5 au total »). Dessous, un index compact des constats (numéro à la couleur de la gravité et première ligne, ignorés et obsolètes atténués) : un clic fait défiler jusqu'aux lignes et à la carte, mises en évidence comme avec `?line`. Tant que des cartes de constat sont sous la partie visible, une indication collée en bas de la colonne les compte (« 3 constats plus bas ↓ ») ; un clic amène la suivante. Les constats, numérotés dans l'ordre de leur première ligne, sont des cartes alignées sur leur ligne et reliées au diff ; des cartes voisines s'empilent sans se chevaucher et défilent avec le diff. Chaque carte montre numéro, gravité, lignes, titre, explication, l'interrupteur « Afficher le correctif dans le code » (constat avec correctif) et les boutons « Copier le prompt », « Ignorer » / « Rouvrir », « Remarque ». Les constats ignorés ou obsolètes restent visibles, atténués. Les remarques de ligne sont des cartes alignées de la même façon, datées au format court (date complète en infobulle), avec « Modifier », qui rouvre le texte sur place dans la même saisie que la création, et « Supprimer » ; modifier une remarque retenue dans une revue déjà soumise recalcule le prompt enregistré ; les remarques sur le fichier entier et ce qui vise des lignes hors du diff affiché sont en tête de colonne. Sous 1200 px de large, la colonne passe sous le diff et ses cartes s'empilent.
|
|
253
253
|
|
|
254
254
|
Un fichier binaire, supprimé ou de plus de 1 Mo n'a pas de contenu conservé : on ne peut pas y ancrer de constat ni de remarque de ligne, seulement une remarque sur le fichier entier.
|
|
255
255
|
|
|
@@ -4,6 +4,8 @@ export declare const FILE_STATUSES: readonly ["added", "modified", "deleted", "r
|
|
|
4
4
|
export declare const SEVERITIES: readonly ["critical", "major", "minor", "trivial"];
|
|
5
5
|
export declare const FINDING_KINDS: readonly ["issue", "requirement_gap"];
|
|
6
6
|
export declare const FINDING_STATUSES: readonly ["open", "ignored", "outdated"];
|
|
7
|
+
export declare const EXPLANATION_SIDES: readonly ["new", "old"];
|
|
8
|
+
export declare const EXPLANATION_STATUSES: readonly ["current", "outdated"];
|
|
7
9
|
export declare const DIAGRAM_KINDS: readonly ["flow", "layers", "mindmap"];
|
|
8
10
|
export declare const NODE_SHAPES: readonly ["box", "pill", "decision"];
|
|
9
11
|
export declare const NODE_STATUSES: readonly ["new", "modified", "impacted", "existing", "finding", "missing"];
|
|
@@ -66,6 +68,41 @@ export declare const FindingSchema: z.ZodObject<{
|
|
|
66
68
|
}>;
|
|
67
69
|
createdAt: z.ZodString;
|
|
68
70
|
}, z.core.$strip>;
|
|
71
|
+
/**
|
|
72
|
+
* Plage de lignes décrite par une explication, bornes incluses : côté `new` (numéros
|
|
73
|
+
* du côté « nouveau », code ajouté ou conservé) ou côté `old` (numéros du côté
|
|
74
|
+
* « ancien », code supprimé).
|
|
75
|
+
*/
|
|
76
|
+
export declare const ExplanationLocationSchema: z.ZodObject<{
|
|
77
|
+
path: z.ZodString;
|
|
78
|
+
side: z.ZodEnum<{
|
|
79
|
+
new: "new";
|
|
80
|
+
old: "old";
|
|
81
|
+
}>;
|
|
82
|
+
startLine: z.ZodNumber;
|
|
83
|
+
endLine: z.ZodNumber;
|
|
84
|
+
}, z.core.$strip>;
|
|
85
|
+
/** Description, par l'agent, de ce que fait un bloc de code et de la façon dont il le fait. */
|
|
86
|
+
export declare const ExplanationSchema: z.ZodObject<{
|
|
87
|
+
id: z.ZodString;
|
|
88
|
+
title: z.ZodString;
|
|
89
|
+
body: z.ZodString;
|
|
90
|
+
location: z.ZodObject<{
|
|
91
|
+
path: z.ZodString;
|
|
92
|
+
side: z.ZodEnum<{
|
|
93
|
+
new: "new";
|
|
94
|
+
old: "old";
|
|
95
|
+
}>;
|
|
96
|
+
startLine: z.ZodNumber;
|
|
97
|
+
endLine: z.ZodNumber;
|
|
98
|
+
}, z.core.$strip>;
|
|
99
|
+
anchorText: z.ZodString;
|
|
100
|
+
status: z.ZodEnum<{
|
|
101
|
+
current: "current";
|
|
102
|
+
outdated: "outdated";
|
|
103
|
+
}>;
|
|
104
|
+
createdAt: z.ZodString;
|
|
105
|
+
}, z.core.$strip>;
|
|
69
106
|
/** Emplacement d'une remarque : une ligne du côté « nouveau », ou le fichier entier (`line` null). */
|
|
70
107
|
export declare const NoteLocationSchema: z.ZodObject<{
|
|
71
108
|
path: z.ZodString;
|
|
@@ -247,6 +284,26 @@ export declare const AnalysisSchema: z.ZodObject<{
|
|
|
247
284
|
}>;
|
|
248
285
|
createdAt: z.ZodString;
|
|
249
286
|
}, z.core.$strip>>;
|
|
287
|
+
explanations: z.ZodArray<z.ZodObject<{
|
|
288
|
+
id: z.ZodString;
|
|
289
|
+
title: z.ZodString;
|
|
290
|
+
body: z.ZodString;
|
|
291
|
+
location: z.ZodObject<{
|
|
292
|
+
path: z.ZodString;
|
|
293
|
+
side: z.ZodEnum<{
|
|
294
|
+
new: "new";
|
|
295
|
+
old: "old";
|
|
296
|
+
}>;
|
|
297
|
+
startLine: z.ZodNumber;
|
|
298
|
+
endLine: z.ZodNumber;
|
|
299
|
+
}, z.core.$strip>;
|
|
300
|
+
anchorText: z.ZodString;
|
|
301
|
+
status: z.ZodEnum<{
|
|
302
|
+
current: "current";
|
|
303
|
+
outdated: "outdated";
|
|
304
|
+
}>;
|
|
305
|
+
createdAt: z.ZodString;
|
|
306
|
+
}, z.core.$strip>>;
|
|
250
307
|
notes: z.ZodArray<z.ZodObject<{
|
|
251
308
|
id: z.ZodString;
|
|
252
309
|
location: z.ZodNullable<z.ZodObject<{
|
|
@@ -323,6 +380,8 @@ export type FileStatus = (typeof FILE_STATUSES)[number];
|
|
|
323
380
|
export type Severity = (typeof SEVERITIES)[number];
|
|
324
381
|
export type FindingKind = (typeof FINDING_KINDS)[number];
|
|
325
382
|
export type FindingStatus = (typeof FINDING_STATUSES)[number];
|
|
383
|
+
export type ExplanationSide = (typeof EXPLANATION_SIDES)[number];
|
|
384
|
+
export type ExplanationStatus = (typeof EXPLANATION_STATUSES)[number];
|
|
326
385
|
export type DiagramKind = (typeof DIAGRAM_KINDS)[number];
|
|
327
386
|
export type NodeShape = (typeof NODE_SHAPES)[number];
|
|
328
387
|
export type NodeStatus = (typeof NODE_STATUSES)[number];
|
|
@@ -332,6 +391,8 @@ export type Editor = (typeof EDITORS)[number];
|
|
|
332
391
|
export type FileEntry = z.infer<typeof FileEntrySchema>;
|
|
333
392
|
export type FindingLocation = z.infer<typeof FindingLocationSchema>;
|
|
334
393
|
export type Finding = z.infer<typeof FindingSchema>;
|
|
394
|
+
export type ExplanationLocation = z.infer<typeof ExplanationLocationSchema>;
|
|
395
|
+
export type Explanation = z.infer<typeof ExplanationSchema>;
|
|
335
396
|
export type NoteLocation = z.infer<typeof NoteLocationSchema>;
|
|
336
397
|
export type Note = z.infer<typeof NoteSchema>;
|
|
337
398
|
export type DiagramNodeLocation = z.infer<typeof DiagramNodeLocationSchema>;
|
|
@@ -4,6 +4,8 @@ export const FILE_STATUSES = ["added", "modified", "deleted", "renamed"];
|
|
|
4
4
|
export const SEVERITIES = ["critical", "major", "minor", "trivial"];
|
|
5
5
|
export const FINDING_KINDS = ["issue", "requirement_gap"];
|
|
6
6
|
export const FINDING_STATUSES = ["open", "ignored", "outdated"];
|
|
7
|
+
export const EXPLANATION_SIDES = ["new", "old"];
|
|
8
|
+
export const EXPLANATION_STATUSES = ["current", "outdated"];
|
|
7
9
|
export const DIAGRAM_KINDS = ["flow", "layers", "mindmap"];
|
|
8
10
|
export const NODE_SHAPES = ["box", "pill", "decision"];
|
|
9
11
|
export const NODE_STATUSES = ["new", "modified", "impacted", "existing", "finding", "missing"];
|
|
@@ -55,6 +57,29 @@ export const FindingSchema = z.object({
|
|
|
55
57
|
status: z.enum(FINDING_STATUSES),
|
|
56
58
|
createdAt: IsoDateSchema,
|
|
57
59
|
});
|
|
60
|
+
/**
|
|
61
|
+
* Plage de lignes décrite par une explication, bornes incluses : côté `new` (numéros
|
|
62
|
+
* du côté « nouveau », code ajouté ou conservé) ou côté `old` (numéros du côté
|
|
63
|
+
* « ancien », code supprimé).
|
|
64
|
+
*/
|
|
65
|
+
export const ExplanationLocationSchema = z.object({
|
|
66
|
+
path: z.string().min(1),
|
|
67
|
+
side: z.enum(EXPLANATION_SIDES),
|
|
68
|
+
startLine: LineSchema,
|
|
69
|
+
endLine: LineSchema,
|
|
70
|
+
});
|
|
71
|
+
/** Description, par l'agent, de ce que fait un bloc de code et de la façon dont il le fait. */
|
|
72
|
+
export const ExplanationSchema = z.object({
|
|
73
|
+
id: ItemIdSchema,
|
|
74
|
+
title: z.string().min(1),
|
|
75
|
+
body: z.string().min(1),
|
|
76
|
+
location: ExplanationLocationSchema,
|
|
77
|
+
/** Texte exact des lignes décrites au moment où l'explication a été ancrée. */
|
|
78
|
+
anchorText: z.string(),
|
|
79
|
+
/** `outdated` quand les lignes décrites ont changé depuis, au recalcul du snapshot. */
|
|
80
|
+
status: z.enum(EXPLANATION_STATUSES),
|
|
81
|
+
createdAt: IsoDateSchema,
|
|
82
|
+
});
|
|
58
83
|
/** Emplacement d'une remarque : une ligne du côté « nouveau », ou le fichier entier (`line` null). */
|
|
59
84
|
export const NoteLocationSchema = z.object({
|
|
60
85
|
path: z.string().min(1),
|
|
@@ -140,6 +165,7 @@ export const AnalysisSchema = z.object({
|
|
|
140
165
|
summary: z.array(z.string().min(1)),
|
|
141
166
|
files: z.array(FileEntrySchema),
|
|
142
167
|
findings: z.array(FindingSchema),
|
|
168
|
+
explanations: z.array(ExplanationSchema),
|
|
143
169
|
notes: z.array(NoteSchema),
|
|
144
170
|
diagrams: z.array(DiagramSchema),
|
|
145
171
|
review: ReviewSchema,
|
|
@@ -19,6 +19,10 @@ export declare const AddNoteBodySchema: z.ZodObject<{
|
|
|
19
19
|
text: z.ZodString;
|
|
20
20
|
baseRevision: z.ZodNumber;
|
|
21
21
|
}, z.core.$strict>;
|
|
22
|
+
export declare const UpdateNoteBodySchema: z.ZodObject<{
|
|
23
|
+
text: z.ZodString;
|
|
24
|
+
baseRevision: z.ZodNumber;
|
|
25
|
+
}, z.core.$strict>;
|
|
22
26
|
export declare const DeleteNoteBodySchema: z.ZodObject<{
|
|
23
27
|
baseRevision: z.ZodNumber;
|
|
24
28
|
}, z.core.$strict>;
|
|
@@ -35,6 +39,7 @@ export declare const SubmitReviewBodySchema: z.ZodObject<{
|
|
|
35
39
|
export type SetFileReviewedBody = z.infer<typeof SetFileReviewedBodySchema>;
|
|
36
40
|
export type SetFindingStatusBody = z.infer<typeof SetFindingStatusBodySchema>;
|
|
37
41
|
export type AddNoteBody = z.infer<typeof AddNoteBodySchema>;
|
|
42
|
+
export type UpdateNoteBody = z.infer<typeof UpdateNoteBodySchema>;
|
|
38
43
|
export type DeleteNoteBody = z.infer<typeof DeleteNoteBodySchema>;
|
|
39
44
|
export type SubmitReviewBody = z.infer<typeof SubmitReviewBodySchema>;
|
|
40
45
|
/** Données IPC des requêtes de la GUI qui visent une analyse. */
|
|
@@ -70,6 +75,14 @@ export declare const AddNoteRequestSchema: z.ZodObject<{
|
|
|
70
75
|
baseRevision: z.ZodNumber;
|
|
71
76
|
}, z.core.$strict>;
|
|
72
77
|
}, z.core.$strict>;
|
|
78
|
+
export declare const UpdateNoteRequestSchema: z.ZodObject<{
|
|
79
|
+
analysisId: z.ZodString;
|
|
80
|
+
noteId: z.ZodString;
|
|
81
|
+
body: z.ZodObject<{
|
|
82
|
+
text: z.ZodString;
|
|
83
|
+
baseRevision: z.ZodNumber;
|
|
84
|
+
}, z.core.$strict>;
|
|
85
|
+
}, z.core.$strict>;
|
|
73
86
|
export declare const DeleteNoteRequestSchema: z.ZodObject<{
|
|
74
87
|
analysisId: z.ZodString;
|
|
75
88
|
noteId: z.ZodString;
|
|
@@ -22,6 +22,10 @@ export const AddNoteBodySchema = z.strictObject({
|
|
|
22
22
|
text: z.string().trim().min(1, "the note text is empty"),
|
|
23
23
|
baseRevision: BaseRevisionSchema,
|
|
24
24
|
});
|
|
25
|
+
export const UpdateNoteBodySchema = z.strictObject({
|
|
26
|
+
text: z.string().trim().min(1, "the note text is empty"),
|
|
27
|
+
baseRevision: BaseRevisionSchema,
|
|
28
|
+
});
|
|
25
29
|
export const DeleteNoteBodySchema = z.strictObject({
|
|
26
30
|
baseRevision: BaseRevisionSchema,
|
|
27
31
|
});
|
|
@@ -39,6 +43,10 @@ export const SetFindingStatusRequestSchema = AnalysisRequestSchema.extend({
|
|
|
39
43
|
body: SetFindingStatusBodySchema,
|
|
40
44
|
});
|
|
41
45
|
export const AddNoteRequestSchema = AnalysisRequestSchema.extend({ body: AddNoteBodySchema });
|
|
46
|
+
export const UpdateNoteRequestSchema = AnalysisRequestSchema.extend({
|
|
47
|
+
noteId: z.string().min(1),
|
|
48
|
+
body: UpdateNoteBodySchema,
|
|
49
|
+
});
|
|
42
50
|
export const DeleteNoteRequestSchema = AnalysisRequestSchema.extend({
|
|
43
51
|
noteId: z.string().min(1),
|
|
44
52
|
body: DeleteNoteBodySchema,
|
|
@@ -25,5 +25,5 @@ export type IPCMessage = z.infer<typeof IPCMessageSchema>;
|
|
|
25
25
|
export declare const PUSHED_EVENT_TYPES: readonly ["INITIAL_STATE", "ANALYSES_UPDATED", "ANALYSIS_UPDATED"];
|
|
26
26
|
export type PushedEventType = (typeof PUSHED_EVENT_TYPES)[number];
|
|
27
27
|
/** Requêtes du GUI worker au maître ; chacune reçoit une réponse de même `correlationId`. */
|
|
28
|
-
export declare const GUI_REQUEST_TYPES: readonly ["GET_CONFIG", "LIST_ANALYSES", "GET_ANALYSIS", "GET_DIFF", "SET_FILE_REVIEWED", "SET_FINDING_STATUS", "ADD_NOTE", "DELETE_NOTE", "SUBMIT_REVIEW"];
|
|
28
|
+
export declare const GUI_REQUEST_TYPES: readonly ["GET_CONFIG", "LIST_ANALYSES", "GET_ANALYSIS", "GET_DIFF", "SET_FILE_REVIEWED", "SET_FINDING_STATUS", "ADD_NOTE", "UPDATE_NOTE", "DELETE_NOTE", "SUBMIT_REVIEW"];
|
|
29
29
|
export type GuiRequestType = (typeof GUI_REQUEST_TYPES)[number];
|
package/dist/src/core/ids.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export declare function analysisId(title: string): string;
|
|
|
7
7
|
/** Préfixe des identifiants générés, par type d'élément. */
|
|
8
8
|
export declare const ITEM_ID_PREFIXES: {
|
|
9
9
|
readonly finding: "f";
|
|
10
|
+
readonly explanation: "x";
|
|
10
11
|
readonly note: "n";
|
|
11
12
|
readonly diagram: "d";
|
|
12
13
|
readonly node: "nd";
|
package/dist/src/core/ids.js
CHANGED
|
@@ -3,12 +3,14 @@ import type { Analysis } from "../../shared/schemas/analysis.schema.js";
|
|
|
3
3
|
* Liste les violations des règles de cohérence d'une analyse :
|
|
4
4
|
* - refs cohérentes avec le mode : `branch` a une ref et un commit de tête ; `working_tree`
|
|
5
5
|
* compare à HEAD, sans ref ni commit de tête ;
|
|
6
|
-
* - identifiants uniques (fichiers, constats, remarques, schémas, nœuds d'un schéma) ;
|
|
6
|
+
* - identifiants uniques (fichiers, constats, explications, remarques, schémas, nœuds d'un schéma) ;
|
|
7
7
|
* - un constat `issue` a un emplacement, un `requirement_gap` peut ne pas en avoir ;
|
|
8
8
|
* - un emplacement de constat porte un `anchorText` ; sans emplacement, ni `anchorText` ni `suggestion` ;
|
|
9
9
|
* - un constat `open` vise un fichier de l'analyse dont le contenu est disponible, avec
|
|
10
10
|
* 1 ≤ startLine ≤ endLine ≤ nombre de lignes ; un constat `ignored` ou `outdated` garde
|
|
11
11
|
* l'emplacement qu'il avait, même si le snapshot a changé depuis ;
|
|
12
|
+
* - une explication `current` vise un fichier de l'analyse ; côté `new`, son contenu est
|
|
13
|
+
* disponible et endLine ≤ nombre de lignes ;
|
|
12
14
|
* - les liens d'un schéma relient des nœuds existants ; en `layers`, chaque nœud a une couche
|
|
13
15
|
* déclarée dans `layers`, sans doublon ;
|
|
14
16
|
* - la revue ne sélectionne que des constats et remarques existants, et son état est cohérent.
|
|
@@ -14,12 +14,14 @@ function duplicates(ids) {
|
|
|
14
14
|
* Liste les violations des règles de cohérence d'une analyse :
|
|
15
15
|
* - refs cohérentes avec le mode : `branch` a une ref et un commit de tête ; `working_tree`
|
|
16
16
|
* compare à HEAD, sans ref ni commit de tête ;
|
|
17
|
-
* - identifiants uniques (fichiers, constats, remarques, schémas, nœuds d'un schéma) ;
|
|
17
|
+
* - identifiants uniques (fichiers, constats, explications, remarques, schémas, nœuds d'un schéma) ;
|
|
18
18
|
* - un constat `issue` a un emplacement, un `requirement_gap` peut ne pas en avoir ;
|
|
19
19
|
* - un emplacement de constat porte un `anchorText` ; sans emplacement, ni `anchorText` ni `suggestion` ;
|
|
20
20
|
* - un constat `open` vise un fichier de l'analyse dont le contenu est disponible, avec
|
|
21
21
|
* 1 ≤ startLine ≤ endLine ≤ nombre de lignes ; un constat `ignored` ou `outdated` garde
|
|
22
22
|
* l'emplacement qu'il avait, même si le snapshot a changé depuis ;
|
|
23
|
+
* - une explication `current` vise un fichier de l'analyse ; côté `new`, son contenu est
|
|
24
|
+
* disponible et endLine ≤ nombre de lignes ;
|
|
23
25
|
* - les liens d'un schéma relient des nœuds existants ; en `layers`, chaque nœud a une couche
|
|
24
26
|
* déclarée dans `layers`, sans doublon ;
|
|
25
27
|
* - la revue ne sélectionne que des constats et remarques existants, et son état est cohérent.
|
|
@@ -36,6 +38,7 @@ export function findInvariantViolations(analysis) {
|
|
|
36
38
|
for (const [label, ids] of [
|
|
37
39
|
["file path", analysis.files.map((file) => file.path)],
|
|
38
40
|
["finding id", analysis.findings.map((finding) => finding.id)],
|
|
41
|
+
["explanation id", analysis.explanations.map((explanation) => explanation.id)],
|
|
39
42
|
["note id", analysis.notes.map((note) => note.id)],
|
|
40
43
|
["diagram id", analysis.diagrams.map((diagram) => diagram.id)],
|
|
41
44
|
]) {
|
|
@@ -75,6 +78,29 @@ export function findInvariantViolations(analysis) {
|
|
|
75
78
|
`which has ${file.lineCount} line(s)`);
|
|
76
79
|
}
|
|
77
80
|
}
|
|
81
|
+
for (const explanation of analysis.explanations) {
|
|
82
|
+
const where = `explanation "${explanation.id}"`;
|
|
83
|
+
const location = explanation.location;
|
|
84
|
+
if (location.startLine > location.endLine) {
|
|
85
|
+
problems.push(`${where} has startLine ${location.startLine} after endLine ${location.endLine}`);
|
|
86
|
+
}
|
|
87
|
+
if (explanation.status !== "current")
|
|
88
|
+
continue;
|
|
89
|
+
const file = files.get(location.path);
|
|
90
|
+
if (!file) {
|
|
91
|
+
problems.push(`${where} describes "${location.path}", which is not a changed file of this analysis`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (location.side !== "new")
|
|
95
|
+
continue;
|
|
96
|
+
if (!file.contentAvailable) {
|
|
97
|
+
problems.push(`${where} describes new-side lines of "${location.path}", whose content is not available`);
|
|
98
|
+
}
|
|
99
|
+
else if (location.endLine > file.lineCount) {
|
|
100
|
+
problems.push(`${where} describes lines ${location.startLine}-${location.endLine} of "${location.path}", ` +
|
|
101
|
+
`which has ${file.lineCount} line(s)`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
78
104
|
for (const diagram of analysis.diagrams) {
|
|
79
105
|
const where = `diagram "${diagram.id}"`;
|
|
80
106
|
const nodeIds = new Set(diagram.nodes.map((node) => node.id));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { FileEntry } from "../../shared/schemas/analysis.schema.js";
|
|
2
|
-
import type { DiffSnapshot } from "../../shared/schemas/diff.schema.js";
|
|
1
|
+
import type { ExplanationLocation, ExplanationSide, FileEntry } from "../../shared/schemas/analysis.schema.js";
|
|
2
|
+
import type { DiffSnapshot, FileDiff } from "../../shared/schemas/diff.schema.js";
|
|
3
3
|
export interface LocationRequest {
|
|
4
4
|
path: string;
|
|
5
5
|
startLine: number;
|
|
@@ -17,9 +17,27 @@ export interface ResolvedLocation {
|
|
|
17
17
|
* Vérifie un emplacement (lignes du côté « nouveau ») contre le snapshot et
|
|
18
18
|
* renvoie le texte des lignes visées. Erreur explicite si le fichier n'est pas
|
|
19
19
|
* dans l'analyse, si son contenu n'est pas disponible ou si les lignes sortent
|
|
20
|
-
* du fichier.
|
|
20
|
+
* du fichier ; `unavailableHint` complète le message d'un contenu indisponible.
|
|
21
21
|
*/
|
|
22
|
-
export declare function resolveLocation(snapshot: DiffSnapshot, files: FileEntry[], request: LocationRequest): ResolvedLocation;
|
|
22
|
+
export declare function resolveLocation(snapshot: DiffSnapshot, files: FileEntry[], request: LocationRequest, unavailableHint?: string): ResolvedLocation;
|
|
23
|
+
/**
|
|
24
|
+
* Texte actuel des lignes décrites par une explication, ou null quand elles ne
|
|
25
|
+
* sont plus lisibles : côté `new`, dans le contenu conservé du fichier ; côté
|
|
26
|
+
* `old`, dans les lignes du côté « ancien » que montre le diff.
|
|
27
|
+
*/
|
|
28
|
+
export declare function explanationText(diff: FileDiff | undefined, location: ExplanationLocation): string | null;
|
|
29
|
+
export interface ExplanationLocationRequest extends LocationRequest {
|
|
30
|
+
side: ExplanationSide;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Vérifie l'emplacement d'une explication et renvoie le texte des lignes décrites.
|
|
34
|
+
* - côté `new` : mêmes règles qu'un constat ;
|
|
35
|
+
* - côté `old` : toutes les lignes doivent figurer dans le diff (numéros de la
|
|
36
|
+
* première colonne de get_diff), et au moins une doit être supprimée.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveExplanationLocation(snapshot: DiffSnapshot, files: FileEntry[], request: ExplanationLocationRequest): ExplanationLocation & {
|
|
39
|
+
anchorText: string;
|
|
40
|
+
};
|
|
23
41
|
/**
|
|
24
42
|
* Vérifie l'emplacement d'une remarque : un fichier de l'analyse, et une ligne
|
|
25
43
|
* du côté « nouveau » quand `line` est fournie.
|
|
@@ -14,13 +14,15 @@ function unavailableReason(file) {
|
|
|
14
14
|
return "it is binary";
|
|
15
15
|
return "it is larger than the snapshot size limit";
|
|
16
16
|
}
|
|
17
|
+
/** Indication donnée à un constat dont le fichier n'a pas de contenu conservé. */
|
|
18
|
+
const FINDING_UNAVAILABLE_HINT = "Anchor the point on lines of another changed file, or omit the location (allowed for requirement_gap findings).";
|
|
17
19
|
/**
|
|
18
20
|
* Vérifie un emplacement (lignes du côté « nouveau ») contre le snapshot et
|
|
19
21
|
* renvoie le texte des lignes visées. Erreur explicite si le fichier n'est pas
|
|
20
22
|
* dans l'analyse, si son contenu n'est pas disponible ou si les lignes sortent
|
|
21
|
-
* du fichier.
|
|
23
|
+
* du fichier ; `unavailableHint` complète le message d'un contenu indisponible.
|
|
22
24
|
*/
|
|
23
|
-
export function resolveLocation(snapshot, files, request) {
|
|
25
|
+
export function resolveLocation(snapshot, files, request, unavailableHint = FINDING_UNAVAILABLE_HINT) {
|
|
24
26
|
const endLine = request.endLine ?? request.startLine;
|
|
25
27
|
const { path, startLine } = request;
|
|
26
28
|
const file = files.find((entry) => entry.path === path);
|
|
@@ -32,8 +34,7 @@ export function resolveLocation(snapshot, files, request) {
|
|
|
32
34
|
}
|
|
33
35
|
const diff = snapshot.files.find((entry) => entry.path === path);
|
|
34
36
|
if (!file.contentAvailable || !diff || diff.newContent === null) {
|
|
35
|
-
throw new AnalysisError(`Cannot anchor on lines of "${path}": its content is not available because ${unavailableReason(file)}. `
|
|
36
|
-
`Anchor the point on lines of another changed file, or omit the location (allowed for requirement_gap findings).`);
|
|
37
|
+
throw new AnalysisError(`Cannot anchor on lines of "${path}": its content is not available because ${unavailableReason(file)}. ${unavailableHint}`);
|
|
37
38
|
}
|
|
38
39
|
const lines = splitLines(diff.newContent);
|
|
39
40
|
if (endLine > lines.length) {
|
|
@@ -42,6 +43,89 @@ export function resolveLocation(snapshot, files, request) {
|
|
|
42
43
|
}
|
|
43
44
|
return { path, startLine, endLine, anchorText: lines.slice(startLine - 1, endLine).join("\n") };
|
|
44
45
|
}
|
|
46
|
+
/** Texte des lignes du côté « ancien » que montre le diff (lignes supprimées et contexte), par numéro. */
|
|
47
|
+
function oldSideLines(diff) {
|
|
48
|
+
const lines = new Map();
|
|
49
|
+
for (const hunk of diff.hunks) {
|
|
50
|
+
for (const line of hunk.lines) {
|
|
51
|
+
if (line.oldNo !== null)
|
|
52
|
+
lines.set(line.oldNo, { text: line.text, removed: line.type === "del" });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return lines;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Texte actuel des lignes décrites par une explication, ou null quand elles ne
|
|
59
|
+
* sont plus lisibles : côté `new`, dans le contenu conservé du fichier ; côté
|
|
60
|
+
* `old`, dans les lignes du côté « ancien » que montre le diff.
|
|
61
|
+
*/
|
|
62
|
+
export function explanationText(diff, location) {
|
|
63
|
+
if (!diff || location.startLine > location.endLine)
|
|
64
|
+
return null;
|
|
65
|
+
if (location.side === "new") {
|
|
66
|
+
if (diff.newContent === null)
|
|
67
|
+
return null;
|
|
68
|
+
const lines = splitLines(diff.newContent);
|
|
69
|
+
if (location.startLine < 1 || location.endLine > lines.length)
|
|
70
|
+
return null;
|
|
71
|
+
return lines.slice(location.startLine - 1, location.endLine).join("\n");
|
|
72
|
+
}
|
|
73
|
+
const old = oldSideLines(diff);
|
|
74
|
+
const texts = [];
|
|
75
|
+
for (let line = location.startLine; line <= location.endLine; line++) {
|
|
76
|
+
const entry = old.get(line);
|
|
77
|
+
if (!entry)
|
|
78
|
+
return null;
|
|
79
|
+
texts.push(entry.text);
|
|
80
|
+
}
|
|
81
|
+
return texts.join("\n");
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Vérifie l'emplacement d'une explication et renvoie le texte des lignes décrites.
|
|
85
|
+
* - côté `new` : mêmes règles qu'un constat ;
|
|
86
|
+
* - côté `old` : toutes les lignes doivent figurer dans le diff (numéros de la
|
|
87
|
+
* première colonne de get_diff), et au moins une doit être supprimée.
|
|
88
|
+
*/
|
|
89
|
+
export function resolveExplanationLocation(snapshot, files, request) {
|
|
90
|
+
if (request.side === "new") {
|
|
91
|
+
const resolved = resolveLocation(snapshot, files, request, `Explain its removed lines on the old side ("side": "old"), or explain lines of another changed file.`);
|
|
92
|
+
return { path: resolved.path, side: "new", startLine: resolved.startLine, endLine: resolved.endLine, anchorText: resolved.anchorText };
|
|
93
|
+
}
|
|
94
|
+
const endLine = request.endLine ?? request.startLine;
|
|
95
|
+
const { path, startLine } = request;
|
|
96
|
+
if (!files.some((entry) => entry.path === path)) {
|
|
97
|
+
throw new AnalysisError(`File "${path}" is not part of this analysis (${describeFiles(files)}).`);
|
|
98
|
+
}
|
|
99
|
+
if (!Number.isInteger(startLine) || !Number.isInteger(endLine) || startLine < 1 || endLine < startLine) {
|
|
100
|
+
throw new AnalysisError(`Invalid line range ${startLine}-${endLine} for "${path}": lines are 1-based integers and end_line must be >= start_line.`);
|
|
101
|
+
}
|
|
102
|
+
const diff = snapshot.files.find((entry) => entry.path === path);
|
|
103
|
+
const old = diff ? oldSideLines(diff) : new Map();
|
|
104
|
+
const missing = [];
|
|
105
|
+
let removed = false;
|
|
106
|
+
for (let line = startLine; line <= endLine; line++) {
|
|
107
|
+
const entry = old.get(line);
|
|
108
|
+
if (!entry)
|
|
109
|
+
missing.push(line);
|
|
110
|
+
else if (entry.removed)
|
|
111
|
+
removed = true;
|
|
112
|
+
}
|
|
113
|
+
if (missing.length > 0) {
|
|
114
|
+
throw new AnalysisError(`Old-side lines ${startLine}-${endLine} of "${path}" are not all in the diff (missing: ${lineList(missing)}). ` +
|
|
115
|
+
`Old-side numbers are those of the first column of get_diff; an old-side explanation covers removed lines and their context.`);
|
|
116
|
+
}
|
|
117
|
+
if (!removed) {
|
|
118
|
+
throw new AnalysisError(`Old-side lines ${startLine}-${endLine} of "${path}" contain no removed line. ` +
|
|
119
|
+
`The old side is for removed code: explain kept or added code on the new side ("side": "new").`);
|
|
120
|
+
}
|
|
121
|
+
const location = { path, side: "old", startLine, endLine };
|
|
122
|
+
return { ...location, anchorText: explanationText(diff, location) };
|
|
123
|
+
}
|
|
124
|
+
/** `3, 4, 9` ; au-delà de dix numéros, les dix premiers suivis de `…`. */
|
|
125
|
+
function lineList(lines) {
|
|
126
|
+
const shown = lines.slice(0, 10).join(", ");
|
|
127
|
+
return lines.length > 10 ? `${shown}, …` : shown;
|
|
128
|
+
}
|
|
45
129
|
/**
|
|
46
130
|
* Vérifie l'emplacement d'une remarque : un fichier de l'analyse, et une ligne
|
|
47
131
|
* du côté « nouveau » quand `line` est fournie.
|
|
@@ -12,6 +12,8 @@ export declare function repoFolderName(repoPath: string): string;
|
|
|
12
12
|
export declare const addProject: MigrationStep;
|
|
13
13
|
/** Ajoute `overview` à null : l'analyse n'a pas encore de vue d'ensemble. */
|
|
14
14
|
export declare const addOverview: MigrationStep;
|
|
15
|
+
/** Ajoute `explanations` vide : l'analyse n'a pas encore d'explication. */
|
|
16
|
+
export declare const addExplanations: MigrationStep;
|
|
15
17
|
/** Étapes appliquées aux analyses (`analyses/<id>.json`), dans l'ordre. */
|
|
16
18
|
export declare const ANALYSIS_MIGRATIONS: readonly MigrationStep[];
|
|
17
19
|
/** Étapes appliquées aux snapshots de diff (`diffs/<id>.json`), dans l'ordre. */
|
package/dist/src/core/migrate.js
CHANGED
|
@@ -24,8 +24,10 @@ export const addProject = (doc) => {
|
|
|
24
24
|
};
|
|
25
25
|
/** Ajoute `overview` à null : l'analyse n'a pas encore de vue d'ensemble. */
|
|
26
26
|
export const addOverview = (doc) => (doc.overview !== undefined ? null : { ...doc, overview: null });
|
|
27
|
+
/** Ajoute `explanations` vide : l'analyse n'a pas encore d'explication. */
|
|
28
|
+
export const addExplanations = (doc) => (doc.explanations !== undefined ? null : { ...doc, explanations: [] });
|
|
27
29
|
/** Étapes appliquées aux analyses (`analyses/<id>.json`), dans l'ordre. */
|
|
28
|
-
export const ANALYSIS_MIGRATIONS = [addProject, addOverview];
|
|
30
|
+
export const ANALYSIS_MIGRATIONS = [addProject, addOverview, addExplanations];
|
|
29
31
|
/** Étapes appliquées aux snapshots de diff (`diffs/<id>.json`), dans l'ordre. */
|
|
30
32
|
export const SNAPSHOT_MIGRATIONS = [];
|
|
31
33
|
/** Applique `steps` à la suite. Renvoie null si aucune étape n'a modifié le document. */
|
|
@@ -14,9 +14,13 @@ export interface RefreshStats {
|
|
|
14
14
|
findingsOutdated: number;
|
|
15
15
|
/** Constats `outdated` revenus à `open`. */
|
|
16
16
|
findingsRestored: number;
|
|
17
|
+
/** Explications `current` passées à `outdated`. */
|
|
18
|
+
explanationsOutdated: number;
|
|
19
|
+
/** Explications `outdated` revenues à `current`. */
|
|
20
|
+
explanationsRestored: number;
|
|
17
21
|
}
|
|
18
22
|
export interface RefreshResult {
|
|
19
|
-
fields: Pick<Analysis, "baseCommit" | "headCommit" | "snapshotAt" | "files" | "findings">;
|
|
23
|
+
fields: Pick<Analysis, "baseCommit" | "headCommit" | "snapshotAt" | "files" | "findings" | "explanations">;
|
|
20
24
|
stats: RefreshStats;
|
|
21
25
|
}
|
|
22
26
|
/**
|
|
@@ -25,6 +29,8 @@ export interface RefreshResult {
|
|
|
25
29
|
* - un constat `open` dont les lignes ne correspondent plus à `anchorText`, ou dont le
|
|
26
30
|
* fichier a quitté le diff, passe à `outdated` ;
|
|
27
31
|
* - un constat `outdated` dont les lignes correspondent de nouveau revient à `open` ;
|
|
28
|
-
* - un constat `ignored` reste `ignored`, un constat sans emplacement ne change pas
|
|
32
|
+
* - un constat `ignored` reste `ignored`, un constat sans emplacement ne change pas ;
|
|
33
|
+
* - une explication dont les lignes ne correspondent plus à `anchorText` passe à
|
|
34
|
+
* `outdated`, et revient à `current` quand elles correspondent de nouveau.
|
|
29
35
|
*/
|
|
30
36
|
export declare function applyRefresh(analysis: Analysis, oldSnapshot: DiffSnapshot, next: ComputedSnapshot): RefreshResult;
|
package/dist/src/core/refresh.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { splitLines } from "../../shared/text.js";
|
|
2
2
|
import { AnalysisError } from "./errors.js";
|
|
3
|
+
import { explanationText } from "./locations.js";
|
|
3
4
|
function hunksEqual(a, b) {
|
|
4
5
|
if (a.length !== b.length)
|
|
5
6
|
return false;
|
|
@@ -48,7 +49,9 @@ function currentText(finding, diffs) {
|
|
|
48
49
|
* - un constat `open` dont les lignes ne correspondent plus à `anchorText`, ou dont le
|
|
49
50
|
* fichier a quitté le diff, passe à `outdated` ;
|
|
50
51
|
* - un constat `outdated` dont les lignes correspondent de nouveau revient à `open` ;
|
|
51
|
-
* - un constat `ignored` reste `ignored`, un constat sans emplacement ne change pas
|
|
52
|
+
* - un constat `ignored` reste `ignored`, un constat sans emplacement ne change pas ;
|
|
53
|
+
* - une explication dont les lignes ne correspondent plus à `anchorText` passe à
|
|
54
|
+
* `outdated`, et revient à `current` quand elles correspondent de nouveau.
|
|
52
55
|
*/
|
|
53
56
|
export function applyRefresh(analysis, oldSnapshot, next) {
|
|
54
57
|
if (next.repoPath !== analysis.repoPath || next.mode !== analysis.mode || next.base !== analysis.base || next.head !== analysis.head) {
|
|
@@ -65,6 +68,8 @@ export function applyRefresh(analysis, oldSnapshot, next) {
|
|
|
65
68
|
reviewedReset: 0,
|
|
66
69
|
findingsOutdated: 0,
|
|
67
70
|
findingsRestored: 0,
|
|
71
|
+
explanationsOutdated: 0,
|
|
72
|
+
explanationsRestored: 0,
|
|
68
73
|
};
|
|
69
74
|
const files = next.files.map((entry) => {
|
|
70
75
|
const previous = oldEntries.get(entry.path);
|
|
@@ -100,6 +105,18 @@ export function applyRefresh(analysis, oldSnapshot, next) {
|
|
|
100
105
|
}
|
|
101
106
|
return finding;
|
|
102
107
|
});
|
|
108
|
+
const explanations = analysis.explanations.map((explanation) => {
|
|
109
|
+
const matches = explanationText(newDiffs.get(explanation.location.path), explanation.location) === explanation.anchorText;
|
|
110
|
+
if (explanation.status === "current" && !matches) {
|
|
111
|
+
stats.explanationsOutdated++;
|
|
112
|
+
return { ...explanation, status: "outdated" };
|
|
113
|
+
}
|
|
114
|
+
if (explanation.status === "outdated" && matches) {
|
|
115
|
+
stats.explanationsRestored++;
|
|
116
|
+
return { ...explanation, status: "current" };
|
|
117
|
+
}
|
|
118
|
+
return explanation;
|
|
119
|
+
});
|
|
103
120
|
return {
|
|
104
121
|
fields: {
|
|
105
122
|
baseCommit: next.baseCommit,
|
|
@@ -107,6 +124,7 @@ export function applyRefresh(analysis, oldSnapshot, next) {
|
|
|
107
124
|
snapshotAt: next.snapshotAt,
|
|
108
125
|
files,
|
|
109
126
|
findings,
|
|
127
|
+
explanations,
|
|
110
128
|
},
|
|
111
129
|
stats,
|
|
112
130
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Analysis } from "../../shared/schemas/analysis.schema.js";
|
|
2
|
-
import type { AddNoteBody, DeleteNoteBody, SetFileReviewedBody, SetFindingStatusBody, SubmitReviewBody } from "../../shared/schemas/api.schema.js";
|
|
2
|
+
import type { AddNoteBody, DeleteNoteBody, SetFileReviewedBody, SetFindingStatusBody, SubmitReviewBody, UpdateNoteBody } from "../../shared/schemas/api.schema.js";
|
|
3
3
|
import type { AnalysisStore } from "./analysis-store.js";
|
|
4
4
|
/**
|
|
5
5
|
* Modifications faites par le relecteur depuis la GUI. Chacune passe par
|
|
@@ -15,6 +15,11 @@ export declare function setFileReviewed(store: AnalysisStore, analysisId: string
|
|
|
15
15
|
export declare function setFindingStatus(store: AnalysisStore, analysisId: string, findingId: string, body: SetFindingStatusBody): Analysis;
|
|
16
16
|
/** Ajoute une remarque du relecteur sur une ligne du côté « nouveau » ou sur un fichier entier. */
|
|
17
17
|
export declare function addNote(store: AnalysisStore, analysisId: string, body: AddNoteBody): Analysis;
|
|
18
|
+
/**
|
|
19
|
+
* Remplace le texte d'une remarque. Quand elle fait partie d'une revue soumise,
|
|
20
|
+
* le prompt enregistré est recalculé avec le nouveau texte.
|
|
21
|
+
*/
|
|
22
|
+
export declare function updateNote(store: AnalysisStore, analysisId: string, noteId: string, body: UpdateNoteBody): Analysis;
|
|
18
23
|
/**
|
|
19
24
|
* Supprime une remarque. Quand elle fait partie d'une revue soumise, elle est
|
|
20
25
|
* retirée de la sélection et le prompt enregistré est recalculé sans elle.
|
|
@@ -47,6 +47,26 @@ export function addNote(store, analysisId, body) {
|
|
|
47
47
|
});
|
|
48
48
|
}, { baseRevision: body.baseRevision });
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Remplace le texte d'une remarque. Quand elle fait partie d'une revue soumise,
|
|
52
|
+
* le prompt enregistré est recalculé avec le nouveau texte.
|
|
53
|
+
*/
|
|
54
|
+
export function updateNote(store, analysisId, noteId, body) {
|
|
55
|
+
return store.mutate(analysisId, "user", (draft) => {
|
|
56
|
+
const note = draft.notes.find((entry) => entry.id === noteId);
|
|
57
|
+
if (!note)
|
|
58
|
+
throw new NotFoundError(`Note "${noteId}" not found in analysis "${analysisId}".`);
|
|
59
|
+
note.text = body.text;
|
|
60
|
+
const review = draft.review;
|
|
61
|
+
if (review.state === "submitted" && review.selectedNoteIds.includes(noteId)) {
|
|
62
|
+
review.prompt = buildAgentPrompt(draft, {
|
|
63
|
+
findingIds: review.selectedFindingIds,
|
|
64
|
+
noteIds: review.selectedNoteIds,
|
|
65
|
+
decision: review.decision,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}, { baseRevision: body.baseRevision });
|
|
69
|
+
}
|
|
50
70
|
/**
|
|
51
71
|
* Supprime une remarque. Quand elle fait partie d'une revue soumise, elle est
|
|
52
72
|
* retirée de la sélection et le prompt enregistré est recalculé sans elle.
|