@mrclrchtr/supi-review 1.6.0 → 1.7.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/package.json +1 -1
- package/src/review.ts +53 -13
- package/src/target/packet.ts +121 -3
- package/src/tool/brief-runner.ts +1 -1
- package/src/tool/review-runner.ts +20 -3
package/package.json
CHANGED
package/src/review.ts
CHANGED
|
@@ -260,20 +260,60 @@ function maybeQueueReviewFollowUp(pi: ExtensionAPI, result: ReviewResult): void
|
|
|
260
260
|
function buildReviewFollowUpInstruction(
|
|
261
261
|
result: Extract<ReviewResult, { kind: "success" }>,
|
|
262
262
|
): string {
|
|
263
|
-
const findings = result.output
|
|
264
|
-
|
|
265
|
-
);
|
|
263
|
+
const { findings, overall_correctness } = result.output;
|
|
264
|
+
const criticalCount = findings.filter((f) => f.priority === 3).length;
|
|
265
|
+
const majorCount = findings.filter((f) => f.priority === 2).length;
|
|
266
|
+
|
|
267
|
+
const findingList = findings.map((finding, index) => `- #${index + 1}: ${finding.title}`);
|
|
266
268
|
|
|
267
|
-
|
|
268
|
-
"A code review just completed and the result is available in the preceding `supi-review` message."
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
"If the user chooses Verify findings, verify the findings first and then ask again whether to Fix all or Fix selected.",
|
|
269
|
+
const header =
|
|
270
|
+
"A code review just completed and the result is available in the preceding `supi-review` message.";
|
|
271
|
+
const noFixing = "Do not start fixing code immediately.";
|
|
272
|
+
const useAskUser = "If the `ask_user` tool is available, use it for this decision.";
|
|
273
|
+
|
|
274
|
+
const appendFindings = (lines: string[]): string[] => [
|
|
275
|
+
...lines,
|
|
275
276
|
"",
|
|
276
277
|
"Current findings:",
|
|
277
|
-
...
|
|
278
|
-
]
|
|
278
|
+
...findingList,
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
if (criticalCount > 0) {
|
|
282
|
+
return appendFindings([
|
|
283
|
+
header,
|
|
284
|
+
`⚠️ ${criticalCount} critical finding(s) found. Urge the user to fix before merging.`,
|
|
285
|
+
noFixing,
|
|
286
|
+
useAskUser,
|
|
287
|
+
"Offer these options: Fix all, Fix critical only, Done.",
|
|
288
|
+
]).join("\n");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const contradictionNote =
|
|
292
|
+
overall_correctness === "patch is correct" && findings.length > 0
|
|
293
|
+
? "The reviewer marked the patch as correct but found issues — verify the verdict before acting."
|
|
294
|
+
: undefined;
|
|
295
|
+
|
|
296
|
+
if (majorCount > 0) {
|
|
297
|
+
const lines: string[] = [header];
|
|
298
|
+
if (contradictionNote) lines.push(contradictionNote);
|
|
299
|
+
lines.push(
|
|
300
|
+
noFixing,
|
|
301
|
+
useAskUser,
|
|
302
|
+
"Offer exactly these options: Done, Fix all, Fix selected, Verify findings.",
|
|
303
|
+
"If the user chooses Fix selected, ask a follow-up question listing the findings by number/title.",
|
|
304
|
+
"If the user chooses Verify findings, verify the findings first and then ask again whether to Fix all or Fix selected.",
|
|
305
|
+
);
|
|
306
|
+
return appendFindings(lines).join("\n");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Only minor/info findings
|
|
310
|
+
return appendFindings([
|
|
311
|
+
header,
|
|
312
|
+
...(contradictionNote
|
|
313
|
+
? [contradictionNote]
|
|
314
|
+
: ["Only minor/info suggestions — no blocking issues."]),
|
|
315
|
+
noFixing,
|
|
316
|
+
useAskUser,
|
|
317
|
+
"Offer these options: Apply suggestions, Skip.",
|
|
318
|
+
]).join("\n");
|
|
279
319
|
}
|
package/src/target/packet.ts
CHANGED
|
@@ -5,9 +5,11 @@ import type {
|
|
|
5
5
|
SynthesizedReviewBrief,
|
|
6
6
|
} from "../types.ts";
|
|
7
7
|
|
|
8
|
-
interface DiffSection {
|
|
8
|
+
export interface DiffSection {
|
|
9
9
|
file: string;
|
|
10
10
|
text: string;
|
|
11
|
+
additions: number;
|
|
12
|
+
deletions: number;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
/** Build the final prompt packet for the reviewer child session. */
|
|
@@ -49,6 +51,8 @@ export function buildReviewPacket(
|
|
|
49
51
|
...snapshot.changedFiles.map((file) => `- ${file}`),
|
|
50
52
|
];
|
|
51
53
|
|
|
54
|
+
baseParts.push("", buildFileOverviewTable(snapshot.changedFiles, sections));
|
|
55
|
+
|
|
52
56
|
if (preamble.trim()) {
|
|
53
57
|
baseParts.push("", "## Snapshot notes", truncate(preamble.trim(), 1_500));
|
|
54
58
|
}
|
|
@@ -115,7 +119,21 @@ export function getPacketCharBudget(model: ReviewModelSelection): number {
|
|
|
115
119
|
return tokenBudget * 4;
|
|
116
120
|
}
|
|
117
121
|
|
|
118
|
-
function
|
|
122
|
+
function countDiffLines(lines: string[]): { additions: number; deletions: number } {
|
|
123
|
+
let additions = 0;
|
|
124
|
+
let deletions = 0;
|
|
125
|
+
for (const line of lines) {
|
|
126
|
+
// Skip diff header lines (e.g. "--- a/file", "+++ b/file").
|
|
127
|
+
// The trailing space ensures we do not skip content lines whose first
|
|
128
|
+
// characters happen to be "+++" or "---".
|
|
129
|
+
if (line.startsWith("--- ") || line.startsWith("+++ ")) continue;
|
|
130
|
+
if (line.startsWith("+")) additions++;
|
|
131
|
+
else if (line.startsWith("-")) deletions++;
|
|
132
|
+
}
|
|
133
|
+
return { additions, deletions };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function splitDiffSections(text: string): { preamble: string; sections: DiffSection[] } {
|
|
119
137
|
const lines = text.split("\n");
|
|
120
138
|
const preamble: string[] = [];
|
|
121
139
|
const sections: DiffSection[] = [];
|
|
@@ -128,7 +146,13 @@ function splitDiffSections(text: string): { preamble: string; sections: DiffSect
|
|
|
128
146
|
currentFile = undefined;
|
|
129
147
|
return;
|
|
130
148
|
}
|
|
131
|
-
|
|
149
|
+
const stats = countDiffLines(current);
|
|
150
|
+
sections.push({
|
|
151
|
+
file: currentFile,
|
|
152
|
+
text: current.join("\n").trimEnd(),
|
|
153
|
+
additions: stats.additions,
|
|
154
|
+
deletions: stats.deletions,
|
|
155
|
+
});
|
|
132
156
|
current = [];
|
|
133
157
|
currentFile = undefined;
|
|
134
158
|
};
|
|
@@ -206,6 +230,100 @@ function toPathTokens(path: string): string[] {
|
|
|
206
230
|
.filter((part) => part.length >= 3);
|
|
207
231
|
}
|
|
208
232
|
|
|
233
|
+
/** Categorize a file path for skip-list annotation, or undefined if it should be reviewed. */
|
|
234
|
+
export function classifySkipCategory(file: string): string | undefined {
|
|
235
|
+
const lockfiles = new Set([
|
|
236
|
+
"package-lock.json",
|
|
237
|
+
"yarn.lock",
|
|
238
|
+
"pnpm-lock.yaml",
|
|
239
|
+
"Gemfile.lock",
|
|
240
|
+
"Cargo.lock",
|
|
241
|
+
"poetry.lock",
|
|
242
|
+
"composer.lock",
|
|
243
|
+
]);
|
|
244
|
+
|
|
245
|
+
const name = file.split("/").pop() ?? file;
|
|
246
|
+
if (lockfiles.has(name)) return "lockfile";
|
|
247
|
+
if (name.startsWith("CHANGELOG") || name.startsWith("CHANGES") || name === "CHANGE_LOG") {
|
|
248
|
+
return "changelog";
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (file.includes("__snapshots__/") || name.endsWith(".snap")) {
|
|
252
|
+
return "snapshot";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (
|
|
256
|
+
file.includes("dist/") ||
|
|
257
|
+
file.includes("build/") ||
|
|
258
|
+
file.includes(".next/") ||
|
|
259
|
+
file.includes("__generated__/")
|
|
260
|
+
) {
|
|
261
|
+
return "generated";
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (file.includes("vendor/") || file.includes("third_party/")) {
|
|
265
|
+
return "vendored";
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (name.endsWith(".min.js") || name.endsWith(".min.css")) return "generated";
|
|
269
|
+
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function formatOverviewRow(
|
|
274
|
+
file: string,
|
|
275
|
+
statsMap: Map<string, { additions: number; deletions: number }>,
|
|
276
|
+
binaryFiles: Set<string>,
|
|
277
|
+
): string {
|
|
278
|
+
const stats = statsMap.get(file);
|
|
279
|
+
const skipCategory = classifySkipCategory(file);
|
|
280
|
+
const annotations: string[] = [];
|
|
281
|
+
|
|
282
|
+
if (!stats) {
|
|
283
|
+
// File in the changed-files manifest but not in the parsed diff sections.
|
|
284
|
+
// This happens for untracked files in working-tree snapshots, which
|
|
285
|
+
// have no diff section to parse. Do not guess 0/0 or mark as trivial.
|
|
286
|
+
if (skipCategory) annotations.push(`skip — ${skipCategory}`);
|
|
287
|
+
const annotation = annotations.length > 0 ? ` (${annotations.join(", ")})` : "";
|
|
288
|
+
return `| ${file} | ? | ?${annotation} |`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (binaryFiles.has(file)) {
|
|
292
|
+
// Binary diffs have no diffable +/- lines. Show unknown stats.
|
|
293
|
+
if (skipCategory) annotations.push(`skip — ${skipCategory}`);
|
|
294
|
+
annotations.push("binary");
|
|
295
|
+
const annotation = annotations.length > 0 ? ` (${annotations.join(", ")})` : "";
|
|
296
|
+
return `| ${file} | ? | ?${annotation} |`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const total = stats.additions + stats.deletions;
|
|
300
|
+
if (total < 5) annotations.push("trivial");
|
|
301
|
+
if (skipCategory) annotations.push(`skip — ${skipCategory}`);
|
|
302
|
+
const annotation = annotations.length > 0 ? ` (${annotations.join(", ")})` : "";
|
|
303
|
+
return `| ${file} | ${stats.additions} | ${stats.deletions}${annotation} |`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function buildFileOverviewTable(changedFiles: string[], sections: DiffSection[]): string {
|
|
307
|
+
const statsMap = new Map<string, { additions: number; deletions: number }>();
|
|
308
|
+
const binaryFiles = new Set<string>();
|
|
309
|
+
for (const section of sections) {
|
|
310
|
+
const existing = statsMap.get(section.file);
|
|
311
|
+
statsMap.set(section.file, {
|
|
312
|
+
additions: (existing?.additions ?? 0) + section.additions,
|
|
313
|
+
deletions: (existing?.deletions ?? 0) + section.deletions,
|
|
314
|
+
});
|
|
315
|
+
if (section.text.includes("Binary files ")) {
|
|
316
|
+
binaryFiles.add(section.file);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const header = "| File | +Add | -Del |";
|
|
321
|
+
const separator = "|---|---|---|";
|
|
322
|
+
const rows = changedFiles.map((file) => formatOverviewRow(file, statsMap, binaryFiles));
|
|
323
|
+
|
|
324
|
+
return [`## File overview`, "", header, separator, ...rows].join("\n");
|
|
325
|
+
}
|
|
326
|
+
|
|
209
327
|
function toBullets(items: string[], fallback: string): string[] {
|
|
210
328
|
return items.length > 0 ? items.map((item) => `- ${item}`) : [fallback];
|
|
211
329
|
}
|
package/src/tool/brief-runner.ts
CHANGED
|
@@ -69,7 +69,7 @@ async function createBriefSession(
|
|
|
69
69
|
cwd: invocation.cwd,
|
|
70
70
|
model: invocation.model,
|
|
71
71
|
modelRegistry: invocation.modelRegistry,
|
|
72
|
-
thinkingLevel: clampThinkingLevel(invocation.model, "
|
|
72
|
+
thinkingLevel: clampThinkingLevel(invocation.model, "xhigh"),
|
|
73
73
|
tools: ["submit_review_brief"],
|
|
74
74
|
customTools: [submitBriefTool],
|
|
75
75
|
resourceLoader,
|
|
@@ -65,9 +65,21 @@ export function buildReviewerSystemPrompt(): string {
|
|
|
65
65
|
"- Title: concise and specific.",
|
|
66
66
|
"- Body: explain the issue, why it matters, and a concrete fix direction.",
|
|
67
67
|
"- code_location: 1-based inclusive line range.",
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
68
|
+
"",
|
|
69
|
+
"--- Finding calibration ---",
|
|
70
|
+
"Priority:",
|
|
71
|
+
" 0 (info): style nits, naming suggestions, non-functional improvements without real impact",
|
|
72
|
+
" 1 (minor): unlikely edge cases, minor perf concerns without benchmarks, missing comments",
|
|
73
|
+
" 2 (major): logic errors, incorrect error handling, API contract violations, race conditions",
|
|
74
|
+
" 3 (critical): security vulnerabilities, data loss/corruption, crashes, breaking changes",
|
|
75
|
+
"Confidence:",
|
|
76
|
+
" 0.8-1.0: you verified the issue by reading surrounding code or grepping the codebase",
|
|
77
|
+
" 0.5-0.8: suspected from the diff, plausible but not fully verified",
|
|
78
|
+
" <0.5: do not report — too uncertain; either verify further or drop it",
|
|
79
|
+
"overall_correctness:",
|
|
80
|
+
" 'patch is correct': no issues found, or only info-level suggestions",
|
|
81
|
+
" 'mostly correct': minor issues present, safe to merge with fixes",
|
|
82
|
+
" 'patch is incorrect': major/critical issues that should block merge",
|
|
71
83
|
"",
|
|
72
84
|
"--- Tool strategy ---",
|
|
73
85
|
"- Use read to inspect full files when the inline diff lacks context.",
|
|
@@ -75,6 +87,11 @@ export function buildReviewerSystemPrompt(): string {
|
|
|
75
87
|
"- Use find to locate related files quickly.",
|
|
76
88
|
"- Use ls when you need a quick directory overview.",
|
|
77
89
|
"",
|
|
90
|
+
"--- Skipped files ---",
|
|
91
|
+
"- Skip reviewing: lockfiles, generated/bundled code (dist/, .next/, __generated__/),",
|
|
92
|
+
" vendored dependencies, changelogs, snapshot files, minified bundles, and binary files.",
|
|
93
|
+
"- Focus on application source and test code.",
|
|
94
|
+
"",
|
|
78
95
|
"Do NOT output JSON directly — call submit_review with the structured result.",
|
|
79
96
|
].join("\n");
|
|
80
97
|
}
|