mitsupi 1.4.0 → 1.6.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/AGENTS.md +1 -1
- package/CHANGELOG.md +24 -0
- package/README.md +28 -18
- package/analyze-edits.py +232 -0
- package/distributions/README.md +8 -0
- package/distributions/mitsupi-common/README.md +11 -0
- package/distributions/mitsupi-common/package.json +95 -0
- package/distributions/mitsupi-loaded/README.md +13 -0
- package/distributions/mitsupi-loaded/package.json +38 -0
- package/{pi-extensions → extensions}/answer.ts +11 -11
- package/extensions/btw.ts +963 -0
- package/{pi-extensions → extensions}/context.ts +2 -2
- package/{pi-extensions → extensions}/control.ts +20 -13
- package/{pi-extensions → extensions}/files.ts +6 -8
- package/{pi-extensions → extensions}/go-to-bed.ts +2 -2
- package/{pi-extensions → extensions}/loop.ts +18 -11
- package/extensions/multi-edit.ts +871 -0
- package/{pi-extensions → extensions}/review.ts +254 -82
- package/{pi-extensions → extensions}/session-breakdown.ts +551 -74
- package/extensions/split-fork.ts +130 -0
- package/{pi-extensions → extensions}/todos.ts +41 -34
- package/extensions/uv.ts +123 -0
- package/intercepted-commands/poetry +8 -1
- package/intercepted-commands/python +42 -2
- package/intercepted-commands/python3 +42 -2
- package/package.json +6 -3
- package/skills/google-workspace/SKILL.md +53 -8
- package/skills/google-workspace/scripts/auth.js +106 -28
- package/skills/google-workspace/scripts/common.js +201 -32
- package/skills/google-workspace/scripts/workspace.js +64 -9
- package/skills/native-web-search/SKILL.md +2 -0
- package/skills/native-web-search/search.mjs +102 -15
- package/skills/pi-share/SKILL.md +5 -2
- package/skills/pi-share/fetch-session.mjs +46 -15
- package/skills/summarize/SKILL.md +4 -3
- package/skills/summarize/to-markdown.mjs +3 -1
- package/skills/web-browser/SKILL.md +6 -0
- package/skills/web-browser/scripts/start.js +75 -27
- package/pi-extensions/uv.ts +0 -33
- /package/{pi-extensions → extensions}/notify.ts +0 -0
- /package/{pi-extensions → extensions}/prompt-editor.ts +0 -0
- /package/{pi-extensions → extensions}/whimsical.ts +0 -0
- /package/{pi-themes → themes}/nightowl.json +0 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - Review against a base branch (PR style)
|
|
8
8
|
* - Review uncommitted changes
|
|
9
9
|
* - Review a specific commit
|
|
10
|
-
* -
|
|
10
|
+
* - Shared custom review instructions (applied to all review modes when configured)
|
|
11
11
|
*
|
|
12
12
|
* Usage:
|
|
13
13
|
* - `/review` - show interactive selector
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* - `/review branch main` - review against main branch
|
|
18
18
|
* - `/review commit abc123` - review specific commit
|
|
19
19
|
* - `/review folder src docs` - review specific folders/files (snapshot, not diff)
|
|
20
|
-
* - `/review custom
|
|
20
|
+
* - `/review` selector includes Add/Remove custom review instructions (applies to all modes)
|
|
21
|
+
* - `/review --extra "focus on performance regressions"` - add extra review instruction (works with any mode)
|
|
21
22
|
*
|
|
22
23
|
* Project-specific review guidelines:
|
|
23
24
|
* - If a REVIEW_GUIDELINES.md file exists in the same directory as .pi,
|
|
@@ -31,7 +32,6 @@ import { DynamicBorder, BorderedLoader } from "@mariozechner/pi-coding-agent";
|
|
|
31
32
|
import {
|
|
32
33
|
Container,
|
|
33
34
|
fuzzyFilter,
|
|
34
|
-
getEditorKeybindings,
|
|
35
35
|
Input,
|
|
36
36
|
type SelectItem,
|
|
37
37
|
SelectList,
|
|
@@ -47,6 +47,7 @@ import { promises as fs } from "node:fs";
|
|
|
47
47
|
let reviewOriginId: string | undefined = undefined;
|
|
48
48
|
let endReviewInProgress = false;
|
|
49
49
|
let reviewLoopFixingEnabled = false;
|
|
50
|
+
let reviewCustomInstructions: string | undefined = undefined;
|
|
50
51
|
let reviewLoopInProgress = false;
|
|
51
52
|
|
|
52
53
|
const REVIEW_STATE_TYPE = "review-session";
|
|
@@ -63,6 +64,7 @@ type ReviewSessionState = {
|
|
|
63
64
|
|
|
64
65
|
type ReviewSettingsState = {
|
|
65
66
|
loopFixingEnabled?: boolean;
|
|
67
|
+
customInstructions?: string;
|
|
66
68
|
};
|
|
67
69
|
|
|
68
70
|
function setReviewWidget(ctx: ExtensionContext, active: boolean) {
|
|
@@ -124,12 +126,14 @@ function getReviewSettings(ctx: ExtensionContext): ReviewSettingsState {
|
|
|
124
126
|
|
|
125
127
|
return {
|
|
126
128
|
loopFixingEnabled: state?.loopFixingEnabled === true,
|
|
129
|
+
customInstructions: state?.customInstructions?.trim() || undefined,
|
|
127
130
|
};
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
function applyReviewSettings(ctx: ExtensionContext) {
|
|
131
134
|
const state = getReviewSettings(ctx);
|
|
132
135
|
reviewLoopFixingEnabled = state.loopFixingEnabled === true;
|
|
136
|
+
reviewCustomInstructions = state.customInstructions?.trim() || undefined;
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
function parseMarkdownHeading(line: string): { level: number; title: string } | null {
|
|
@@ -351,7 +355,6 @@ type ReviewTarget =
|
|
|
351
355
|
| { type: "uncommitted" }
|
|
352
356
|
| { type: "baseBranch"; branch: string }
|
|
353
357
|
| { type: "commit"; sha: string; title?: string }
|
|
354
|
-
| { type: "custom"; instructions: string }
|
|
355
358
|
| { type: "pullRequest"; prNumber: number; baseBranch: string; title: string }
|
|
356
359
|
| { type: "folder"; paths: string[] };
|
|
357
360
|
|
|
@@ -401,6 +404,7 @@ Flag issues that:
|
|
|
401
404
|
7. Have provable impact on other parts of the code — it is not enough to speculate that a change may disrupt another part, you must identify the parts that are provably affected.
|
|
402
405
|
8. Are clearly not intentional changes by the author.
|
|
403
406
|
9. Be particularly careful with untrusted user input and follow the specific guidelines to review.
|
|
407
|
+
10. Treat silent local error recovery (especially parsing/IO/network fallbacks) as high-signal review candidates unless there is explicit boundary-level justification.
|
|
404
408
|
|
|
405
409
|
## Untrusted User Input
|
|
406
410
|
|
|
@@ -423,13 +427,46 @@ Flag issues that:
|
|
|
423
427
|
|
|
424
428
|
## Review priorities
|
|
425
429
|
|
|
426
|
-
1.
|
|
430
|
+
1. Surface critical non-blocking human callouts (migrations, dependency churn, auth/permissions, compatibility, destructive operations) at the end.
|
|
427
431
|
2. Prefer simple, direct solutions over wrappers or abstractions without clear value.
|
|
428
|
-
3.
|
|
429
|
-
4.
|
|
430
|
-
5.
|
|
431
|
-
|
|
432
|
-
|
|
432
|
+
3. Treat back pressure handling as critical to system stability.
|
|
433
|
+
4. Apply system-level thinking; flag changes that increase operational risk or on-call wakeups.
|
|
434
|
+
5. Ensure that errors are always checked against codes or stable identifiers, never error messages.
|
|
435
|
+
|
|
436
|
+
## Fail-fast error handling (strict)
|
|
437
|
+
|
|
438
|
+
When reviewing added or modified error handling, default to fail-fast behavior.
|
|
439
|
+
|
|
440
|
+
1. Evaluate every new or changed \`try/catch\`: identify what can fail and why local handling is correct at that exact layer.
|
|
441
|
+
2. Prefer propagation over local recovery. If the current scope cannot fully recover while preserving correctness, rethrow (optionally with context) instead of returning fallbacks.
|
|
442
|
+
3. Flag catch blocks that hide failure signals (e.g. returning \`null\`/\`[]\`/\`false\`, swallowing JSON parse failures, logging-and-continue, or “best effort” silent recovery).
|
|
443
|
+
4. JSON parsing/decoding should fail loudly by default. Quiet fallback parsing is only acceptable with an explicit compatibility requirement and clear tested behavior.
|
|
444
|
+
5. Boundary handlers (HTTP routes, CLI entrypoints, supervisors) may translate errors, but must not pretend success or silently degrade.
|
|
445
|
+
6. If a catch exists only to satisfy lint/style without real handling, treat it as a bug.
|
|
446
|
+
7. When uncertain, prefer crashing fast over silent degradation.
|
|
447
|
+
|
|
448
|
+
## Required human callouts (non-blocking, at the very end)
|
|
449
|
+
|
|
450
|
+
After findings/verdict, you MUST append this final section:
|
|
451
|
+
|
|
452
|
+
## Human Reviewer Callouts (Non-Blocking)
|
|
453
|
+
|
|
454
|
+
Include only applicable callouts (no yes/no lines):
|
|
455
|
+
|
|
456
|
+
- **This change adds a database migration:** <files/details>
|
|
457
|
+
- **This change introduces a new dependency:** <package(s)/details>
|
|
458
|
+
- **This change changes a dependency (or the lockfile):** <files/package(s)/details>
|
|
459
|
+
- **This change modifies auth/permission behavior:** <what changed and where>
|
|
460
|
+
- **This change introduces backwards-incompatible public schema/API/contract changes:** <what changed and where>
|
|
461
|
+
- **This change includes irreversible or destructive operations:** <operation and scope>
|
|
462
|
+
|
|
463
|
+
Rules for this section:
|
|
464
|
+
1. These are informational callouts for the human reviewer, not fix items.
|
|
465
|
+
2. Do not include them in Findings unless there is an independent defect.
|
|
466
|
+
3. These callouts alone must not change the verdict.
|
|
467
|
+
4. Only include callouts that apply to the reviewed change.
|
|
468
|
+
5. Keep each emitted callout bold exactly as written.
|
|
469
|
+
6. If none apply, write "- (none)".
|
|
433
470
|
|
|
434
471
|
## Priority levels
|
|
435
472
|
|
|
@@ -445,11 +482,12 @@ Provide your findings in a clear, structured format:
|
|
|
445
482
|
1. List each finding with its priority tag, file location, and explanation.
|
|
446
483
|
2. Findings must reference locations that overlap with the actual diff — don't flag pre-existing code.
|
|
447
484
|
3. Keep line references as short as possible (avoid ranges over 5-10 lines; pick the most suitable subrange).
|
|
448
|
-
4.
|
|
485
|
+
4. Provide an overall verdict: "correct" (no blocking issues) or "needs attention" (has blocking issues).
|
|
449
486
|
5. Ignore trivial style issues unless they obscure meaning or violate documented standards.
|
|
450
487
|
6. Do not generate a full PR fix — only flag issues and optionally provide short suggestion blocks.
|
|
488
|
+
7. End with the required "Human Reviewer Callouts (Non-Blocking)" section and all applicable bold callouts (no yes/no).
|
|
451
489
|
|
|
452
|
-
Output all findings the author would fix if they knew about them. If there are no qualifying findings, explicitly state the code looks good. Don't stop at the first finding - list every qualifying issue.`;
|
|
490
|
+
Output all findings the author would fix if they knew about them. If there are no qualifying findings, explicitly state the code looks good. Don't stop at the first finding - list every qualifying issue. Then append the required non-blocking callouts section.`;
|
|
453
491
|
|
|
454
492
|
async function loadProjectReviewGuidelines(cwd: string): Promise<string | null> {
|
|
455
493
|
let currentDir = path.resolve(cwd);
|
|
@@ -683,9 +721,6 @@ async function buildReviewPrompt(
|
|
|
683
721
|
}
|
|
684
722
|
return COMMIT_PROMPT.replace("{sha}", target.sha);
|
|
685
723
|
|
|
686
|
-
case "custom":
|
|
687
|
-
return target.instructions;
|
|
688
|
-
|
|
689
724
|
case "pullRequest": {
|
|
690
725
|
const mergeBase = await getMergeBase(pi, target.baseBranch);
|
|
691
726
|
const basePrompt = mergeBase
|
|
@@ -719,8 +754,6 @@ function getUserFacingHint(target: ReviewTarget): string {
|
|
|
719
754
|
const shortSha = target.sha.slice(0, 7);
|
|
720
755
|
return target.title ? `commit ${shortSha}: ${target.title}` : `commit ${shortSha}`;
|
|
721
756
|
}
|
|
722
|
-
case "custom":
|
|
723
|
-
return target.instructions.length > 40 ? target.instructions.slice(0, 37) + "..." : target.instructions;
|
|
724
757
|
|
|
725
758
|
case "pullRequest": {
|
|
726
759
|
const shortTitle = target.title.length > 30 ? target.title.slice(0, 27) + "..." : target.title;
|
|
@@ -802,16 +835,31 @@ const REVIEW_PRESETS = [
|
|
|
802
835
|
{ value: "commit", label: "Review a commit", description: "" },
|
|
803
836
|
{ value: "pullRequest", label: "Review a pull request", description: "(GitHub PR)" },
|
|
804
837
|
{ value: "folder", label: "Review a folder (or more)", description: "(snapshot, not diff)" },
|
|
805
|
-
{ value: "custom", label: "Custom review instructions", description: "" },
|
|
806
838
|
] as const;
|
|
807
839
|
|
|
808
840
|
const TOGGLE_LOOP_FIXING_VALUE = "toggleLoopFixing" as const;
|
|
809
|
-
|
|
841
|
+
const TOGGLE_CUSTOM_INSTRUCTIONS_VALUE = "toggleCustomInstructions" as const;
|
|
842
|
+
type ReviewPresetValue =
|
|
843
|
+
| (typeof REVIEW_PRESETS)[number]["value"]
|
|
844
|
+
| typeof TOGGLE_LOOP_FIXING_VALUE
|
|
845
|
+
| typeof TOGGLE_CUSTOM_INSTRUCTIONS_VALUE;
|
|
810
846
|
|
|
811
847
|
export default function reviewExtension(pi: ExtensionAPI) {
|
|
848
|
+
function persistReviewSettings() {
|
|
849
|
+
pi.appendEntry(REVIEW_SETTINGS_TYPE, {
|
|
850
|
+
loopFixingEnabled: reviewLoopFixingEnabled,
|
|
851
|
+
customInstructions: reviewCustomInstructions,
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
|
|
812
855
|
function setReviewLoopFixingEnabled(enabled: boolean) {
|
|
813
856
|
reviewLoopFixingEnabled = enabled;
|
|
814
|
-
|
|
857
|
+
persistReviewSettings();
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function setReviewCustomInstructions(instructions: string | undefined) {
|
|
861
|
+
reviewCustomInstructions = instructions?.trim() || undefined;
|
|
862
|
+
persistReviewSettings();
|
|
815
863
|
}
|
|
816
864
|
|
|
817
865
|
function applyAllReviewState(ctx: ExtensionContext) {
|
|
@@ -865,10 +913,21 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
865
913
|
const smartDefaultIndex = presetItems.findIndex((item) => item.value === smartDefault);
|
|
866
914
|
|
|
867
915
|
while (true) {
|
|
916
|
+
const customInstructionsLabel = reviewCustomInstructions
|
|
917
|
+
? "Remove custom review instructions"
|
|
918
|
+
: "Add custom review instructions";
|
|
919
|
+
const customInstructionsDescription = reviewCustomInstructions
|
|
920
|
+
? "(currently set)"
|
|
921
|
+
: "(applies to all review modes)";
|
|
868
922
|
const loopToggleLabel = reviewLoopFixingEnabled ? "Disable Loop Fixing" : "Enable Loop Fixing";
|
|
869
923
|
const loopToggleDescription = reviewLoopFixingEnabled ? "(currently on)" : "(currently off)";
|
|
870
924
|
const items: SelectItem[] = [
|
|
871
925
|
...presetItems,
|
|
926
|
+
{
|
|
927
|
+
value: TOGGLE_CUSTOM_INSTRUCTIONS_VALUE,
|
|
928
|
+
label: customInstructionsLabel,
|
|
929
|
+
description: customInstructionsDescription,
|
|
930
|
+
},
|
|
872
931
|
{ value: TOGGLE_LOOP_FIXING_VALUE, label: loopToggleLabel, description: loopToggleDescription },
|
|
873
932
|
];
|
|
874
933
|
|
|
@@ -920,6 +979,28 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
920
979
|
continue;
|
|
921
980
|
}
|
|
922
981
|
|
|
982
|
+
if (result === TOGGLE_CUSTOM_INSTRUCTIONS_VALUE) {
|
|
983
|
+
if (reviewCustomInstructions) {
|
|
984
|
+
setReviewCustomInstructions(undefined);
|
|
985
|
+
ctx.ui.notify("Custom review instructions removed", "info");
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const customInstructions = await ctx.ui.editor(
|
|
990
|
+
"Enter custom review instructions (applies to all review modes):",
|
|
991
|
+
"",
|
|
992
|
+
);
|
|
993
|
+
|
|
994
|
+
if (!customInstructions?.trim()) {
|
|
995
|
+
ctx.ui.notify("Custom review instructions not changed", "info");
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
setReviewCustomInstructions(customInstructions);
|
|
1000
|
+
ctx.ui.notify("Custom review instructions saved", "info");
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
923
1004
|
// Handle each preset type
|
|
924
1005
|
switch (result) {
|
|
925
1006
|
case "uncommitted":
|
|
@@ -941,12 +1022,6 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
941
1022
|
break;
|
|
942
1023
|
}
|
|
943
1024
|
|
|
944
|
-
case "custom": {
|
|
945
|
-
const target = await showCustomInput(ctx);
|
|
946
|
-
if (target) return target;
|
|
947
|
-
break;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
1025
|
case "folder": {
|
|
951
1026
|
const target = await showFolderInput(ctx);
|
|
952
1027
|
if (target) return target;
|
|
@@ -997,7 +1072,7 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
997
1072
|
description: branch === defaultBranch ? "(default)" : "",
|
|
998
1073
|
}));
|
|
999
1074
|
|
|
1000
|
-
const result = await ctx.ui.custom<string | null>((tui, theme,
|
|
1075
|
+
const result = await ctx.ui.custom<string | null>((tui, theme, keybindings, done) => {
|
|
1001
1076
|
const container = new Container();
|
|
1002
1077
|
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
1003
1078
|
container.addChild(new Text(theme.fg("accent", theme.bold("Select base branch"))));
|
|
@@ -1053,16 +1128,15 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1053
1128
|
container.invalidate();
|
|
1054
1129
|
},
|
|
1055
1130
|
handleInput(data: string) {
|
|
1056
|
-
const kb = getEditorKeybindings();
|
|
1057
1131
|
if (
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1132
|
+
keybindings.matches(data, "tui.select.up") ||
|
|
1133
|
+
keybindings.matches(data, "tui.select.down") ||
|
|
1134
|
+
keybindings.matches(data, "tui.select.confirm") ||
|
|
1135
|
+
keybindings.matches(data, "tui.select.cancel")
|
|
1062
1136
|
) {
|
|
1063
1137
|
if (selectList) {
|
|
1064
1138
|
selectList.handleInput(data);
|
|
1065
|
-
} else if (
|
|
1139
|
+
} else if (keybindings.matches(data, "tui.select.cancel")) {
|
|
1066
1140
|
done(null);
|
|
1067
1141
|
}
|
|
1068
1142
|
tui.requestRender();
|
|
@@ -1097,7 +1171,7 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1097
1171
|
description: "",
|
|
1098
1172
|
}));
|
|
1099
1173
|
|
|
1100
|
-
const result = await ctx.ui.custom<{ sha: string; title: string } | null>((tui, theme,
|
|
1174
|
+
const result = await ctx.ui.custom<{ sha: string; title: string } | null>((tui, theme, keybindings, done) => {
|
|
1101
1175
|
const container = new Container();
|
|
1102
1176
|
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
1103
1177
|
container.addChild(new Text(theme.fg("accent", theme.bold("Select commit to review"))));
|
|
@@ -1160,16 +1234,15 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1160
1234
|
container.invalidate();
|
|
1161
1235
|
},
|
|
1162
1236
|
handleInput(data: string) {
|
|
1163
|
-
const kb = getEditorKeybindings();
|
|
1164
1237
|
if (
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1238
|
+
keybindings.matches(data, "tui.select.up") ||
|
|
1239
|
+
keybindings.matches(data, "tui.select.down") ||
|
|
1240
|
+
keybindings.matches(data, "tui.select.confirm") ||
|
|
1241
|
+
keybindings.matches(data, "tui.select.cancel")
|
|
1169
1242
|
) {
|
|
1170
1243
|
if (selectList) {
|
|
1171
1244
|
selectList.handleInput(data);
|
|
1172
|
-
} else if (
|
|
1245
|
+
} else if (keybindings.matches(data, "tui.select.cancel")) {
|
|
1173
1246
|
done(null);
|
|
1174
1247
|
}
|
|
1175
1248
|
tui.requestRender();
|
|
@@ -1187,18 +1260,6 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1187
1260
|
return { type: "commit", sha: result.sha, title: result.title };
|
|
1188
1261
|
}
|
|
1189
1262
|
|
|
1190
|
-
/**
|
|
1191
|
-
* Show custom instructions input
|
|
1192
|
-
*/
|
|
1193
|
-
async function showCustomInput(ctx: ExtensionContext): Promise<ReviewTarget | null> {
|
|
1194
|
-
const result = await ctx.ui.editor(
|
|
1195
|
-
"Enter review instructions:",
|
|
1196
|
-
"Review the code for security vulnerabilities and potential bugs...",
|
|
1197
|
-
);
|
|
1198
|
-
|
|
1199
|
-
if (!result?.trim()) return null;
|
|
1200
|
-
return { type: "custom", instructions: result.trim() };
|
|
1201
|
-
}
|
|
1202
1263
|
|
|
1203
1264
|
function parseReviewPaths(value: string): string[] {
|
|
1204
1265
|
return value
|
|
@@ -1288,7 +1349,7 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1288
1349
|
ctx: ExtensionCommandContext,
|
|
1289
1350
|
target: ReviewTarget,
|
|
1290
1351
|
useFreshSession: boolean,
|
|
1291
|
-
options?: { includeLocalChanges?: boolean },
|
|
1352
|
+
options?: { includeLocalChanges?: boolean; extraInstruction?: string },
|
|
1292
1353
|
): Promise<boolean> {
|
|
1293
1354
|
// Check if we're already in a review
|
|
1294
1355
|
if (reviewOriginId) {
|
|
@@ -1360,6 +1421,14 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1360
1421
|
// Combine the review rubric with the specific prompt
|
|
1361
1422
|
let fullPrompt = `${REVIEW_RUBRIC}\n\n---\n\nPlease perform a code review with the following focus:\n\n${prompt}`;
|
|
1362
1423
|
|
|
1424
|
+
if (reviewCustomInstructions) {
|
|
1425
|
+
fullPrompt += `\n\nShared custom review instructions (applies to all reviews):\n\n${reviewCustomInstructions}`;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
if (options?.extraInstruction?.trim()) {
|
|
1429
|
+
fullPrompt += `\n\nAdditional user-provided review instruction:\n\n${options.extraInstruction.trim()}`;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1363
1432
|
if (projectGuidelines) {
|
|
1364
1433
|
fullPrompt += `\n\nThis project has additional instructions for code reviews:\n\n${projectGuidelines}`;
|
|
1365
1434
|
}
|
|
@@ -1376,49 +1445,122 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1376
1445
|
* Parse command arguments for direct invocation
|
|
1377
1446
|
* Returns the target or a special marker for PR that needs async handling
|
|
1378
1447
|
*/
|
|
1379
|
-
|
|
1380
|
-
|
|
1448
|
+
type ParsedReviewArgs = {
|
|
1449
|
+
target: ReviewTarget | { type: "pr"; ref: string } | null;
|
|
1450
|
+
extraInstruction?: string;
|
|
1451
|
+
error?: string;
|
|
1452
|
+
};
|
|
1453
|
+
|
|
1454
|
+
function tokenizeArgs(value: string): string[] {
|
|
1455
|
+
const tokens: string[] = [];
|
|
1456
|
+
let current = "";
|
|
1457
|
+
let quote: '"' | "'" | null = null;
|
|
1458
|
+
|
|
1459
|
+
for (let i = 0; i < value.length; i++) {
|
|
1460
|
+
const char = value[i];
|
|
1461
|
+
|
|
1462
|
+
if (quote) {
|
|
1463
|
+
if (char === "\\" && i + 1 < value.length) {
|
|
1464
|
+
current += value[i + 1];
|
|
1465
|
+
i += 1;
|
|
1466
|
+
continue;
|
|
1467
|
+
}
|
|
1468
|
+
if (char === quote) {
|
|
1469
|
+
quote = null;
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
current += char;
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
if (char === '"' || char === "'") {
|
|
1477
|
+
quote = char;
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
if (/\s/.test(char)) {
|
|
1482
|
+
if (current.length > 0) {
|
|
1483
|
+
tokens.push(current);
|
|
1484
|
+
current = "";
|
|
1485
|
+
}
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
current += char;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
if (current.length > 0) {
|
|
1493
|
+
tokens.push(current);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
return tokens;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
function parseArgs(args: string | undefined): ParsedReviewArgs {
|
|
1500
|
+
if (!args?.trim()) return { target: null };
|
|
1501
|
+
|
|
1502
|
+
const rawParts = tokenizeArgs(args.trim());
|
|
1503
|
+
const parts: string[] = [];
|
|
1504
|
+
let extraInstruction: string | undefined;
|
|
1505
|
+
|
|
1506
|
+
for (let i = 0; i < rawParts.length; i++) {
|
|
1507
|
+
const part = rawParts[i];
|
|
1508
|
+
if (part === "--extra") {
|
|
1509
|
+
const next = rawParts[i + 1];
|
|
1510
|
+
if (!next) {
|
|
1511
|
+
return { target: null, error: "Missing value for --extra" };
|
|
1512
|
+
}
|
|
1513
|
+
extraInstruction = next;
|
|
1514
|
+
i += 1;
|
|
1515
|
+
continue;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
if (part.startsWith("--extra=")) {
|
|
1519
|
+
extraInstruction = part.slice("--extra=".length);
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
parts.push(part);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
if (parts.length === 0) {
|
|
1527
|
+
return { target: null, extraInstruction };
|
|
1528
|
+
}
|
|
1381
1529
|
|
|
1382
|
-
const parts = args.trim().split(/\s+/);
|
|
1383
1530
|
const subcommand = parts[0]?.toLowerCase();
|
|
1384
1531
|
|
|
1385
1532
|
switch (subcommand) {
|
|
1386
1533
|
case "uncommitted":
|
|
1387
|
-
return { type: "uncommitted" };
|
|
1534
|
+
return { target: { type: "uncommitted" }, extraInstruction };
|
|
1388
1535
|
|
|
1389
1536
|
case "branch": {
|
|
1390
1537
|
const branch = parts[1];
|
|
1391
|
-
if (!branch) return null;
|
|
1392
|
-
return { type: "baseBranch", branch };
|
|
1538
|
+
if (!branch) return { target: null, extraInstruction };
|
|
1539
|
+
return { target: { type: "baseBranch", branch }, extraInstruction };
|
|
1393
1540
|
}
|
|
1394
1541
|
|
|
1395
1542
|
case "commit": {
|
|
1396
1543
|
const sha = parts[1];
|
|
1397
|
-
if (!sha) return null;
|
|
1544
|
+
if (!sha) return { target: null, extraInstruction };
|
|
1398
1545
|
const title = parts.slice(2).join(" ") || undefined;
|
|
1399
|
-
return { type: "commit", sha, title };
|
|
1546
|
+
return { target: { type: "commit", sha, title }, extraInstruction };
|
|
1400
1547
|
}
|
|
1401
1548
|
|
|
1402
|
-
case "custom": {
|
|
1403
|
-
const instructions = parts.slice(1).join(" ");
|
|
1404
|
-
if (!instructions) return null;
|
|
1405
|
-
return { type: "custom", instructions };
|
|
1406
|
-
}
|
|
1407
1549
|
|
|
1408
1550
|
case "folder": {
|
|
1409
1551
|
const paths = parseReviewPaths(parts.slice(1).join(" "));
|
|
1410
|
-
if (paths.length === 0) return null;
|
|
1411
|
-
return { type: "folder", paths };
|
|
1552
|
+
if (paths.length === 0) return { target: null, extraInstruction };
|
|
1553
|
+
return { target: { type: "folder", paths }, extraInstruction };
|
|
1412
1554
|
}
|
|
1413
1555
|
|
|
1414
1556
|
case "pr": {
|
|
1415
1557
|
const ref = parts[1];
|
|
1416
|
-
if (!ref) return null;
|
|
1417
|
-
return { type: "pr", ref };
|
|
1558
|
+
if (!ref) return { target: null, extraInstruction };
|
|
1559
|
+
return { target: { type: "pr", ref }, extraInstruction };
|
|
1418
1560
|
}
|
|
1419
1561
|
|
|
1420
1562
|
default:
|
|
1421
|
-
return null;
|
|
1563
|
+
return { target: null, extraInstruction };
|
|
1422
1564
|
}
|
|
1423
1565
|
}
|
|
1424
1566
|
|
|
@@ -1474,7 +1616,11 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1474
1616
|
return false;
|
|
1475
1617
|
}
|
|
1476
1618
|
|
|
1477
|
-
async function runLoopFixingReview(
|
|
1619
|
+
async function runLoopFixingReview(
|
|
1620
|
+
ctx: ExtensionCommandContext,
|
|
1621
|
+
target: ReviewTarget,
|
|
1622
|
+
extraInstruction?: string,
|
|
1623
|
+
): Promise<void> {
|
|
1478
1624
|
if (reviewLoopInProgress) {
|
|
1479
1625
|
ctx.ui.notify("Loop fixing review is already running.", "warning");
|
|
1480
1626
|
return;
|
|
@@ -1490,7 +1636,10 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1490
1636
|
|
|
1491
1637
|
for (let pass = 1; pass <= REVIEW_LOOP_MAX_ITERATIONS; pass++) {
|
|
1492
1638
|
const reviewBaselineAssistantId = getLastAssistantSnapshot(ctx)?.id;
|
|
1493
|
-
const started = await executeReview(ctx, target, true, {
|
|
1639
|
+
const started = await executeReview(ctx, target, true, {
|
|
1640
|
+
includeLocalChanges: true,
|
|
1641
|
+
extraInstruction,
|
|
1642
|
+
});
|
|
1494
1643
|
if (!started) {
|
|
1495
1644
|
ctx.ui.notify("Loop fixing stopped before starting the review pass.", "warning");
|
|
1496
1645
|
return;
|
|
@@ -1588,7 +1737,7 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1588
1737
|
|
|
1589
1738
|
// Register the /review command
|
|
1590
1739
|
pi.registerCommand("review", {
|
|
1591
|
-
description: "Review code changes (PR, uncommitted, branch, commit,
|
|
1740
|
+
description: "Review code changes (PR, uncommitted, branch, commit, or folder)",
|
|
1592
1741
|
handler: async (args, ctx) => {
|
|
1593
1742
|
if (!ctx.hasUI) {
|
|
1594
1743
|
ctx.ui.notify("Review requires interactive mode", "error");
|
|
@@ -1616,17 +1765,23 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1616
1765
|
// Try to parse direct arguments
|
|
1617
1766
|
let target: ReviewTarget | null = null;
|
|
1618
1767
|
let fromSelector = false;
|
|
1768
|
+
let extraInstruction: string | undefined;
|
|
1619
1769
|
const parsed = parseArgs(args);
|
|
1770
|
+
if (parsed.error) {
|
|
1771
|
+
ctx.ui.notify(parsed.error, "error");
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
extraInstruction = parsed.extraInstruction?.trim() || undefined;
|
|
1620
1775
|
|
|
1621
|
-
if (parsed) {
|
|
1622
|
-
if (parsed.type === "pr") {
|
|
1776
|
+
if (parsed.target) {
|
|
1777
|
+
if (parsed.target.type === "pr") {
|
|
1623
1778
|
// Handle PR checkout (async operation)
|
|
1624
|
-
target = await handlePrCheckout(ctx, parsed.ref);
|
|
1779
|
+
target = await handlePrCheckout(ctx, parsed.target.ref);
|
|
1625
1780
|
if (!target) {
|
|
1626
1781
|
ctx.ui.notify("PR review failed. Returning to review menu.", "warning");
|
|
1627
1782
|
}
|
|
1628
1783
|
} else {
|
|
1629
|
-
target = parsed;
|
|
1784
|
+
target = parsed.target;
|
|
1630
1785
|
}
|
|
1631
1786
|
}
|
|
1632
1787
|
|
|
@@ -1655,7 +1810,7 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1655
1810
|
}
|
|
1656
1811
|
|
|
1657
1812
|
if (reviewLoopFixingEnabled) {
|
|
1658
|
-
await runLoopFixingReview(ctx, target);
|
|
1813
|
+
await runLoopFixingReview(ctx, target, extraInstruction);
|
|
1659
1814
|
return;
|
|
1660
1815
|
}
|
|
1661
1816
|
|
|
@@ -1683,7 +1838,7 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1683
1838
|
useFreshSession = choice === "Empty branch";
|
|
1684
1839
|
}
|
|
1685
1840
|
|
|
1686
|
-
await executeReview(ctx, target, useFreshSession);
|
|
1841
|
+
await executeReview(ctx, target, useFreshSession, { extraInstruction });
|
|
1687
1842
|
return;
|
|
1688
1843
|
}
|
|
1689
1844
|
},
|
|
@@ -1718,6 +1873,19 @@ For EACH finding, include:
|
|
|
1718
1873
|
- Any constraints or preferences mentioned during review
|
|
1719
1874
|
- Or "(none)"
|
|
1720
1875
|
|
|
1876
|
+
## Human Reviewer Callouts (Non-Blocking)
|
|
1877
|
+
Include only applicable callouts (no yes/no lines):
|
|
1878
|
+
- **This change adds a database migration:** <files/details>
|
|
1879
|
+
- **This change introduces a new dependency:** <package(s)/details>
|
|
1880
|
+
- **This change changes a dependency (or the lockfile):** <files/package(s)/details>
|
|
1881
|
+
- **This change modifies auth/permission behavior:** <what changed and where>
|
|
1882
|
+
- **This change introduces backwards-incompatible public schema/API/contract changes:** <what changed and where>
|
|
1883
|
+
- **This change includes irreversible or destructive operations:** <operation and scope>
|
|
1884
|
+
|
|
1885
|
+
If none apply, write "- (none)".
|
|
1886
|
+
|
|
1887
|
+
These are informational callouts for humans and are not fix items by themselves.
|
|
1888
|
+
|
|
1721
1889
|
Preserve exact file paths, function names, and error messages where available.`;
|
|
1722
1890
|
|
|
1723
1891
|
const REVIEW_FIX_FINDINGS_PROMPT = `Use the latest review summary in this session and implement the review findings now.
|
|
@@ -1726,8 +1894,12 @@ Instructions:
|
|
|
1726
1894
|
1. Treat the summary's Findings/Fix Queue as a checklist.
|
|
1727
1895
|
2. Fix in priority order: P0, P1, then P2 (include P3 if quick and safe).
|
|
1728
1896
|
3. If a finding is invalid/already fixed/not possible right now, briefly explain why and continue.
|
|
1729
|
-
4.
|
|
1730
|
-
5.
|
|
1897
|
+
4. Treat "Human Reviewer Callouts (Non-Blocking)" as informational only; do not convert them into fix tasks unless there is a separate explicit finding.
|
|
1898
|
+
5. Follow fail-fast error handling: do not add local catch/fallback recovery unless this scope is an explicit boundary that can safely translate the failure.
|
|
1899
|
+
6. If you add or keep a \`try/catch\`, explain the expected failure mode and either rethrow with context or return a boundary-safe error response.
|
|
1900
|
+
7. JSON parsing/decoding should fail loudly by default; avoid silent fallback parsing.
|
|
1901
|
+
8. Run relevant tests/checks for touched code where practical.
|
|
1902
|
+
9. End with: fixed items, deferred/skipped items (with reasons), and verification results.`;
|
|
1731
1903
|
|
|
1732
1904
|
type EndReviewAction = "returnOnly" | "returnAndFix" | "returnAndSummarize";
|
|
1733
1905
|
type EndReviewActionResult = "ok" | "cancelled" | "error";
|