@imenam/mcp-feature-analyzer 1.0.17 → 1.0.18
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 +357 -351
- package/dist/shared/labels.d.ts +2 -0
- package/dist/shared/labels.js +2 -0
- package/dist/shared/review-state.d.ts +32 -2
- package/dist/shared/review-state.js +74 -3
- package/dist/shared/schemas/analysis.schema.d.ts +100 -0
- package/dist/shared/schemas/analysis.schema.js +24 -0
- package/dist/shared/schemas/ipc.schema.d.ts +17 -2
- package/dist/shared/schemas/ipc.schema.js +2 -2
- package/dist/src/core/analysis-store.d.ts +0 -2
- package/dist/src/core/analysis-store.js +1 -5
- package/dist/src/core/invariants.js +3 -0
- package/dist/src/core/migrate.d.ts +5 -0
- package/dist/src/core/migrate.js +14 -1
- package/dist/src/core/review-actions.d.ts +9 -10
- package/dist/src/core/review-actions.js +22 -29
- package/dist/src/gui-worker.js +48 -46
- package/dist/src/index.js +36 -13
- package/dist/src/tools/format.d.ts +4 -1
- package/dist/src/tools/format.js +6 -2
- package/dist/src/tools/get-review-feedback.d.ts +5 -1
- package/dist/src/tools/get-review-feedback.js +59 -19
- package/dist/src/tools/list-analyses.d.ts +7 -1
- package/dist/src/tools/list-analyses.js +39 -14
- package/dist/src/tools/mark-feedback-processed.d.ts +8 -0
- package/dist/src/tools/mark-feedback-processed.js +34 -0
- package/gui/dist/assets/{angular-html-mqETY8Ta.js → angular-html-khSGtcDV.js} +1 -1
- package/gui/dist/assets/{angular-ts-Dyua8-1t.js → angular-ts-CQZskKbG.js} +1 -1
- package/gui/dist/assets/{apl-3jbxnvrH.js → apl-Dk2A1KW8.js} +1 -1
- package/gui/dist/assets/{astro-Co4C3aDz.js → astro-CDNLxpIR.js} +1 -1
- package/gui/dist/assets/{blade-B4EPDbIQ.js → blade-HVRxvZlI.js} +1 -1
- package/gui/dist/assets/{c-B6Zq6Std.js → c-BAogwQrx.js} +1 -1
- package/gui/dist/assets/{chapel-BQd1_20m.js → chapel-CXJ1GKiJ.js} +1 -1
- package/gui/dist/assets/{cobol-m9aPmaKL.js → cobol-CUpuiRry.js} +1 -1
- package/gui/dist/assets/{coffee-9RfP0zJM.js → coffee-Bvuk4CPF.js} +1 -1
- package/gui/dist/assets/{cpp-BVBdUNUD.js → cpp-DrhGCibo.js} +1 -1
- package/gui/dist/assets/{crystal-BPUdd6PR.js → crystal-CoGIF_h4.js} +1 -1
- package/gui/dist/assets/{css-LrCWcNdJ.js → css-CUXMpymc.js} +1 -1
- package/gui/dist/assets/{edge-D-Q1P4iH.js → edge-MTcLzslY.js} +1 -1
- package/gui/dist/assets/{elixir-BvGDaF1W.js → elixir-DpYpNuu6.js} +1 -1
- package/gui/dist/assets/{elm-DcLIpqA8.js → elm-CAdLQ8j2.js} +1 -1
- package/gui/dist/assets/{erb-BNIJdl1V.js → erb-wGT1uBBo.js} +1 -1
- package/gui/dist/assets/{git-rebase-9T7mb2tK.js → git-rebase-D7SHvGwZ.js} +1 -1
- package/gui/dist/assets/{glimmer-js-7oEVZM3L.js → glimmer-js-DEF7JSd6.js} +1 -1
- package/gui/dist/assets/{glimmer-ts-B9lGBZyd.js → glimmer-ts-CM94Mbx8.js} +1 -1
- package/gui/dist/assets/{glsl-C59JLFCt.js → glsl-DvB1v13j.js} +1 -1
- package/gui/dist/assets/{graphql-DhT3P-M_.js → graphql-CpBeqKe2.js} +1 -1
- package/gui/dist/assets/{hack-B22EZN7R.js → hack-CBlJ1Su7.js} +1 -1
- package/gui/dist/assets/{haml-DOHFovSe.js → haml-Db0uDFny.js} +1 -1
- package/gui/dist/assets/{handlebars-ChdRsCo8.js → handlebars-B9jQcMgj.js} +1 -1
- package/gui/dist/assets/{html-Cvii8U8Z.js → html-DTSojrQm.js} +1 -1
- package/gui/dist/assets/{html-derivative-DvPQ-VHx.js → html-derivative-7jGgl4AX.js} +1 -1
- package/gui/dist/assets/{http-DrT8XJXI.js → http-DJbcEzJc.js} +1 -1
- package/gui/dist/assets/{hurl-B-_5p73n.js → hurl-BjBdeErH.js} +1 -1
- package/gui/dist/assets/index-Bj1iisUG.css +1 -0
- package/gui/dist/assets/index-CR3WhYFz.js +87 -0
- package/gui/dist/assets/{java-DX7zTQhc.js → java-Dn64Xe0Z.js} +1 -1
- package/gui/dist/assets/{javascript-Db2TOoX-.js → javascript-BbkSu_9c.js} +1 -1
- package/gui/dist/assets/{jinja-BlzjXyBG.js → jinja-DqC5t5sF.js} +1 -1
- package/gui/dist/assets/{jison-CYH9v_vD.js → jison-DpuZbYSp.js} +1 -1
- package/gui/dist/assets/{json-DvgESUmx.js → json-B2a7n5JV.js} +1 -1
- package/gui/dist/assets/{jsx-BxUu3NOl.js → jsx-ClgcRv-g.js} +1 -1
- package/gui/dist/assets/{julia-BD6shmUv.js → julia-Do1-bHs6.js} +1 -1
- package/gui/dist/assets/{just-B58vJyzZ.js → just-DHt2GVD3.js} +1 -1
- package/gui/dist/assets/{latex-DpXG4qlp.js → latex-B_f2UeRI.js} +1 -1
- package/gui/dist/assets/{liquid-CKXKEzQm.js → liquid-Ds2eU_MP.js} +1 -1
- package/gui/dist/assets/{lua-CrR2yS38.js → lua-S7tZuNGt.js} +1 -1
- package/gui/dist/assets/{marko-DIY1RQi3.js → marko-udM4lCVd.js} +1 -1
- package/gui/dist/assets/{mdc-CSxBBNi0.js → mdc-Cp5CPiQh.js} +1 -1
- package/gui/dist/assets/{nginx-BR5UiLsj.js → nginx-fGikazrY.js} +1 -1
- package/gui/dist/assets/{nim-81Kxf6HC.js → nim-ByS27m8u.js} +1 -1
- package/gui/dist/assets/{org-YkzSiTpH.js → org-CWef6EjL.js} +1 -1
- package/gui/dist/assets/{perl-BKAf7ctD.js → perl-B3b8PFq6.js} +1 -1
- package/gui/dist/assets/{php-D60BkLMS.js → php-BgIqtqOt.js} +1 -1
- package/gui/dist/assets/{pug-CeojeKqv.js → pug-BRImkfAG.js} +1 -1
- package/gui/dist/assets/{qml-vJFVB1sq.js → qml-CzXZbZNQ.js} +1 -1
- package/gui/dist/assets/{r-buvgYjbV.js → r-Nteo2x9j.js} +1 -1
- package/gui/dist/assets/{razor-Dz_7Cjz-.js → razor-D_Afw34M.js} +1 -1
- package/gui/dist/assets/{regexp-B68ulszt.js → regexp-CKIOMRkq.js} +1 -1
- package/gui/dist/assets/{rst-BJ9SfN4C.js → rst-BKqSLPk8.js} +1 -1
- package/gui/dist/assets/{ruby-C_OOgGF-.js → ruby-e08qQ71e.js} +1 -1
- package/gui/dist/assets/{sas-DyKx0IvO.js → sas-CBP-Z7IW.js} +1 -1
- package/gui/dist/assets/{scss-D2FME1KT.js → scss-D_FoTbGY.js} +1 -1
- package/gui/dist/assets/{shellscript-DKhUkHWJ.js → shellscript-Ck8Pcu_n.js} +1 -1
- package/gui/dist/assets/{shellsession-B9VkeV-t.js → shellsession-ClnG1wCa.js} +1 -1
- package/gui/dist/assets/{soy-DT_8USFA.js → soy-SRwHcO2G.js} +1 -1
- package/gui/dist/assets/{sql-CTY7Kq3S.js → sql-CjUCW4z6.js} +1 -1
- package/gui/dist/assets/{stata-BXc7Lm53.js → stata-DbpO8t8N.js} +1 -1
- package/gui/dist/assets/{surrealql-Dc_YL61u.js → surrealql-BvdjWgRW.js} +1 -1
- package/gui/dist/assets/{svelte-fq0JF0B3.js → svelte-Bce_bVba.js} +1 -1
- package/gui/dist/assets/{templ-B20S0R9K.js → templ-D1Wmfx4s.js} +1 -1
- package/gui/dist/assets/{tex-BhmGjqVa.js → tex-DAUoYbei.js} +1 -1
- package/gui/dist/assets/{ts-tags-D_pLLOmi.js → ts-tags-DeHdW_lm.js} +1 -1
- package/gui/dist/assets/{tsx-DULdqnBJ.js → tsx-BtUXi9Pr.js} +1 -1
- package/gui/dist/assets/{twig-DIYYhsx1.js → twig-NY4Ref0L.js} +1 -1
- package/gui/dist/assets/{typescript-0qGmyQ6p.js → typescript-BRVtVFtH.js} +1 -1
- package/gui/dist/assets/{typst-qJ1kgBh8.js → typst-CEx4fnEq.js} +1 -1
- package/gui/dist/assets/{vue-BZCq4N1_.js → vue-CzSBuAS4.js} +1 -1
- package/gui/dist/assets/{vue-html-DtRFW8u7.js → vue-html-3GkraVg2.js} +1 -1
- package/gui/dist/assets/{vue-vine-CoFObNiz.js → vue-vine-un5mayXW.js} +1 -1
- package/gui/dist/assets/{xml-DjvoX99Q.js → xml-DYFbmBqP.js} +1 -1
- package/gui/dist/assets/{xsl-B0Obrqq8.js → xsl-CVaXn7rF.js} +1 -1
- package/gui/dist/assets/{yaml-Dbi9c2aj.js → yaml-CCwQ0s68.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/gui/dist/assets/index-B_Us1tSN.css +0 -1
- package/gui/dist/assets/index-D7dShIx3.js +0 -87
package/dist/shared/labels.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare const MODE_LABELS: Record<AnalysisMode, string>;
|
|
|
17
17
|
export declare const DECISION_LABELS: Record<ReviewDecision, string>;
|
|
18
18
|
/** Pastille de l'état d'avancement d'une revue. */
|
|
19
19
|
export declare const REVIEW_PROGRESS_STYLES: Record<ReviewProgressState, BadgeStyle>;
|
|
20
|
+
/** Pastille d'une revue soumise dont l'agent a marqué le retour traité. */
|
|
21
|
+
export declare const PROCESSED_STYLE: BadgeStyle;
|
|
20
22
|
/** Pastille de la décision d'une revue soumise, affichée à côté de « Soumise ». */
|
|
21
23
|
export declare const DECISION_STYLES: Record<ReviewDecision, BadgeStyle>;
|
|
22
24
|
/**
|
package/dist/shared/labels.js
CHANGED
|
@@ -46,6 +46,8 @@ export const REVIEW_PROGRESS_STYLES = {
|
|
|
46
46
|
files_reviewed: { label: "Fichiers revus", color: "#8b97ff", background: "#1b1f3d" },
|
|
47
47
|
submitted: { label: "Soumise", color: "#3fb950", background: "#11201a" },
|
|
48
48
|
};
|
|
49
|
+
/** Pastille d'une revue soumise dont l'agent a marqué le retour traité. */
|
|
50
|
+
export const PROCESSED_STYLE = { label: "Traitée", color: "#5fd4c4", background: "#10272a" };
|
|
49
51
|
/** Pastille de la décision d'une revue soumise, affichée à côté de « Soumise ». */
|
|
50
52
|
export const DECISION_STYLES = {
|
|
51
53
|
approve: { label: "Approuvée", color: "#3fb950", background: "#11201a" },
|
|
@@ -1,10 +1,40 @@
|
|
|
1
|
-
import type { Analysis, ReviewProgress } from "./schemas/analysis.schema.js";
|
|
1
|
+
import type { Analysis, Review, ReviewProgress, SubmissionRecord } from "./schemas/analysis.schema.js";
|
|
2
2
|
/**
|
|
3
3
|
* Avancement de la revue d'une analyse :
|
|
4
|
-
* - `submitted` : revue soumise, avec sa décision ;
|
|
4
|
+
* - `submitted` : revue soumise, avec sa décision et son traitement par l'agent ;
|
|
5
5
|
* - `files_reviewed` : tous les fichiers revus, revue non soumise ;
|
|
6
6
|
* - `in_progress` : au moins un fichier revu, revue non soumise ;
|
|
7
7
|
* - `not_started` : aucun fichier revu, revue non soumise. Une analyse sans
|
|
8
8
|
* fichier reste `not_started` jusqu'à la soumission.
|
|
9
9
|
*/
|
|
10
10
|
export declare function reviewProgress(analysis: Pick<Analysis, "files" | "review">): ReviewProgress;
|
|
11
|
+
/** Copie autonome de la revue soumise de `analysis` : décision, date, traitement, points retenus et points écartés. */
|
|
12
|
+
export declare function recordSubmission(analysis: Pick<Analysis, "review" | "findings" | "notes">): SubmissionRecord;
|
|
13
|
+
/**
|
|
14
|
+
* Repasse une revue soumise à l'état en attente, avant une modification du
|
|
15
|
+
* relecteur : la version soumise est conservée dans `previousSubmission`. Sans
|
|
16
|
+
* effet sur une revue en attente.
|
|
17
|
+
*/
|
|
18
|
+
export declare function reopenSubmittedReview(draft: Pick<Analysis, "review" | "findings" | "notes">): void;
|
|
19
|
+
/** Revue vierge : en attente, sans décision, sélection, prompt, traitement ni version précédente. */
|
|
20
|
+
export declare function pendingReview(): Review;
|
|
21
|
+
/** Écarts entre deux versions soumises d'une revue. */
|
|
22
|
+
export interface SubmissionChanges {
|
|
23
|
+
decision: {
|
|
24
|
+
from: SubmissionRecord["decision"];
|
|
25
|
+
to: SubmissionRecord["decision"];
|
|
26
|
+
} | null;
|
|
27
|
+
addedFindings: SubmissionRecord["findings"];
|
|
28
|
+
removedFindings: SubmissionRecord["findings"];
|
|
29
|
+
addedNotes: SubmissionRecord["notes"];
|
|
30
|
+
/** Remarques retenues dans les deux versions dont le texte ou l'emplacement a changé. */
|
|
31
|
+
modifiedNotes: Array<{
|
|
32
|
+
before: SubmissionRecord["notes"][number];
|
|
33
|
+
after: SubmissionRecord["notes"][number];
|
|
34
|
+
}>;
|
|
35
|
+
removedNotes: SubmissionRecord["notes"];
|
|
36
|
+
}
|
|
37
|
+
/** Ce qui a changé de la version soumise `before` à la version `after`. */
|
|
38
|
+
export declare function submissionChanges(before: SubmissionRecord, after: SubmissionRecord): SubmissionChanges;
|
|
39
|
+
/** Vrai quand `submissionChanges` ne relève aucun écart. */
|
|
40
|
+
export declare function hasNoChanges(changes: SubmissionChanges): boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Avancement de la revue d'une analyse :
|
|
3
|
-
* - `submitted` : revue soumise, avec sa décision ;
|
|
3
|
+
* - `submitted` : revue soumise, avec sa décision et son traitement par l'agent ;
|
|
4
4
|
* - `files_reviewed` : tous les fichiers revus, revue non soumise ;
|
|
5
5
|
* - `in_progress` : au moins un fichier revu, revue non soumise ;
|
|
6
6
|
* - `not_started` : aucun fichier revu, revue non soumise. Une analyse sans
|
|
@@ -10,8 +10,79 @@ export function reviewProgress(analysis) {
|
|
|
10
10
|
const totalFiles = analysis.files.length;
|
|
11
11
|
const reviewedFiles = analysis.files.filter((file) => file.reviewed).length;
|
|
12
12
|
if (analysis.review.state === "submitted") {
|
|
13
|
-
return { state: "submitted", reviewedFiles, totalFiles, decision: analysis.review.decision };
|
|
13
|
+
return { state: "submitted", reviewedFiles, totalFiles, decision: analysis.review.decision, processed: analysis.review.processed };
|
|
14
14
|
}
|
|
15
15
|
const state = reviewedFiles === 0 ? "not_started" : reviewedFiles === totalFiles ? "files_reviewed" : "in_progress";
|
|
16
|
-
return { state, reviewedFiles, totalFiles, decision: null };
|
|
16
|
+
return { state, reviewedFiles, totalFiles, decision: null, processed: null };
|
|
17
|
+
}
|
|
18
|
+
/** Copie autonome de la revue soumise de `analysis` : décision, date, traitement, points retenus et points écartés. */
|
|
19
|
+
export function recordSubmission(analysis) {
|
|
20
|
+
const review = analysis.review;
|
|
21
|
+
if (review.state !== "submitted" || review.decision === null || review.submittedAt === null) {
|
|
22
|
+
throw new Error("Only a submitted review can be recorded.");
|
|
23
|
+
}
|
|
24
|
+
const findingIds = new Set(review.selectedFindingIds);
|
|
25
|
+
const noteIds = new Set(review.selectedNoteIds);
|
|
26
|
+
return {
|
|
27
|
+
decision: review.decision,
|
|
28
|
+
submittedAt: review.submittedAt,
|
|
29
|
+
processed: review.processed,
|
|
30
|
+
findings: analysis.findings.filter((finding) => findingIds.has(finding.id)).map(({ id, title }) => ({ id, title })),
|
|
31
|
+
notes: analysis.notes.filter((note) => noteIds.has(note.id)).map(({ id, location, text }) => ({ id, location, text })),
|
|
32
|
+
excludedFindingIds: analysis.findings.filter((finding) => finding.status === "open" && !findingIds.has(finding.id)).map(({ id }) => id),
|
|
33
|
+
excludedNoteIds: analysis.notes.filter((note) => !noteIds.has(note.id)).map(({ id }) => id),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Repasse une revue soumise à l'état en attente, avant une modification du
|
|
38
|
+
* relecteur : la version soumise est conservée dans `previousSubmission`. Sans
|
|
39
|
+
* effet sur une revue en attente.
|
|
40
|
+
*/
|
|
41
|
+
export function reopenSubmittedReview(draft) {
|
|
42
|
+
if (draft.review.state !== "submitted")
|
|
43
|
+
return;
|
|
44
|
+
draft.review = { ...pendingReview(), previousSubmission: recordSubmission(draft) };
|
|
45
|
+
}
|
|
46
|
+
/** Revue vierge : en attente, sans décision, sélection, prompt, traitement ni version précédente. */
|
|
47
|
+
export function pendingReview() {
|
|
48
|
+
return {
|
|
49
|
+
state: "pending",
|
|
50
|
+
decision: null,
|
|
51
|
+
selectedFindingIds: [],
|
|
52
|
+
selectedNoteIds: [],
|
|
53
|
+
prompt: null,
|
|
54
|
+
submittedAt: null,
|
|
55
|
+
processed: null,
|
|
56
|
+
previousSubmission: null,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function sameLocation(a, b) {
|
|
60
|
+
return a === null || b === null ? a === b : a.path === b.path && a.line === b.line;
|
|
61
|
+
}
|
|
62
|
+
/** Ce qui a changé de la version soumise `before` à la version `after`. */
|
|
63
|
+
export function submissionChanges(before, after) {
|
|
64
|
+
const afterFindings = new Set(after.findings.map((finding) => finding.id));
|
|
65
|
+
const beforeFindings = new Set(before.findings.map((finding) => finding.id));
|
|
66
|
+
const afterNotes = new Map(after.notes.map((note) => [note.id, note]));
|
|
67
|
+
const beforeNotes = new Map(before.notes.map((note) => [note.id, note]));
|
|
68
|
+
return {
|
|
69
|
+
decision: before.decision === after.decision ? null : { from: before.decision, to: after.decision },
|
|
70
|
+
addedFindings: after.findings.filter((finding) => !beforeFindings.has(finding.id)),
|
|
71
|
+
removedFindings: before.findings.filter((finding) => !afterFindings.has(finding.id)),
|
|
72
|
+
addedNotes: after.notes.filter((note) => !beforeNotes.has(note.id)),
|
|
73
|
+
modifiedNotes: after.notes.flatMap((note) => {
|
|
74
|
+
const previous = beforeNotes.get(note.id);
|
|
75
|
+
return previous && (previous.text !== note.text || !sameLocation(previous.location, note.location)) ? [{ before: previous, after: note }] : [];
|
|
76
|
+
}),
|
|
77
|
+
removedNotes: before.notes.filter((note) => !afterNotes.has(note.id)),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Vrai quand `submissionChanges` ne relève aucun écart. */
|
|
81
|
+
export function hasNoChanges(changes) {
|
|
82
|
+
return (changes.decision === null &&
|
|
83
|
+
changes.addedFindings.length === 0 &&
|
|
84
|
+
changes.removedFindings.length === 0 &&
|
|
85
|
+
changes.addedNotes.length === 0 &&
|
|
86
|
+
changes.modifiedNotes.length === 0 &&
|
|
87
|
+
changes.removedNotes.length === 0);
|
|
17
88
|
}
|
|
@@ -191,6 +191,42 @@ export declare const DiagramSchema: z.ZodObject<{
|
|
|
191
191
|
}, z.core.$strip>>;
|
|
192
192
|
updatedAt: z.ZodString;
|
|
193
193
|
}, z.core.$strip>;
|
|
194
|
+
/** Traitement d'un retour par l'agent : date et note facultative (commits, réponses données…). */
|
|
195
|
+
export declare const ProcessingSchema: z.ZodObject<{
|
|
196
|
+
at: z.ZodString;
|
|
197
|
+
note: z.ZodNullable<z.ZodString>;
|
|
198
|
+
}, z.core.$strip>;
|
|
199
|
+
/**
|
|
200
|
+
* Dernière version soumise d'une revue, conservée quand le relecteur la modifie
|
|
201
|
+
* ou la soumet à nouveau. Elle recopie les points retenus pour rester lisible
|
|
202
|
+
* même après la suppression de constats ou de remarques.
|
|
203
|
+
*/
|
|
204
|
+
export declare const SubmissionRecordSchema: z.ZodObject<{
|
|
205
|
+
decision: z.ZodEnum<{
|
|
206
|
+
approve: "approve";
|
|
207
|
+
reject: "reject";
|
|
208
|
+
request_changes: "request_changes";
|
|
209
|
+
}>;
|
|
210
|
+
submittedAt: z.ZodString;
|
|
211
|
+
processed: z.ZodNullable<z.ZodObject<{
|
|
212
|
+
at: z.ZodString;
|
|
213
|
+
note: z.ZodNullable<z.ZodString>;
|
|
214
|
+
}, z.core.$strip>>;
|
|
215
|
+
findings: z.ZodArray<z.ZodObject<{
|
|
216
|
+
id: z.ZodString;
|
|
217
|
+
title: z.ZodString;
|
|
218
|
+
}, z.core.$strip>>;
|
|
219
|
+
notes: z.ZodArray<z.ZodObject<{
|
|
220
|
+
id: z.ZodString;
|
|
221
|
+
location: z.ZodNullable<z.ZodObject<{
|
|
222
|
+
path: z.ZodString;
|
|
223
|
+
line: z.ZodNullable<z.ZodNumber>;
|
|
224
|
+
}, z.core.$strip>>;
|
|
225
|
+
text: z.ZodString;
|
|
226
|
+
}, z.core.$strip>>;
|
|
227
|
+
excludedFindingIds: z.ZodArray<z.ZodString>;
|
|
228
|
+
excludedNoteIds: z.ZodArray<z.ZodString>;
|
|
229
|
+
}, z.core.$strip>;
|
|
194
230
|
export declare const ReviewSchema: z.ZodObject<{
|
|
195
231
|
state: z.ZodEnum<{
|
|
196
232
|
pending: "pending";
|
|
@@ -205,6 +241,36 @@ export declare const ReviewSchema: z.ZodObject<{
|
|
|
205
241
|
selectedNoteIds: z.ZodArray<z.ZodString>;
|
|
206
242
|
prompt: z.ZodNullable<z.ZodString>;
|
|
207
243
|
submittedAt: z.ZodNullable<z.ZodString>;
|
|
244
|
+
processed: z.ZodNullable<z.ZodObject<{
|
|
245
|
+
at: z.ZodString;
|
|
246
|
+
note: z.ZodNullable<z.ZodString>;
|
|
247
|
+
}, z.core.$strip>>;
|
|
248
|
+
previousSubmission: z.ZodNullable<z.ZodObject<{
|
|
249
|
+
decision: z.ZodEnum<{
|
|
250
|
+
approve: "approve";
|
|
251
|
+
reject: "reject";
|
|
252
|
+
request_changes: "request_changes";
|
|
253
|
+
}>;
|
|
254
|
+
submittedAt: z.ZodString;
|
|
255
|
+
processed: z.ZodNullable<z.ZodObject<{
|
|
256
|
+
at: z.ZodString;
|
|
257
|
+
note: z.ZodNullable<z.ZodString>;
|
|
258
|
+
}, z.core.$strip>>;
|
|
259
|
+
findings: z.ZodArray<z.ZodObject<{
|
|
260
|
+
id: z.ZodString;
|
|
261
|
+
title: z.ZodString;
|
|
262
|
+
}, z.core.$strip>>;
|
|
263
|
+
notes: z.ZodArray<z.ZodObject<{
|
|
264
|
+
id: z.ZodString;
|
|
265
|
+
location: z.ZodNullable<z.ZodObject<{
|
|
266
|
+
path: z.ZodString;
|
|
267
|
+
line: z.ZodNullable<z.ZodNumber>;
|
|
268
|
+
}, z.core.$strip>>;
|
|
269
|
+
text: z.ZodString;
|
|
270
|
+
}, z.core.$strip>>;
|
|
271
|
+
excludedFindingIds: z.ZodArray<z.ZodString>;
|
|
272
|
+
excludedNoteIds: z.ZodArray<z.ZodString>;
|
|
273
|
+
}, z.core.$strip>>;
|
|
208
274
|
}, z.core.$strip>;
|
|
209
275
|
export declare const RequestSchema: z.ZodObject<{
|
|
210
276
|
text: z.ZodString;
|
|
@@ -371,6 +437,36 @@ export declare const AnalysisSchema: z.ZodObject<{
|
|
|
371
437
|
selectedNoteIds: z.ZodArray<z.ZodString>;
|
|
372
438
|
prompt: z.ZodNullable<z.ZodString>;
|
|
373
439
|
submittedAt: z.ZodNullable<z.ZodString>;
|
|
440
|
+
processed: z.ZodNullable<z.ZodObject<{
|
|
441
|
+
at: z.ZodString;
|
|
442
|
+
note: z.ZodNullable<z.ZodString>;
|
|
443
|
+
}, z.core.$strip>>;
|
|
444
|
+
previousSubmission: z.ZodNullable<z.ZodObject<{
|
|
445
|
+
decision: z.ZodEnum<{
|
|
446
|
+
approve: "approve";
|
|
447
|
+
reject: "reject";
|
|
448
|
+
request_changes: "request_changes";
|
|
449
|
+
}>;
|
|
450
|
+
submittedAt: z.ZodString;
|
|
451
|
+
processed: z.ZodNullable<z.ZodObject<{
|
|
452
|
+
at: z.ZodString;
|
|
453
|
+
note: z.ZodNullable<z.ZodString>;
|
|
454
|
+
}, z.core.$strip>>;
|
|
455
|
+
findings: z.ZodArray<z.ZodObject<{
|
|
456
|
+
id: z.ZodString;
|
|
457
|
+
title: z.ZodString;
|
|
458
|
+
}, z.core.$strip>>;
|
|
459
|
+
notes: z.ZodArray<z.ZodObject<{
|
|
460
|
+
id: z.ZodString;
|
|
461
|
+
location: z.ZodNullable<z.ZodObject<{
|
|
462
|
+
path: z.ZodString;
|
|
463
|
+
line: z.ZodNullable<z.ZodNumber>;
|
|
464
|
+
}, z.core.$strip>>;
|
|
465
|
+
text: z.ZodString;
|
|
466
|
+
}, z.core.$strip>>;
|
|
467
|
+
excludedFindingIds: z.ZodArray<z.ZodString>;
|
|
468
|
+
excludedNoteIds: z.ZodArray<z.ZodString>;
|
|
469
|
+
}, z.core.$strip>>;
|
|
374
470
|
}, z.core.$strip>;
|
|
375
471
|
revision: z.ZodNumber;
|
|
376
472
|
createdAt: z.ZodString;
|
|
@@ -404,6 +500,8 @@ export type DiagramNodeLocation = z.infer<typeof DiagramNodeLocationSchema>;
|
|
|
404
500
|
export type DiagramNode = z.infer<typeof DiagramNodeSchema>;
|
|
405
501
|
export type DiagramLink = z.infer<typeof DiagramLinkSchema>;
|
|
406
502
|
export type Diagram = z.infer<typeof DiagramSchema>;
|
|
503
|
+
export type Processing = z.infer<typeof ProcessingSchema>;
|
|
504
|
+
export type SubmissionRecord = z.infer<typeof SubmissionRecordSchema>;
|
|
407
505
|
export type Review = z.infer<typeof ReviewSchema>;
|
|
408
506
|
export type AnalysisRequest = z.infer<typeof RequestSchema>;
|
|
409
507
|
export type Overview = z.infer<typeof OverviewSchema>;
|
|
@@ -418,6 +516,8 @@ export interface ReviewProgress {
|
|
|
418
516
|
totalFiles: number;
|
|
419
517
|
/** Décision de la revue soumise ; null tant qu'elle ne l'est pas. */
|
|
420
518
|
decision: ReviewDecision | null;
|
|
519
|
+
/** Traitement de la revue soumise par l'agent ; null tant qu'il est à faire ou que la revue n'est pas soumise. */
|
|
520
|
+
processed: Processing | null;
|
|
421
521
|
}
|
|
422
522
|
/** Vue légère d'une analyse, pour les listes. */
|
|
423
523
|
export interface AnalysisSummary {
|
|
@@ -122,6 +122,26 @@ export const DiagramSchema = z.object({
|
|
|
122
122
|
links: z.array(DiagramLinkSchema),
|
|
123
123
|
updatedAt: IsoDateSchema,
|
|
124
124
|
});
|
|
125
|
+
/** Traitement d'un retour par l'agent : date et note facultative (commits, réponses données…). */
|
|
126
|
+
export const ProcessingSchema = z.object({
|
|
127
|
+
at: IsoDateSchema,
|
|
128
|
+
note: z.string().min(1).nullable(),
|
|
129
|
+
});
|
|
130
|
+
/**
|
|
131
|
+
* Dernière version soumise d'une revue, conservée quand le relecteur la modifie
|
|
132
|
+
* ou la soumet à nouveau. Elle recopie les points retenus pour rester lisible
|
|
133
|
+
* même après la suppression de constats ou de remarques.
|
|
134
|
+
*/
|
|
135
|
+
export const SubmissionRecordSchema = z.object({
|
|
136
|
+
decision: z.enum(REVIEW_DECISIONS),
|
|
137
|
+
submittedAt: IsoDateSchema,
|
|
138
|
+
processed: ProcessingSchema.nullable(),
|
|
139
|
+
findings: z.array(z.object({ id: ItemIdSchema, title: z.string().min(1) })),
|
|
140
|
+
notes: z.array(z.object({ id: ItemIdSchema, location: NoteLocationSchema.nullable(), text: z.string().min(1) })),
|
|
141
|
+
/** Constats ouverts et remarques que le relecteur pouvait transmettre et a écartés. */
|
|
142
|
+
excludedFindingIds: z.array(ItemIdSchema),
|
|
143
|
+
excludedNoteIds: z.array(ItemIdSchema),
|
|
144
|
+
});
|
|
125
145
|
export const ReviewSchema = z.object({
|
|
126
146
|
state: z.enum(REVIEW_STATES),
|
|
127
147
|
decision: z.enum(REVIEW_DECISIONS).nullable(),
|
|
@@ -129,6 +149,10 @@ export const ReviewSchema = z.object({
|
|
|
129
149
|
selectedNoteIds: z.array(ItemIdSchema),
|
|
130
150
|
prompt: z.string().nullable(),
|
|
131
151
|
submittedAt: IsoDateSchema.nullable(),
|
|
152
|
+
/** Traitement de la soumission en cours par l'agent ; null tant qu'il ne l'a pas marquée traitée. */
|
|
153
|
+
processed: ProcessingSchema.nullable(),
|
|
154
|
+
/** Version soumise précédente ; null avant la première modification ou nouvelle soumission. */
|
|
155
|
+
previousSubmission: SubmissionRecordSchema.nullable(),
|
|
132
156
|
});
|
|
133
157
|
export const RequestSchema = z.object({
|
|
134
158
|
text: z.string().min(1),
|
|
@@ -21,9 +21,24 @@ export declare const IPCMessageSchema: z.ZodObject<{
|
|
|
21
21
|
timestamp: z.ZodString;
|
|
22
22
|
}, z.core.$strip>;
|
|
23
23
|
export type IPCMessage = z.infer<typeof IPCMessageSchema>;
|
|
24
|
-
/** Types d'IPC envoyés par le maître
|
|
25
|
-
export declare const PUSHED_EVENT_TYPES: readonly ["
|
|
24
|
+
/** Types d'IPC envoyés par le maître, journalisés par le GUI worker et servis tels quels par `/api/changes`. */
|
|
25
|
+
export declare const PUSHED_EVENT_TYPES: readonly ["ANALYSES_UPDATED", "ANALYSIS_UPDATED"];
|
|
26
26
|
export type PushedEventType = (typeof PUSHED_EVENT_TYPES)[number];
|
|
27
|
+
/**
|
|
28
|
+
* Réponse de `GET /api/changes`. `epoch` identifie l'instance du GUI worker,
|
|
29
|
+
* `seq` le dernier évènement journalisé. `reset: true` signifie que le client
|
|
30
|
+
* doit tout recharger ; sinon `events` liste les évènements postérieurs à `since`.
|
|
31
|
+
*/
|
|
32
|
+
export type ChangesResponse = {
|
|
33
|
+
epoch: string;
|
|
34
|
+
seq: number;
|
|
35
|
+
reset: true;
|
|
36
|
+
} | {
|
|
37
|
+
epoch: string;
|
|
38
|
+
seq: number;
|
|
39
|
+
reset: false;
|
|
40
|
+
events: IPCMessage[];
|
|
41
|
+
};
|
|
27
42
|
/** Requêtes du GUI worker au maître ; chacune reçoit une réponse de même `correlationId`. */
|
|
28
43
|
export declare const GUI_REQUEST_TYPES: readonly ["GET_CONFIG", "LIST_ANALYSES", "GET_ANALYSIS", "GET_DIFF", "SET_FILE_REVIEWED", "SET_FINDING_STATUS", "ADD_NOTE", "UPDATE_NOTE", "DELETE_NOTE", "SUBMIT_REVIEW"];
|
|
29
44
|
export type GuiRequestType = (typeof GUI_REQUEST_TYPES)[number];
|
|
@@ -14,8 +14,8 @@ export const IPCMessageSchema = z.object({
|
|
|
14
14
|
errorKind: z.enum(IPC_ERROR_KINDS).optional(),
|
|
15
15
|
timestamp: z.string(),
|
|
16
16
|
});
|
|
17
|
-
/** Types d'IPC envoyés par le maître
|
|
18
|
-
export const PUSHED_EVENT_TYPES = ["
|
|
17
|
+
/** Types d'IPC envoyés par le maître, journalisés par le GUI worker et servis tels quels par `/api/changes`. */
|
|
18
|
+
export const PUSHED_EVENT_TYPES = ["ANALYSES_UPDATED", "ANALYSIS_UPDATED"];
|
|
19
19
|
/** Requêtes du GUI worker au maître ; chacune reçoit une réponse de même `correlationId`. */
|
|
20
20
|
export const GUI_REQUEST_TYPES = [
|
|
21
21
|
"GET_CONFIG",
|
|
@@ -29,8 +29,6 @@ export interface ReplaceSnapshotResult {
|
|
|
29
29
|
/** Revue en place avant le recalcul, remplacée par une revue vierge. */
|
|
30
30
|
previousReview: Review;
|
|
31
31
|
}
|
|
32
|
-
/** Revue vierge : en attente, sans décision, sélection ni prompt. */
|
|
33
|
-
export declare function pendingReview(): Review;
|
|
34
32
|
/**
|
|
35
33
|
* Persistance des analyses : `<dataDir>/analyses/<id>.json` pour l'analyse et
|
|
36
34
|
* `<dataDir>/diffs/<id>.json` pour son snapshot de diff. Plusieurs processus
|
|
@@ -3,17 +3,13 @@ import fs from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { ANALYSIS_ID_PATTERN, AnalysisSchema, } from "../../shared/schemas/analysis.schema.js";
|
|
5
5
|
import { DiffSnapshotSchema } from "../../shared/schemas/diff.schema.js";
|
|
6
|
-
import { reviewProgress } from "../../shared/review-state.js";
|
|
6
|
+
import { pendingReview, reviewProgress } from "../../shared/review-state.js";
|
|
7
7
|
import { emptySeverityCounts } from "../../shared/severity.js";
|
|
8
8
|
import { AnalysisError, NotFoundError, RevisionConflictError } from "./errors.js";
|
|
9
9
|
import { analysisId } from "./ids.js";
|
|
10
10
|
import { assertInvariants, assertSnapshotFieldsUnchanged } from "./invariants.js";
|
|
11
11
|
import { migrateAnalysis, migrateSnapshot } from "./migrate.js";
|
|
12
12
|
import { applyRefresh } from "./refresh.js";
|
|
13
|
-
/** Revue vierge : en attente, sans décision, sélection ni prompt. */
|
|
14
|
-
export function pendingReview() {
|
|
15
|
-
return { state: "pending", decision: null, selectedFindingIds: [], selectedNoteIds: [], prompt: null, submittedAt: null };
|
|
16
|
-
}
|
|
17
13
|
/** Au-delà de ce délai, un verrou est réputé abandonné par un processus arrêté en cours d'écriture. */
|
|
18
14
|
const LOCK_STALE_MS = 10_000;
|
|
19
15
|
const LOCK_RETRY_MS = 25;
|
|
@@ -148,6 +148,9 @@ export function findInvariantViolations(analysis) {
|
|
|
148
148
|
if (review.state === "pending" && review.submittedAt !== null) {
|
|
149
149
|
problems.push("a pending review cannot have a submittedAt date");
|
|
150
150
|
}
|
|
151
|
+
if (review.state === "pending" && review.processed !== null) {
|
|
152
|
+
problems.push("a pending review cannot be marked processed");
|
|
153
|
+
}
|
|
151
154
|
return problems;
|
|
152
155
|
}
|
|
153
156
|
/** Lève une AnalysisError listant toutes les violations, s'il y en a. */
|
|
@@ -16,6 +16,11 @@ export declare const addOverview: MigrationStep;
|
|
|
16
16
|
export declare const addExplanations: MigrationStep;
|
|
17
17
|
/** Ajoute `findingId` à null sur les remarques qui n'en ont pas : elles restent des remarques libres. */
|
|
18
18
|
export declare const addNoteFindingIds: MigrationStep;
|
|
19
|
+
/**
|
|
20
|
+
* Ajoute à la revue `processed` et `previousSubmission` à null : une revue soumise
|
|
21
|
+
* avant ce format est à traiter, sans version précédente.
|
|
22
|
+
*/
|
|
23
|
+
export declare const addReviewProcessing: MigrationStep;
|
|
19
24
|
/** Étapes appliquées aux analyses (`analyses/<id>.json`), dans l'ordre. */
|
|
20
25
|
export declare const ANALYSIS_MIGRATIONS: readonly MigrationStep[];
|
|
21
26
|
/** Étapes appliquées aux snapshots de diff (`diffs/<id>.json`), dans l'ordre. */
|
package/dist/src/core/migrate.js
CHANGED
|
@@ -36,8 +36,21 @@ export const addNoteFindingIds = (doc) => {
|
|
|
36
36
|
return null;
|
|
37
37
|
return { ...doc, notes: notes.map((note) => (missing(note) ? { ...note, findingId: null } : note)) };
|
|
38
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* Ajoute à la revue `processed` et `previousSubmission` à null : une revue soumise
|
|
41
|
+
* avant ce format est à traiter, sans version précédente.
|
|
42
|
+
*/
|
|
43
|
+
export const addReviewProcessing = (doc) => {
|
|
44
|
+
const review = doc.review;
|
|
45
|
+
if (!review || typeof review !== "object" || Array.isArray(review))
|
|
46
|
+
return null;
|
|
47
|
+
const raw = review;
|
|
48
|
+
if (raw.processed !== undefined && raw.previousSubmission !== undefined)
|
|
49
|
+
return null;
|
|
50
|
+
return { ...doc, review: { ...raw, processed: raw.processed ?? null, previousSubmission: raw.previousSubmission ?? null } };
|
|
51
|
+
};
|
|
39
52
|
/** Étapes appliquées aux analyses (`analyses/<id>.json`), dans l'ordre. */
|
|
40
|
-
export const ANALYSIS_MIGRATIONS = [addProject, addOverview, addExplanations, addNoteFindingIds];
|
|
53
|
+
export const ANALYSIS_MIGRATIONS = [addProject, addOverview, addExplanations, addNoteFindingIds, addReviewProcessing];
|
|
41
54
|
/** Étapes appliquées aux snapshots de diff (`diffs/<id>.json`), dans l'ordre. */
|
|
42
55
|
export const SNAPSHOT_MIGRATIONS = [];
|
|
43
56
|
/** Applique `steps` à la suite. Renvoie null si aucune étape n'a modifié le document. */
|
|
@@ -4,6 +4,9 @@ import type { AnalysisStore } from "./analysis-store.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* Modifications faites par le relecteur depuis la GUI. Chacune passe par
|
|
6
6
|
* `store.mutate(id, "user", …, { baseRevision })` et renvoie l'analyse écrite.
|
|
7
|
+
* Changer une remarque ou le statut d'un constat repasse une revue soumise en
|
|
8
|
+
* attente (voir `reopenSubmittedReview`) : son retour n'est plus à traiter tant
|
|
9
|
+
* qu'elle n'est pas soumise à nouveau.
|
|
7
10
|
*/
|
|
8
11
|
/** Marque un fichier de l'analyse comme revu, ou le repasse à revoir. */
|
|
9
12
|
export declare function setFileReviewed(store: AnalysisStore, analysisId: string, body: SetFileReviewedBody): Analysis;
|
|
@@ -18,18 +21,14 @@ export declare function setFindingStatus(store: AnalysisStore, analysisId: strin
|
|
|
18
21
|
* liée au constat `findingId` quand elle y répond.
|
|
19
22
|
*/
|
|
20
23
|
export declare function addNote(store: AnalysisStore, analysisId: string, body: AddNoteBody): Analysis;
|
|
21
|
-
/**
|
|
22
|
-
* Remplace le texte d'une remarque. Quand elle fait partie d'une revue soumise,
|
|
23
|
-
* le prompt enregistré est recalculé avec le nouveau texte.
|
|
24
|
-
*/
|
|
24
|
+
/** Remplace le texte d'une remarque. */
|
|
25
25
|
export declare function updateNote(store: AnalysisStore, analysisId: string, noteId: string, body: UpdateNoteBody): Analysis;
|
|
26
|
-
/**
|
|
27
|
-
* Supprime une remarque. Quand elle fait partie d'une revue soumise, elle est
|
|
28
|
-
* retirée de la sélection et le prompt enregistré est recalculé sans elle.
|
|
29
|
-
*/
|
|
26
|
+
/** Supprime une remarque et la retire de la sélection de la revue. */
|
|
30
27
|
export declare function deleteNote(store: AnalysisStore, analysisId: string, noteId: string, body: DeleteNoteBody): Analysis;
|
|
31
28
|
/**
|
|
32
|
-
* Soumet la revue : décision, points retenus et prompt calculé ici.
|
|
33
|
-
* nouvelle soumission remplace la
|
|
29
|
+
* Soumet la revue : décision, points retenus et prompt calculé ici. La revue
|
|
30
|
+
* soumise est à traiter par l'agent. Une nouvelle soumission remplace la
|
|
31
|
+
* précédente, conservée dans `previousSubmission` ; une revue rouverte garde la
|
|
32
|
+
* version soumise qu'elle conservait déjà.
|
|
34
33
|
*/
|
|
35
34
|
export declare function submitReview(store: AnalysisStore, analysisId: string, body: SubmitReviewBody): Analysis;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { buildAgentPrompt } from "../../shared/prompt.js";
|
|
2
|
+
import { recordSubmission, reopenSubmittedReview } from "../../shared/review-state.js";
|
|
2
3
|
import { AnalysisError, NotFoundError } from "./errors.js";
|
|
3
4
|
import { itemId } from "./ids.js";
|
|
4
5
|
import { assertNoteLocation } from "./locations.js";
|
|
5
6
|
/**
|
|
6
7
|
* Modifications faites par le relecteur depuis la GUI. Chacune passe par
|
|
7
8
|
* `store.mutate(id, "user", …, { baseRevision })` et renvoie l'analyse écrite.
|
|
9
|
+
* Changer une remarque ou le statut d'un constat repasse une revue soumise en
|
|
10
|
+
* attente (voir `reopenSubmittedReview`) : son retour n'est plus à traiter tant
|
|
11
|
+
* qu'elle n'est pas soumise à nouveau.
|
|
8
12
|
*/
|
|
9
13
|
/** Marque un fichier de l'analyse comme revu, ou le repasse à revoir. */
|
|
10
14
|
export function setFileReviewed(store, analysisId, body) {
|
|
@@ -31,6 +35,9 @@ export function setFindingStatus(store, analysisId, findingId, body) {
|
|
|
31
35
|
if (finding.status === "outdated") {
|
|
32
36
|
throw new AnalysisError(`Finding "${findingId}" is outdated: its lines changed since it was written. The agent must anchor it again with update_finding before it can be ignored or reopened.`);
|
|
33
37
|
}
|
|
38
|
+
if (finding.status === body.status)
|
|
39
|
+
return;
|
|
40
|
+
reopenSubmittedReview(draft);
|
|
34
41
|
finding.status = body.status;
|
|
35
42
|
}, { baseRevision: body.baseRevision });
|
|
36
43
|
}
|
|
@@ -46,6 +53,7 @@ export function addNote(store, analysisId, body) {
|
|
|
46
53
|
if (findingId !== null && !draft.findings.some((finding) => finding.id === findingId)) {
|
|
47
54
|
throw new NotFoundError(`Finding "${findingId}" not found in analysis "${analysisId}".`);
|
|
48
55
|
}
|
|
56
|
+
reopenSubmittedReview(draft);
|
|
49
57
|
draft.notes.push({
|
|
50
58
|
id: itemId("note", new Set(draft.notes.map((note) => note.id))),
|
|
51
59
|
location,
|
|
@@ -55,47 +63,27 @@ export function addNote(store, analysisId, body) {
|
|
|
55
63
|
});
|
|
56
64
|
}, { baseRevision: body.baseRevision });
|
|
57
65
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Remplace le texte d'une remarque. Quand elle fait partie d'une revue soumise,
|
|
60
|
-
* le prompt enregistré est recalculé avec le nouveau texte.
|
|
61
|
-
*/
|
|
66
|
+
/** Remplace le texte d'une remarque. */
|
|
62
67
|
export function updateNote(store, analysisId, noteId, body) {
|
|
63
68
|
return store.mutate(analysisId, "user", (draft) => {
|
|
64
69
|
const note = draft.notes.find((entry) => entry.id === noteId);
|
|
65
70
|
if (!note)
|
|
66
71
|
throw new NotFoundError(`Note "${noteId}" not found in analysis "${analysisId}".`);
|
|
72
|
+
if (note.text === body.text)
|
|
73
|
+
return;
|
|
74
|
+
reopenSubmittedReview(draft);
|
|
67
75
|
note.text = body.text;
|
|
68
|
-
const review = draft.review;
|
|
69
|
-
if (review.state === "submitted" && review.selectedNoteIds.includes(noteId)) {
|
|
70
|
-
review.prompt = buildAgentPrompt(draft, {
|
|
71
|
-
findingIds: review.selectedFindingIds,
|
|
72
|
-
noteIds: review.selectedNoteIds,
|
|
73
|
-
decision: review.decision,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
76
|
}, { baseRevision: body.baseRevision });
|
|
77
77
|
}
|
|
78
|
-
/**
|
|
79
|
-
* Supprime une remarque. Quand elle fait partie d'une revue soumise, elle est
|
|
80
|
-
* retirée de la sélection et le prompt enregistré est recalculé sans elle.
|
|
81
|
-
*/
|
|
78
|
+
/** Supprime une remarque et la retire de la sélection de la revue. */
|
|
82
79
|
export function deleteNote(store, analysisId, noteId, body) {
|
|
83
80
|
return store.mutate(analysisId, "user", (draft) => {
|
|
84
81
|
const index = draft.notes.findIndex((note) => note.id === noteId);
|
|
85
82
|
if (index === -1)
|
|
86
83
|
throw new NotFoundError(`Note "${noteId}" not found in analysis "${analysisId}".`);
|
|
84
|
+
reopenSubmittedReview(draft);
|
|
87
85
|
draft.notes.splice(index, 1);
|
|
88
|
-
|
|
89
|
-
if (!review.selectedNoteIds.includes(noteId))
|
|
90
|
-
return;
|
|
91
|
-
review.selectedNoteIds = review.selectedNoteIds.filter((id) => id !== noteId);
|
|
92
|
-
if (review.state === "submitted") {
|
|
93
|
-
review.prompt = buildAgentPrompt(draft, {
|
|
94
|
-
findingIds: review.selectedFindingIds,
|
|
95
|
-
noteIds: review.selectedNoteIds,
|
|
96
|
-
decision: review.decision,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
86
|
+
draft.review.selectedNoteIds = draft.review.selectedNoteIds.filter((id) => id !== noteId);
|
|
99
87
|
}, { baseRevision: body.baseRevision });
|
|
100
88
|
}
|
|
101
89
|
function assertKnownIds(label, ids, known) {
|
|
@@ -108,13 +96,16 @@ function assertKnownIds(label, ids, known) {
|
|
|
108
96
|
throw new AnalysisError(`The selection lists ${label} id(s) twice: ${[...new Set(duplicated)].join(", ")}.`);
|
|
109
97
|
}
|
|
110
98
|
/**
|
|
111
|
-
* Soumet la revue : décision, points retenus et prompt calculé ici.
|
|
112
|
-
* nouvelle soumission remplace la
|
|
99
|
+
* Soumet la revue : décision, points retenus et prompt calculé ici. La revue
|
|
100
|
+
* soumise est à traiter par l'agent. Une nouvelle soumission remplace la
|
|
101
|
+
* précédente, conservée dans `previousSubmission` ; une revue rouverte garde la
|
|
102
|
+
* version soumise qu'elle conservait déjà.
|
|
113
103
|
*/
|
|
114
104
|
export function submitReview(store, analysisId, body) {
|
|
115
105
|
return store.mutate(analysisId, "user", (draft) => {
|
|
116
106
|
assertKnownIds("finding", body.findingIds, draft.findings.map((finding) => finding.id));
|
|
117
107
|
assertKnownIds("note", body.noteIds, draft.notes.map((note) => note.id));
|
|
108
|
+
const previousSubmission = draft.review.state === "submitted" ? recordSubmission(draft) : draft.review.previousSubmission;
|
|
118
109
|
draft.review = {
|
|
119
110
|
state: "submitted",
|
|
120
111
|
decision: body.decision,
|
|
@@ -122,6 +113,8 @@ export function submitReview(store, analysisId, body) {
|
|
|
122
113
|
selectedNoteIds: [...body.noteIds],
|
|
123
114
|
prompt: buildAgentPrompt(draft, { findingIds: body.findingIds, noteIds: body.noteIds, decision: body.decision }),
|
|
124
115
|
submittedAt: new Date().toISOString(),
|
|
116
|
+
processed: null,
|
|
117
|
+
previousSubmission,
|
|
125
118
|
};
|
|
126
119
|
}, { baseRevision: body.baseRevision });
|
|
127
120
|
}
|