@mrclrchtr/supi-review 2.8.0 → 3.0.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.
Files changed (44) hide show
  1. package/README.md +71 -180
  2. package/node_modules/@mrclrchtr/supi-core/package.json +2 -1
  3. package/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
  4. package/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +40 -0
  5. package/package.json +3 -3
  6. package/src/config.ts +30 -21
  7. package/src/git-command.ts +78 -0
  8. package/src/git.ts +306 -505
  9. package/src/history/collect.ts +43 -120
  10. package/src/model.ts +3 -1
  11. package/src/review-path.ts +85 -0
  12. package/src/review-result.ts +43 -92
  13. package/src/review.ts +154 -290
  14. package/src/session/review-plan-store.ts +20 -51
  15. package/src/target/packet.ts +46 -369
  16. package/src/tool/agent-review-schemas.ts +101 -104
  17. package/src/tool/agent-review-tools.ts +268 -309
  18. package/src/tool/child-failure-diagnostics.ts +1 -1
  19. package/src/tool/child-resource-loader.ts +37 -0
  20. package/src/tool/child-session-runner.ts +83 -0
  21. package/src/tool/output-page.ts +47 -0
  22. package/src/tool/planner-runner.ts +69 -0
  23. package/src/tool/review-runner.ts +42 -257
  24. package/src/tool/review-system-prompt.ts +12 -110
  25. package/src/tool/review-tools.ts +119 -0
  26. package/src/tool/review-workflow.ts +309 -0
  27. package/src/tool/runner-helpers.ts +3 -8
  28. package/src/tool/schemas.ts +75 -62
  29. package/src/tui/common.ts +175 -0
  30. package/src/tui/prepare.ts +132 -0
  31. package/src/tui/run.ts +160 -0
  32. package/src/types.ts +153 -275
  33. package/src/history/synthesize.ts +0 -121
  34. package/src/tool/agent-review-workflow.ts +0 -398
  35. package/src/tool/brief-runner.ts +0 -180
  36. package/src/tool/guidance.ts +0 -21
  37. package/src/tool/review-handlers.ts +0 -314
  38. package/src/tool/snapshot-tools.ts +0 -117
  39. package/src/ui/flow.ts +0 -189
  40. package/src/ui/format-content.ts +0 -143
  41. package/src/ui/renderer.ts +0 -274
  42. package/src/ui/review-plan-inspector.ts +0 -391
  43. package/src/ui/review-tool-format.ts +0 -138
  44. package/src/ui/review-tool-renderer.ts +0 -234
@@ -1,143 +0,0 @@
1
- import {
2
- formatChildFailureCopy,
3
- formatChildFailureDiagnostics,
4
- } from "../tool/child-failure-diagnostics.ts";
5
- import type {
6
- BriefSynthesisRunResult,
7
- ChildFailureDiagnostics,
8
- ReviewItem,
9
- ReviewResult,
10
- } from "../types.ts";
11
-
12
- /** Format a `impact` or `effort` level value. */
13
- export function formatLevel(value: ReviewItem["impact"] | ReviewItem["effort"]): string {
14
- return `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}`;
15
- }
16
-
17
- /** Format a code location as a human-readable `file:line` or `file:start-end`. */
18
- export function formatLocation(file: string, startLine: number, endLine: number): string {
19
- return `${file}:${startLine === endLine ? startLine : `${startLine}-${endLine}`}`;
20
- }
21
-
22
- /** Format one non-success brief-synthesis result for parent-facing custom-message content. */
23
- export function formatBriefSynthesisFailureContent(
24
- result: Exclude<BriefSynthesisRunResult, { kind: "success" }>,
25
- ): string {
26
- const parts = [formatBriefSynthesisFailureCopy(result)];
27
- appendChildFailureDiagnostics(parts, result.diagnostics);
28
- return parts.join("\n");
29
- }
30
-
31
- /** Generate short static parent-facing copy for a brief-synthesis outcome. */
32
- export function formatBriefSynthesisFailureCopy(
33
- result: Exclude<BriefSynthesisRunResult, { kind: "success" }>,
34
- ): string {
35
- switch (result.kind) {
36
- case "failed":
37
- return formatChildFailureCopy("brief-synthesis", result.failureCode);
38
- case "canceled":
39
- return "Brief synthesis was canceled.";
40
- case "timeout":
41
- return "Brief synthesis timed out.";
42
- }
43
- }
44
-
45
- /** Format review results for the LLM-visible custom message content. */
46
- export function formatReviewContent(result: ReviewResult): string {
47
- switch (result.kind) {
48
- case "success":
49
- return formatSuccessContent(result);
50
- case "failed":
51
- return formatFailureContent(result);
52
- case "canceled":
53
- return formatCanceledContent(result);
54
- case "timeout":
55
- return formatTimeoutContent(result);
56
- }
57
- }
58
-
59
- function formatFailureContent(result: Extract<ReviewResult, { kind: "failed" }>): string {
60
- const parts = [formatChildFailureCopy("reviewer", result.failureCode)];
61
- appendChildFailureDiagnostics(parts, result.diagnostics);
62
- return parts.join("\n");
63
- }
64
-
65
- function formatCanceledContent(result: Extract<ReviewResult, { kind: "canceled" }>): string {
66
- const parts = ["Reviewer was canceled."];
67
- appendChildFailureDiagnostics(parts, result.diagnostics);
68
- return parts.join("\n");
69
- }
70
-
71
- function formatTimeoutContent(result: Extract<ReviewResult, { kind: "timeout" }>): string {
72
- const parts = ["Reviewer timed out."];
73
- appendChildFailureDiagnostics(parts, result.diagnostics);
74
- return parts.join("\n");
75
- }
76
-
77
- function appendChildFailureDiagnostics(
78
- parts: string[],
79
- diagnostics: ChildFailureDiagnostics | undefined,
80
- ): void {
81
- if (!diagnostics) return;
82
- parts.push("", "Diagnostics:", ...formatChildFailureDiagnostics(diagnostics));
83
- }
84
-
85
- function formatSuccessContent(result: Extract<ReviewResult, { kind: "success" }>): string {
86
- const { output } = result;
87
- const confidencePercent = Math.round(output.overall_confidence_score * 100);
88
- const lines: string[] = ["## Code Review Result", "", `**Model:** ${result.modelId}`];
89
-
90
- if (result.brief) {
91
- lines.push("", "### Session-derived Brief", "");
92
- lines.push(`**Summary:** ${result.brief.summary}`);
93
- lines.push(`**Intended outcome:** ${result.brief.intendedOutcome}`);
94
- if (result.brief.constraints.length > 0) {
95
- lines.push("**Constraints:**");
96
- lines.push(...result.brief.constraints.map((item) => `- ${item}`));
97
- }
98
- if (result.brief.focusAreas.length > 0) {
99
- lines.push("**Focus areas:**");
100
- lines.push(...result.brief.focusAreas.map((item) => `- ${item}`));
101
- }
102
- if (result.brief.riskyFiles.length > 0) {
103
- lines.push("**Risky files:**");
104
- lines.push(...result.brief.riskyFiles.map((item) => `- ${item}`));
105
- }
106
- }
107
-
108
- lines.push("", `Verdict: ${output.overall_correctness} (confidence: ${confidencePercent}%)`);
109
- lines.push(
110
- `Summary: ${output.summary.actions.mustFix} must-fix, ${output.summary.actions.shouldFix} should-fix, ${output.summary.actions.consider} consider`,
111
- );
112
-
113
- if (output.items.length > 0) {
114
- lines.push("", "### Review Items", "", ...formatReviewItems(output.items));
115
- }
116
-
117
- lines.push("", `Overall: ${output.overall_explanation}`);
118
- return lines.join("\n");
119
- }
120
-
121
- function formatReviewItems(items: ReviewItem[]): string[] {
122
- return items.flatMap((item, index) => {
123
- const location = item.code_location
124
- ? ` ${formatLocation(item.code_location.absolute_file_path, item.code_location.line_range.start, item.code_location.line_range.end)}`
125
- : undefined;
126
-
127
- const lines = [
128
- ` #${index + 1} ${item.title} [${item.recommended_action}]`,
129
- ` Category: ${item.category}`,
130
- ` Impact: ${formatLevel(item.impact)}`,
131
- ` Effort: ${formatLevel(item.effort)}`,
132
- ];
133
-
134
- if (location) {
135
- lines.push(location);
136
- }
137
-
138
- lines.push(` ${item.body}`);
139
- lines.push(` Suggested fix: ${item.suggested_fix}`);
140
- lines.push(` Verification: ${item.verification_hint}`);
141
- return lines;
142
- });
143
- }
@@ -1,274 +0,0 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import { Box, Container, Spacer, Text } from "@earendil-works/pi-tui";
3
- import {
4
- formatChildFailureCopy,
5
- formatChildFailureDiagnostics,
6
- } from "../tool/child-failure-diagnostics.ts";
7
- import type {
8
- BriefSynthesisRunResult,
9
- ChildFailureDiagnostics,
10
- ReviewItem,
11
- ReviewResult,
12
- ReviewSnapshot,
13
- } from "../types.ts";
14
- import { formatBriefSynthesisFailureCopy, formatLevel, formatLocation } from "./format-content.ts";
15
-
16
- /** Register the custom TUI renderer for `supi-review` messages. */
17
- interface BriefSynthesisFailureMessage {
18
- result: Exclude<BriefSynthesisRunResult, { kind: "success" }>;
19
- snapshot: ReviewSnapshot;
20
- modelId: string;
21
- }
22
-
23
- interface ReviewMessageDetails {
24
- result?: ReviewResult;
25
- briefSynthesisFailure?: BriefSynthesisFailureMessage;
26
- }
27
-
28
- export function registerReviewRenderer(pi: ExtensionAPI): void {
29
- pi.registerMessageRenderer("supi-review", (message, { expanded }, theme) => {
30
- const details = message.details as ReviewMessageDetails | undefined;
31
- if (details?.briefSynthesisFailure) {
32
- return renderBriefSynthesisFailure(details.briefSynthesisFailure, theme);
33
- }
34
-
35
- const result = details?.result;
36
- if (!result) {
37
- return new Text(theme.fg("dim", "No review data"), 1, 0);
38
- }
39
-
40
- switch (result.kind) {
41
- case "success":
42
- return renderSuccess(result, theme, expanded);
43
- case "failed":
44
- return renderFailed(result, theme);
45
- case "canceled":
46
- return renderCanceled(result, theme);
47
- case "timeout":
48
- return renderTimeout(result, theme);
49
- default:
50
- return new Text(theme.fg("dim", "Unknown review state"), 1, 0);
51
- }
52
- });
53
- }
54
-
55
- function renderBriefSynthesisFailure(
56
- failure: BriefSynthesisFailureMessage,
57
- theme: Parameters<Parameters<ExtensionAPI["registerMessageRenderer"]>[1]>[2],
58
- ): Container {
59
- const container = new Container();
60
- const title =
61
- failure.result.kind === "failed"
62
- ? "◆ Brief Synthesis Failed"
63
- : failure.result.kind === "timeout"
64
- ? "◆ Brief Synthesis Timed Out"
65
- : "◆ Brief Synthesis Canceled";
66
- const color = failure.result.kind === "failed" ? "error" : "warning";
67
-
68
- container.addChild(new Text(theme.fg(color, title), 1, 0));
69
- container.addChild(new Spacer(1));
70
- container.addChild(new Text(theme.fg("muted", `Model: ${failure.modelId}`), 1, 0));
71
- container.addChild(new Text(theme.fg("muted", `Snapshot: ${failure.snapshot.title}`), 1, 0));
72
- container.addChild(new Spacer(1));
73
- container.addChild(
74
- new Text(theme.fg(color, formatBriefSynthesisFailureCopy(failure.result)), 1, 0),
75
- );
76
- renderChildFailureDiagnostics(container, failure.result.diagnostics, theme);
77
- return container;
78
- }
79
-
80
- function renderSuccess(
81
- result: Extract<ReviewResult, { kind: "success" }>,
82
- theme: Parameters<Parameters<ExtensionAPI["registerMessageRenderer"]>[1]>[2],
83
- expanded: boolean,
84
- ): Container {
85
- const container = new Container();
86
- const output = result.output;
87
-
88
- container.addChild(new Text(theme.fg("accent", "◆ Code Review Results"), 1, 0));
89
- container.addChild(new Text(theme.fg("muted", `Model: ${result.modelId}`), 1, 0));
90
- container.addChild(new Text(theme.fg("muted", `Snapshot: ${result.snapshot.title}`), 1, 0));
91
- container.addChild(new Spacer(1));
92
-
93
- if (result.brief) {
94
- renderBriefContext(container, result.brief, theme);
95
- container.addChild(new Spacer(1));
96
- }
97
-
98
- const normalizedVerdict = output.overall_correctness.toLowerCase();
99
- const verdictColor = normalizedVerdict.includes("issues") ? "warning" : "success";
100
- container.addChild(
101
- new Text(
102
- `${theme.fg(verdictColor, "●")} ${theme.fg(verdictColor, output.overall_correctness)}` +
103
- theme.fg("dim", ` (confidence: ${(output.overall_confidence_score * 100).toFixed(0)}%)`),
104
- 1,
105
- 0,
106
- ),
107
- );
108
- container.addChild(
109
- new Text(
110
- theme.fg(
111
- "dim",
112
- `Summary: ${output.summary.actions.mustFix} must-fix, ${output.summary.actions.shouldFix} should-fix, ${output.summary.actions.consider} consider`,
113
- ),
114
- 1,
115
- 0,
116
- ),
117
- );
118
-
119
- if (output.overall_explanation) {
120
- container.addChild(new Spacer(1));
121
- container.addChild(new Text(theme.fg("dim", output.overall_explanation), 1, 0));
122
- }
123
-
124
- if (expanded) {
125
- container.addChild(new Spacer(1));
126
-
127
- if (output.items.length === 0) {
128
- container.addChild(new Text(theme.fg("success", "✓ No review items"), 1, 0));
129
- } else {
130
- container.addChild(
131
- new Text(theme.fg("accent", `Review Items (${output.items.length})`), 1, 0),
132
- );
133
- output.items.forEach((item, index) => {
134
- container.addChild(renderReviewItem(item, index, theme));
135
- });
136
- }
137
- }
138
-
139
- return container;
140
- }
141
-
142
- function renderBriefContext(
143
- container: Container,
144
- brief: NonNullable<Extract<ReviewResult, { kind: "success" }>["brief"]>,
145
- theme: Parameters<Parameters<ExtensionAPI["registerMessageRenderer"]>[1]>[2],
146
- ): void {
147
- container.addChild(new Text(theme.fg("muted", `Summary: ${brief.summary}`), 1, 0));
148
- container.addChild(new Text(theme.fg("muted", `Outcome: ${brief.intendedOutcome}`), 1, 0));
149
- if (brief.focusAreas.length > 0) {
150
- container.addChild(
151
- new Text(theme.fg("muted", `Focus: ${brief.focusAreas.slice(0, 3).join(", ")}`), 1, 0),
152
- );
153
- }
154
- if (brief.riskyFiles.length > 0) {
155
- container.addChild(
156
- new Text(theme.fg("muted", `Risky files: ${brief.riskyFiles.slice(0, 3).join(", ")}`), 1, 0),
157
- );
158
- }
159
- }
160
-
161
- function renderReviewItem(
162
- item: ReviewItem,
163
- index: number,
164
- theme: Parameters<Parameters<ExtensionAPI["registerMessageRenderer"]>[1]>[2],
165
- ): Container {
166
- const container = new Container();
167
- const actionColor = actionColorName(item.recommended_action);
168
- const locationText = item.code_location
169
- ? formatLocation(
170
- item.code_location.absolute_file_path,
171
- item.code_location.line_range.start,
172
- item.code_location.line_range.end,
173
- )
174
- : undefined;
175
-
176
- container.addChild(new Spacer(1));
177
- container.addChild(
178
- new Text(
179
- `${theme.fg("text", `#${index + 1} ${item.title}`)} ${theme.fg(actionColor, `[${item.recommended_action}]`)}`,
180
- 1,
181
- 0,
182
- ),
183
- );
184
- container.addChild(new Text(theme.fg("dim", `Category: ${item.category}`), 2, 0));
185
- container.addChild(new Text(theme.fg("dim", `Impact: ${formatLevel(item.impact)}`), 2, 0));
186
- container.addChild(new Text(theme.fg("dim", `Effort: ${formatLevel(item.effort)}`), 2, 0));
187
-
188
- if (locationText) {
189
- container.addChild(new Text(theme.fg("dim", locationText), 2, 0));
190
- }
191
-
192
- if (item.body) {
193
- const body = new Box(2, 0);
194
- body.addChild(new Text(theme.fg("text", item.body), 0, 0));
195
- container.addChild(body);
196
- }
197
-
198
- container.addChild(new Text(theme.fg("dim", `Suggested fix: ${item.suggested_fix}`), 2, 0));
199
- container.addChild(new Text(theme.fg("dim", `Verification: ${item.verification_hint}`), 2, 0));
200
-
201
- return container;
202
- }
203
-
204
- function renderFailed(
205
- result: Extract<ReviewResult, { kind: "failed" }>,
206
- theme: Parameters<Parameters<ExtensionAPI["registerMessageRenderer"]>[1]>[2],
207
- ): Container {
208
- const container = new Container();
209
- container.addChild(new Text(theme.fg("error", "◆ Review Failed"), 1, 0));
210
- container.addChild(new Spacer(1));
211
- container.addChild(new Text(theme.fg("muted", `Model: ${result.modelId}`), 1, 0));
212
- container.addChild(new Text(theme.fg("muted", `Snapshot: ${result.snapshot.title}`), 1, 0));
213
- container.addChild(new Spacer(1));
214
- container.addChild(
215
- new Text(theme.fg("error", formatChildFailureCopy("reviewer", result.failureCode)), 1, 0),
216
- );
217
- renderChildFailureDiagnostics(container, result.diagnostics, theme);
218
- return container;
219
- }
220
-
221
- function renderTimeout(
222
- result: Extract<ReviewResult, { kind: "timeout" }>,
223
- theme: Parameters<Parameters<ExtensionAPI["registerMessageRenderer"]>[1]>[2],
224
- ): Container {
225
- const container = new Container();
226
- container.addChild(new Text(theme.fg("warning", "◆ Review Timed Out"), 1, 0));
227
- container.addChild(new Spacer(1));
228
- container.addChild(new Text(theme.fg("muted", `Model: ${result.modelId}`), 1, 0));
229
- container.addChild(new Text(theme.fg("muted", `Snapshot: ${result.snapshot.title}`), 1, 0));
230
- container.addChild(new Spacer(1));
231
- container.addChild(new Text(theme.fg("warning", "Reviewer timed out."), 1, 0));
232
- renderChildFailureDiagnostics(container, result.diagnostics, theme);
233
- return container;
234
- }
235
-
236
- function renderCanceled(
237
- result: Extract<ReviewResult, { kind: "canceled" }>,
238
- theme: Parameters<Parameters<ExtensionAPI["registerMessageRenderer"]>[1]>[2],
239
- ): Container {
240
- const container = new Container();
241
- container.addChild(new Text(theme.fg("warning", "◆ Review Canceled"), 1, 0));
242
- container.addChild(new Text(theme.fg("warning", "Reviewer was canceled."), 1, 0));
243
- renderChildFailureDiagnostics(container, result.diagnostics, theme);
244
- return container;
245
- }
246
-
247
- function renderChildFailureDiagnostics(
248
- container: Container,
249
- diagnostics: ChildFailureDiagnostics | undefined,
250
- theme: Parameters<Parameters<ExtensionAPI["registerMessageRenderer"]>[1]>[2],
251
- ): void {
252
- if (!diagnostics) return;
253
-
254
- container.addChild(new Spacer(1));
255
- container.addChild(new Text(theme.fg("dim", "Diagnostics:"), 1, 0));
256
- for (const line of formatChildFailureDiagnostics(diagnostics)) {
257
- container.addChild(new Text(theme.fg("dim", line), 1, 0));
258
- }
259
- }
260
-
261
- function actionColorName(
262
- action: ReviewItem["recommended_action"],
263
- ): "success" | "warning" | "error" {
264
- switch (action) {
265
- case "must-fix":
266
- return "error";
267
- case "should-fix":
268
- return "warning";
269
- case "consider":
270
- return "success";
271
- default:
272
- return "success";
273
- }
274
- }