@mrclrchtr/supi-review 2.8.0 → 3.1.0
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 +71 -180
- package/node_modules/@mrclrchtr/supi-core/package.json +2 -1
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +40 -0
- package/package.json +3 -3
- package/src/config.ts +30 -21
- package/src/git-command.ts +78 -0
- package/src/git.ts +311 -507
- package/src/history/collect.ts +43 -120
- package/src/model.ts +3 -1
- package/src/review-path.ts +85 -0
- package/src/review-result.ts +43 -92
- package/src/review.ts +187 -286
- package/src/session/review-plan-store.ts +20 -51
- package/src/target/packet.ts +46 -369
- package/src/tool/agent-review-schemas.ts +108 -104
- package/src/tool/agent-review-tools.ts +283 -307
- package/src/tool/child-failure-diagnostics.ts +59 -1
- package/src/tool/child-lifecycle-trace.ts +32 -3
- package/src/tool/child-resource-loader.ts +37 -0
- package/src/tool/child-session-runner.ts +83 -0
- package/src/tool/output-page.ts +47 -0
- package/src/tool/planner-runner.ts +69 -0
- package/src/tool/review-runner.ts +42 -257
- package/src/tool/review-system-prompt.ts +12 -110
- package/src/tool/review-tools.ts +119 -0
- package/src/tool/review-workflow.ts +325 -0
- package/src/tool/runner-helpers.ts +3 -8
- package/src/tool/schemas.ts +75 -62
- package/src/tui/common.ts +237 -0
- package/src/tui/prepare.ts +132 -0
- package/src/tui/run.ts +160 -0
- package/src/types.ts +157 -275
- package/src/history/synthesize.ts +0 -121
- package/src/tool/agent-review-workflow.ts +0 -398
- package/src/tool/brief-runner.ts +0 -180
- package/src/tool/guidance.ts +0 -21
- package/src/tool/review-handlers.ts +0 -314
- package/src/tool/snapshot-tools.ts +0 -117
- package/src/ui/flow.ts +0 -189
- package/src/ui/format-content.ts +0 -143
- package/src/ui/renderer.ts +0 -274
- package/src/ui/review-plan-inspector.ts +0 -391
- package/src/ui/review-tool-format.ts +0 -138
- package/src/ui/review-tool-renderer.ts +0 -234
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
|
|
5
|
-
import { DynamicBorder, type Theme } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import {
|
|
7
|
-
Container,
|
|
8
|
-
Key,
|
|
9
|
-
matchesKey,
|
|
10
|
-
Spacer,
|
|
11
|
-
Text,
|
|
12
|
-
truncateToWidth,
|
|
13
|
-
wrapTextWithAnsi,
|
|
14
|
-
} from "@earendil-works/pi-tui";
|
|
15
|
-
import { buildReviewPacketPreviewData } from "../target/packet.ts";
|
|
16
|
-
import type { ReviewPlan } from "../types.ts";
|
|
17
|
-
|
|
18
|
-
type PreviewScreen = "summary" | "inspector";
|
|
19
|
-
type InspectorMode = "overview" | "raw";
|
|
20
|
-
type NoticeLevel = "dim" | "warning";
|
|
21
|
-
|
|
22
|
-
interface PreviewNotice {
|
|
23
|
-
level: NoticeLevel;
|
|
24
|
-
text: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface ReviewPlanPreviewComponentArgs {
|
|
28
|
-
plan: ReviewPlan;
|
|
29
|
-
theme: Theme;
|
|
30
|
-
onDone: (approved: boolean) => void;
|
|
31
|
-
requestRender: () => void;
|
|
32
|
-
exportPrompt?: (prompt: string) => string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const INSPECTOR_VIEWPORT_HEIGHT = 18;
|
|
36
|
-
const REVIEW_PROMPT_EXPORT_FILENAME = "supi-review-prompt-latest.txt";
|
|
37
|
-
|
|
38
|
-
/** Stateful review-plan preview that keeps summary + inspector interaction in one TUI surface. */
|
|
39
|
-
export class ReviewPlanPreviewComponent {
|
|
40
|
-
private screen: PreviewScreen = "summary";
|
|
41
|
-
private inspectorMode: InspectorMode = "overview";
|
|
42
|
-
private scrollOffset = 0;
|
|
43
|
-
private notice?: PreviewNotice;
|
|
44
|
-
private readonly exportPrompt: (prompt: string) => string;
|
|
45
|
-
|
|
46
|
-
constructor(private readonly args: ReviewPlanPreviewComponentArgs) {
|
|
47
|
-
this.exportPrompt = args.exportPrompt ?? exportReviewPromptToTempFile;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
render(width: number): string[] {
|
|
51
|
-
return this.screen === "summary"
|
|
52
|
-
? buildSummaryContainer(this.args.theme, this.args.plan, this.notice).render(width)
|
|
53
|
-
: this.renderInspector(width);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
handleInput(data: string): boolean {
|
|
57
|
-
return this.screen === "summary"
|
|
58
|
-
? this.handleSummaryInput(data)
|
|
59
|
-
: this.handleInspectorInput(data);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
invalidate(): void {}
|
|
63
|
-
|
|
64
|
-
private handleSummaryInput(data: string): boolean {
|
|
65
|
-
if (matchesKey(data, Key.enter) || data === "y" || data === "Y") {
|
|
66
|
-
this.args.onDone(true);
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
if (matchesKey(data, Key.escape) || data === "n" || data === "N") {
|
|
70
|
-
this.args.onDone(false);
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
if (data === "v" || data === "V") {
|
|
74
|
-
this.screen = "inspector";
|
|
75
|
-
this.inspectorMode = "overview";
|
|
76
|
-
this.scrollOffset = 0;
|
|
77
|
-
this.args.requestRender();
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
if (data === "e" || data === "E") {
|
|
81
|
-
this.exportRawPrompt();
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private handleInspectorInput(data: string): boolean {
|
|
88
|
-
if (matchesKey(data, Key.escape) || data === "q" || data === "Q") {
|
|
89
|
-
this.screen = "summary";
|
|
90
|
-
this.scrollOffset = 0;
|
|
91
|
-
this.args.requestRender();
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
if (matchesKey(data, Key.tab)) {
|
|
95
|
-
this.inspectorMode = this.inspectorMode === "overview" ? "raw" : "overview";
|
|
96
|
-
this.scrollOffset = 0;
|
|
97
|
-
this.args.requestRender();
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
if (data === "e" || data === "E") {
|
|
101
|
-
this.exportRawPrompt();
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
if (matchesKey(data, Key.down) || data === "j") {
|
|
105
|
-
this.scrollOffset += 1;
|
|
106
|
-
this.args.requestRender();
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
if (matchesKey(data, Key.up) || data === "k") {
|
|
110
|
-
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
111
|
-
this.args.requestRender();
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private renderInspector(width: number): string[] {
|
|
118
|
-
const safeWidth = Math.max(width, 40);
|
|
119
|
-
const border = this.args.theme.fg("accent", "─".repeat(safeWidth));
|
|
120
|
-
const bodyLines = buildInspectorBodyLines(
|
|
121
|
-
Math.max(20, safeWidth - 4),
|
|
122
|
-
this.args.theme,
|
|
123
|
-
this.args.plan,
|
|
124
|
-
this.inspectorMode,
|
|
125
|
-
);
|
|
126
|
-
const clampedOffset = Math.max(
|
|
127
|
-
0,
|
|
128
|
-
Math.min(this.scrollOffset, Math.max(0, bodyLines.length - INSPECTOR_VIEWPORT_HEIGHT)),
|
|
129
|
-
);
|
|
130
|
-
const visibleLines = bodyLines.slice(clampedOffset, clampedOffset + INSPECTOR_VIEWPORT_HEIGHT);
|
|
131
|
-
const visibleEnd = visibleLines.length > 0 ? clampedOffset + visibleLines.length : 0;
|
|
132
|
-
const lines = [
|
|
133
|
-
border,
|
|
134
|
-
truncateToWidth(
|
|
135
|
-
this.args.theme.fg("accent", this.args.theme.bold(" Review Plan Inspector")),
|
|
136
|
-
safeWidth,
|
|
137
|
-
),
|
|
138
|
-
truncateToWidth(
|
|
139
|
-
` Inspector: ${this.inspectorMode === "overview" ? "Overview" : "Raw Prompt"} • Tab switch mode`,
|
|
140
|
-
safeWidth,
|
|
141
|
-
),
|
|
142
|
-
];
|
|
143
|
-
|
|
144
|
-
if (this.notice) {
|
|
145
|
-
lines.push(
|
|
146
|
-
truncateToWidth(` ${this.args.theme.fg(this.notice.level, this.notice.text)}`, safeWidth),
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
lines.push("");
|
|
151
|
-
lines.push(...visibleLines.map((line) => truncateToWidth(line, safeWidth)));
|
|
152
|
-
while (
|
|
153
|
-
visibleLines.length < INSPECTOR_VIEWPORT_HEIGHT &&
|
|
154
|
-
lines.length < INSPECTOR_VIEWPORT_HEIGHT + 6
|
|
155
|
-
) {
|
|
156
|
-
visibleLines.push(" ");
|
|
157
|
-
lines.push(" ");
|
|
158
|
-
}
|
|
159
|
-
lines.push("");
|
|
160
|
-
lines.push(
|
|
161
|
-
truncateToWidth(
|
|
162
|
-
this.args.theme.fg(
|
|
163
|
-
"dim",
|
|
164
|
-
` Lines ${bodyLines.length === 0 ? 0 : clampedOffset + 1}-${visibleEnd} of ${bodyLines.length}`,
|
|
165
|
-
),
|
|
166
|
-
safeWidth,
|
|
167
|
-
),
|
|
168
|
-
);
|
|
169
|
-
lines.push(
|
|
170
|
-
truncateToWidth(
|
|
171
|
-
this.args.theme.fg(
|
|
172
|
-
"dim",
|
|
173
|
-
" ↑↓ / j k scroll • tab switch mode • e export raw prompt • q / esc back",
|
|
174
|
-
),
|
|
175
|
-
safeWidth,
|
|
176
|
-
),
|
|
177
|
-
);
|
|
178
|
-
lines.push(border);
|
|
179
|
-
|
|
180
|
-
return lines;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
private exportRawPrompt(): void {
|
|
184
|
-
try {
|
|
185
|
-
this.notice = {
|
|
186
|
-
level: "dim",
|
|
187
|
-
text: `Exported raw prompt to ${this.exportPrompt(this.args.plan.packet.prompt)}`,
|
|
188
|
-
};
|
|
189
|
-
} catch {
|
|
190
|
-
this.notice = { level: "warning", text: "Unable to export the raw prompt." };
|
|
191
|
-
}
|
|
192
|
-
this.args.requestRender();
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/** Write the raw reviewer prompt to a stable temp file and return the path. */
|
|
197
|
-
export function exportReviewPromptToTempFile(prompt: string): string {
|
|
198
|
-
const path = join(tmpdir(), REVIEW_PROMPT_EXPORT_FILENAME);
|
|
199
|
-
writeFileSync(path, prompt, "utf-8");
|
|
200
|
-
return path;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function buildSummaryContainer(theme: Theme, plan: ReviewPlan, notice?: PreviewNotice): Container {
|
|
204
|
-
const { model, snapshot, brief, packet } = plan;
|
|
205
|
-
const container = new Container();
|
|
206
|
-
const accent = (s: string) => theme.fg("accent", s);
|
|
207
|
-
const dim = (s: string) => theme.fg("dim", s);
|
|
208
|
-
const briefParts = [
|
|
209
|
-
` ${dim("Summary:")} ${brief.summary}`,
|
|
210
|
-
` ${dim("Outcome:")} ${brief.intendedOutcome}`,
|
|
211
|
-
];
|
|
212
|
-
|
|
213
|
-
if (brief.constraints.length > 0) {
|
|
214
|
-
briefParts.push(` ${dim("Constraints:")} ${brief.constraints.join("; ")}`);
|
|
215
|
-
}
|
|
216
|
-
if (brief.focusAreas.length > 0) {
|
|
217
|
-
briefParts.push(` ${dim("Focus:")} ${brief.focusAreas.join("; ")}`);
|
|
218
|
-
}
|
|
219
|
-
if (brief.riskyFiles.length > 0) {
|
|
220
|
-
briefParts.push(` ${dim("Risky:")} ${brief.riskyFiles.join(", ")}`);
|
|
221
|
-
}
|
|
222
|
-
if (brief.unresolvedQuestions.length > 0) {
|
|
223
|
-
briefParts.push(` ${dim("Questions:")} ${brief.unresolvedQuestions.join("; ")}`);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
container.addChild(new DynamicBorder((s: string) => accent(s)));
|
|
227
|
-
container.addChild(new Spacer(1));
|
|
228
|
-
container.addChild(new Text(accent(theme.bold(" Review Plan")), 1, 0));
|
|
229
|
-
container.addChild(new Spacer(1));
|
|
230
|
-
container.addChild(new Text(accent(theme.bold(" ── Metadata ──")), 1, 0));
|
|
231
|
-
container.addChild(
|
|
232
|
-
new Text(
|
|
233
|
-
[
|
|
234
|
-
` ${dim("Model:")} ${model.canonicalId}`,
|
|
235
|
-
` ${dim("Target:")} ${snapshot.title}`,
|
|
236
|
-
` ${dim("Kind:")} ${formatTargetLabel(snapshot.target)}`,
|
|
237
|
-
` ${dim("Files:")} ${snapshot.changedFiles.length} changed ${theme.fg("toolDiffAdded", `+${snapshot.stats.additions}`)}/${theme.fg("toolDiffRemoved", `-${snapshot.stats.deletions}`)}`,
|
|
238
|
-
].join("\n"),
|
|
239
|
-
1,
|
|
240
|
-
0,
|
|
241
|
-
),
|
|
242
|
-
);
|
|
243
|
-
container.addChild(new Spacer(1));
|
|
244
|
-
container.addChild(new Text(accent(theme.bold(" ── Session-derived Brief ──")), 1, 0));
|
|
245
|
-
container.addChild(new Text(briefParts.join("\n"), 1, 0));
|
|
246
|
-
container.addChild(new Spacer(1));
|
|
247
|
-
container.addChild(new Text(buildPromptPreviewHeader(theme, packet.prompt), 1, 0));
|
|
248
|
-
container.addChild(new Text(buildPromptPreviewBody(theme, packet.prompt), 1, 0));
|
|
249
|
-
container.addChild(new Spacer(1));
|
|
250
|
-
container.addChild(
|
|
251
|
-
new Text(
|
|
252
|
-
theme.fg(
|
|
253
|
-
"dim",
|
|
254
|
-
` Diffs: on-demand via read_snapshot_diff • Files: ${snapshot.changedFiles.length} changed`,
|
|
255
|
-
),
|
|
256
|
-
1,
|
|
257
|
-
0,
|
|
258
|
-
),
|
|
259
|
-
);
|
|
260
|
-
container.addChild(new Spacer(1));
|
|
261
|
-
|
|
262
|
-
if (notice) {
|
|
263
|
-
container.addChild(new Text(` ${theme.fg(notice.level, notice.text)}`, 1, 0));
|
|
264
|
-
container.addChild(new Spacer(1));
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
container.addChild(
|
|
268
|
-
new Text(
|
|
269
|
-
` ${dim("Enter")} ${theme.fg("success", "Run review")} ${dim("•")} ${dim("Esc")} ${theme.fg("muted", "Cancel")} ${dim("• y/n")} ${dim("•")} ${dim("v")} ${theme.fg("accent", "inspect full review plan")} ${dim("•")} ${dim("e")} ${theme.fg("accent", "export raw prompt")}`,
|
|
270
|
-
1,
|
|
271
|
-
0,
|
|
272
|
-
),
|
|
273
|
-
);
|
|
274
|
-
container.addChild(new Spacer(1));
|
|
275
|
-
container.addChild(new DynamicBorder((s: string) => accent(s)));
|
|
276
|
-
return container;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function buildPromptPreviewHeader(theme: Theme, prompt: string): string {
|
|
280
|
-
return theme.fg(
|
|
281
|
-
"accent",
|
|
282
|
-
theme.bold(` ── Reviewer Prompt (${prompt.length.toLocaleString()} chars) ──`),
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function buildPromptPreviewBody(theme: Theme, prompt: string): string {
|
|
287
|
-
const maxPreview = 2_000;
|
|
288
|
-
if (prompt.length <= maxPreview) return prompt;
|
|
289
|
-
return `${prompt.slice(0, maxPreview)}\n\n${theme.fg("warning", `[Preview truncated — showing ${maxPreview.toLocaleString()} of ${prompt.length.toLocaleString()} total chars]`)}`;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function buildInspectorBodyLines(
|
|
293
|
-
width: number,
|
|
294
|
-
theme: Theme,
|
|
295
|
-
plan: ReviewPlan,
|
|
296
|
-
mode: InspectorMode,
|
|
297
|
-
): string[] {
|
|
298
|
-
if (mode === "raw") {
|
|
299
|
-
return wrapInspectorLines(plan.packet.prompt.split("\n"), width);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const preview = buildReviewPacketPreviewData(plan.snapshot, plan.brief.reviewInstructionBlockIds);
|
|
303
|
-
const lines = [
|
|
304
|
-
theme.fg("accent", theme.bold("Summary")),
|
|
305
|
-
`Summary: ${plan.brief.summary}`,
|
|
306
|
-
`Outcome: ${plan.brief.intendedOutcome}`,
|
|
307
|
-
"",
|
|
308
|
-
theme.fg("accent", theme.bold("Snapshot")),
|
|
309
|
-
`Model: ${plan.model.canonicalId}`,
|
|
310
|
-
`Target: ${plan.snapshot.title}`,
|
|
311
|
-
`Kind: ${formatTargetLabel(plan.snapshot.target)}`,
|
|
312
|
-
`Files: ${plan.snapshot.changedFiles.length} changed (+${plan.snapshot.stats.additions} / -${plan.snapshot.stats.deletions})`,
|
|
313
|
-
`Prompt size: ${plan.packet.prompt.length.toLocaleString()} chars`,
|
|
314
|
-
`Packet budget: ${plan.packet.charBudget.toLocaleString()} chars`,
|
|
315
|
-
"",
|
|
316
|
-
theme.fg("accent", theme.bold("Constraints")),
|
|
317
|
-
...toBulletLines(plan.brief.constraints, "No explicit constraints extracted."),
|
|
318
|
-
"",
|
|
319
|
-
theme.fg("accent", theme.bold("Focus areas")),
|
|
320
|
-
...toBulletLines(plan.brief.focusAreas, "Review overall correctness and consistency."),
|
|
321
|
-
"",
|
|
322
|
-
theme.fg("accent", theme.bold("Risky files")),
|
|
323
|
-
...toBulletLines(plan.brief.riskyFiles, "No risky files explicitly called out."),
|
|
324
|
-
];
|
|
325
|
-
|
|
326
|
-
if (plan.brief.unresolvedQuestions.length > 0) {
|
|
327
|
-
lines.push("", theme.fg("accent", theme.bold("Open questions")));
|
|
328
|
-
lines.push(
|
|
329
|
-
...toBulletLines(plan.brief.unresolvedQuestions, "No unresolved questions identified."),
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
if (preview.reviewInstructionBlocks.length > 0) {
|
|
333
|
-
lines.push("", theme.fg("accent", theme.bold("Mandatory review instructions")));
|
|
334
|
-
lines.push(
|
|
335
|
-
...preview.reviewInstructionBlocks.map((block) => `- ${block.title}: ${block.instruction}`),
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
lines.push("", theme.fg("accent", theme.bold("File overview")));
|
|
340
|
-
lines.push(
|
|
341
|
-
...preview.fileOverview.map((row) =>
|
|
342
|
-
formatFileOverviewRow(row.file, row.additions, row.deletions, row.annotations),
|
|
343
|
-
),
|
|
344
|
-
);
|
|
345
|
-
|
|
346
|
-
if (preview.snapshotNotes) {
|
|
347
|
-
lines.push("", theme.fg("accent", theme.bold("Snapshot notes")), preview.snapshotNotes);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return wrapInspectorLines(lines, width);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function wrapInspectorLines(lines: string[], width: number): string[] {
|
|
354
|
-
const wrapped: string[] = [];
|
|
355
|
-
for (const line of lines) {
|
|
356
|
-
if (line.length === 0) {
|
|
357
|
-
wrapped.push(" ");
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
for (const segment of wrapTextWithAnsi(line, width)) {
|
|
361
|
-
wrapped.push(` ${segment}`);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
return wrapped;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function toBulletLines(items: string[], fallback: string): string[] {
|
|
368
|
-
return items.length > 0 ? items.map((item) => `- ${item}`) : [`- ${fallback}`];
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function formatFileOverviewRow(
|
|
372
|
-
file: string,
|
|
373
|
-
additions: number | null,
|
|
374
|
-
deletions: number | null,
|
|
375
|
-
annotations: string[],
|
|
376
|
-
): string {
|
|
377
|
-
const stats = `+${additions ?? "?"} / -${deletions ?? "?"}`;
|
|
378
|
-
const suffix = annotations.length > 0 ? ` (${annotations.join(", ")})` : "";
|
|
379
|
-
return `- ${file} — ${stats}${suffix}`;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function formatTargetLabel(target: ReviewPlan["snapshot"]["target"]): string {
|
|
383
|
-
switch (target.kind) {
|
|
384
|
-
case "working-tree":
|
|
385
|
-
return "Working tree";
|
|
386
|
-
case "branch":
|
|
387
|
-
return `${target.base} ← current`;
|
|
388
|
-
case "commit":
|
|
389
|
-
return `commit ${target.sha.slice(0, 7)}`;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AgentReviewBatchDetails,
|
|
3
|
-
AgentReviewerResult,
|
|
4
|
-
BriefCritique,
|
|
5
|
-
PreparedAgentReviewDetails,
|
|
6
|
-
ReviewResult,
|
|
7
|
-
ReviewSnapshotSummary,
|
|
8
|
-
SynthesizedReviewBrief,
|
|
9
|
-
} from "../types.ts";
|
|
10
|
-
import { formatReviewContent } from "./format-content.ts";
|
|
11
|
-
|
|
12
|
-
/** Format a generated brief and the required main-agent quality gate. */
|
|
13
|
-
export function formatPreparedAgentReview(details: PreparedAgentReviewDetails): string {
|
|
14
|
-
return [
|
|
15
|
-
"# Review Brief Prepared",
|
|
16
|
-
"",
|
|
17
|
-
`Plan ID: ${details.planId}`,
|
|
18
|
-
`Brief prompt version: ${details.briefPromptVersion}`,
|
|
19
|
-
`Model: ${details.modelId}`,
|
|
20
|
-
`Snapshot: ${details.snapshot.title}`,
|
|
21
|
-
`Snapshot fingerprint: ${details.snapshotFingerprint}`,
|
|
22
|
-
`Files changed: ${details.snapshot.changedFiles.length}`,
|
|
23
|
-
`Diff stats: +${details.snapshot.stats.additions} / -${details.snapshot.stats.deletions}`,
|
|
24
|
-
"",
|
|
25
|
-
"## Generated brief",
|
|
26
|
-
"",
|
|
27
|
-
...formatBrief(details.generatedBrief),
|
|
28
|
-
"",
|
|
29
|
-
"## Required next step",
|
|
30
|
-
"",
|
|
31
|
-
"Critically compare this brief with the user request, session evidence, and snapshot.",
|
|
32
|
-
"Then call supi_review_run with this planId, an evidence-backed critique, and one to four reviewer assignments.",
|
|
33
|
-
'When the critique verdict is "revise", provide the full corrected revisedBrief.',
|
|
34
|
-
"Do not mutate the review target before running the prepared plan, and call supi_review_run without sibling mutation tools.",
|
|
35
|
-
"",
|
|
36
|
-
"## Changed files",
|
|
37
|
-
"",
|
|
38
|
-
...details.snapshot.changedFiles.map((file) => `- ${file}`),
|
|
39
|
-
].join("\n");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Format a completed review batch for the parent agent. */
|
|
43
|
-
export function formatAgentReviewBatch(details: AgentReviewBatchDetails): string {
|
|
44
|
-
const lines = [
|
|
45
|
-
"# Review Batch Complete",
|
|
46
|
-
"",
|
|
47
|
-
`Plan ID: ${details.evaluation.planId}`,
|
|
48
|
-
`Snapshot: ${details.snapshot.title}`,
|
|
49
|
-
`Model: ${details.evaluation.synthesizerModelId}`,
|
|
50
|
-
`Brief critique: ${details.evaluation.critique.verdict.toUpperCase()} — ${details.evaluation.critique.summary}`,
|
|
51
|
-
`Brief critique findings: ${details.evaluation.critique.findings.length}`,
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
for (const { assignment, result } of details.results) {
|
|
55
|
-
lines.push(
|
|
56
|
-
"",
|
|
57
|
-
"---",
|
|
58
|
-
"",
|
|
59
|
-
`## Reviewer: ${assignment.id}`,
|
|
60
|
-
"",
|
|
61
|
-
`Focus: ${assignment.focus}`,
|
|
62
|
-
"",
|
|
63
|
-
formatReviewContent(hydrateResult(result, details.snapshot)),
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
lines.push(
|
|
68
|
-
"",
|
|
69
|
-
"---",
|
|
70
|
-
"",
|
|
71
|
-
"## Retained main-agent brief critique",
|
|
72
|
-
"",
|
|
73
|
-
...formatCritique(details.evaluation.critique),
|
|
74
|
-
);
|
|
75
|
-
if (details.evaluation.critique.verdict === "revise") {
|
|
76
|
-
lines.push(
|
|
77
|
-
"",
|
|
78
|
-
"## Effective revised brief",
|
|
79
|
-
"",
|
|
80
|
-
...formatBrief(details.evaluation.effectiveBrief),
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return lines.join("\n");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Format one structured main-agent brief critique. */
|
|
88
|
-
export function formatCritique(critique: BriefCritique): string[] {
|
|
89
|
-
const lines = [`Verdict: ${critique.verdict.toUpperCase()}`, `Summary: ${critique.summary}`];
|
|
90
|
-
if (critique.findings.length === 0) {
|
|
91
|
-
lines.push("Findings: none");
|
|
92
|
-
return lines;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
lines.push("Findings:");
|
|
96
|
-
for (const [index, finding] of critique.findings.entries()) {
|
|
97
|
-
lines.push(
|
|
98
|
-
`${index + 1}. [${finding.kind}] ${finding.field}: ${finding.explanation}`,
|
|
99
|
-
` Evidence: ${finding.evidence}`,
|
|
100
|
-
` Proposed change: ${finding.proposedChange}`,
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
return lines;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Format all structured fields of a synthesized review brief. */
|
|
107
|
-
export function formatBrief(brief: SynthesizedReviewBrief): string[] {
|
|
108
|
-
const lines = [
|
|
109
|
-
`Summary: ${brief.summary}`,
|
|
110
|
-
`Intended outcome: ${brief.intendedOutcome}`,
|
|
111
|
-
...formatList("Constraints", brief.constraints),
|
|
112
|
-
...formatList("Focus areas", brief.focusAreas),
|
|
113
|
-
...formatList("Risky files", brief.riskyFiles),
|
|
114
|
-
...formatList("Unresolved questions", brief.unresolvedQuestions),
|
|
115
|
-
...formatList("Review instruction blocks", brief.reviewInstructionBlockIds),
|
|
116
|
-
];
|
|
117
|
-
if (brief.note) lines.push(`Note: ${brief.note}`);
|
|
118
|
-
return lines;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function formatList(label: string, values: readonly string[]): string[] {
|
|
122
|
-
if (values.length === 0) return [`${label}: none`];
|
|
123
|
-
return [`${label}:`, ...values.map((value) => `- ${value}`)];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function hydrateResult(result: AgentReviewerResult, summary: ReviewSnapshotSummary): ReviewResult {
|
|
127
|
-
const snapshot = { ...summary, diffText: "" };
|
|
128
|
-
switch (result.kind) {
|
|
129
|
-
case "success":
|
|
130
|
-
return { ...result, snapshot };
|
|
131
|
-
case "failed":
|
|
132
|
-
return { ...result, snapshot };
|
|
133
|
-
case "canceled":
|
|
134
|
-
return { ...result, snapshot };
|
|
135
|
-
case "timeout":
|
|
136
|
-
return { ...result, snapshot };
|
|
137
|
-
}
|
|
138
|
-
}
|