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.
Files changed (43) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +24 -0
  3. package/README.md +28 -18
  4. package/analyze-edits.py +232 -0
  5. package/distributions/README.md +8 -0
  6. package/distributions/mitsupi-common/README.md +11 -0
  7. package/distributions/mitsupi-common/package.json +95 -0
  8. package/distributions/mitsupi-loaded/README.md +13 -0
  9. package/distributions/mitsupi-loaded/package.json +38 -0
  10. package/{pi-extensions → extensions}/answer.ts +11 -11
  11. package/extensions/btw.ts +963 -0
  12. package/{pi-extensions → extensions}/context.ts +2 -2
  13. package/{pi-extensions → extensions}/control.ts +20 -13
  14. package/{pi-extensions → extensions}/files.ts +6 -8
  15. package/{pi-extensions → extensions}/go-to-bed.ts +2 -2
  16. package/{pi-extensions → extensions}/loop.ts +18 -11
  17. package/extensions/multi-edit.ts +871 -0
  18. package/{pi-extensions → extensions}/review.ts +254 -82
  19. package/{pi-extensions → extensions}/session-breakdown.ts +551 -74
  20. package/extensions/split-fork.ts +130 -0
  21. package/{pi-extensions → extensions}/todos.ts +41 -34
  22. package/extensions/uv.ts +123 -0
  23. package/intercepted-commands/poetry +8 -1
  24. package/intercepted-commands/python +42 -2
  25. package/intercepted-commands/python3 +42 -2
  26. package/package.json +6 -3
  27. package/skills/google-workspace/SKILL.md +53 -8
  28. package/skills/google-workspace/scripts/auth.js +106 -28
  29. package/skills/google-workspace/scripts/common.js +201 -32
  30. package/skills/google-workspace/scripts/workspace.js +64 -9
  31. package/skills/native-web-search/SKILL.md +2 -0
  32. package/skills/native-web-search/search.mjs +102 -15
  33. package/skills/pi-share/SKILL.md +5 -2
  34. package/skills/pi-share/fetch-session.mjs +46 -15
  35. package/skills/summarize/SKILL.md +4 -3
  36. package/skills/summarize/to-markdown.mjs +3 -1
  37. package/skills/web-browser/SKILL.md +6 -0
  38. package/skills/web-browser/scripts/start.js +75 -27
  39. package/pi-extensions/uv.ts +0 -33
  40. /package/{pi-extensions → extensions}/notify.ts +0 -0
  41. /package/{pi-extensions → extensions}/prompt-editor.ts +0 -0
  42. /package/{pi-extensions → extensions}/whimsical.ts +0 -0
  43. /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
- * - Custom review instructions
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 "check for security issues"` - custom instructions
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. Call out newly added dependencies explicitly and explain why they're needed.
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. Favor fail-fast behavior; avoid logging-and-continue patterns that hide errors.
429
- 4. Prefer predictable production behavior; crashing is better than silent degradation.
430
- 5. Treat back pressure handling as critical to system stability.
431
- 6. Apply system-level thinking; flag changes that increase operational risk or on-call wakeups.
432
- 7. Ensure that errors are always checked against codes or stable identifiers, never error messages.
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. At the end, provide an overall verdict: "correct" (no blocking issues) or "needs attention" (has blocking issues).
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
- type ReviewPresetValue = (typeof REVIEW_PRESETS)[number]["value"] | typeof TOGGLE_LOOP_FIXING_VALUE;
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
- pi.appendEntry(REVIEW_SETTINGS_TYPE, { loopFixingEnabled: enabled });
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, _kb, done) => {
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
- kb.matches(data, "selectUp") ||
1059
- kb.matches(data, "selectDown") ||
1060
- kb.matches(data, "selectConfirm") ||
1061
- kb.matches(data, "selectCancel")
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 (kb.matches(data, "selectCancel")) {
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, _kb, done) => {
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
- kb.matches(data, "selectUp") ||
1166
- kb.matches(data, "selectDown") ||
1167
- kb.matches(data, "selectConfirm") ||
1168
- kb.matches(data, "selectCancel")
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 (kb.matches(data, "selectCancel")) {
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
- function parseArgs(args: string | undefined): ReviewTarget | { type: "pr"; ref: string } | null {
1380
- if (!args?.trim()) return null;
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(ctx: ExtensionCommandContext, target: ReviewTarget): Promise<void> {
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, { includeLocalChanges: 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, folder, or custom)",
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. Run relevant tests/checks for touched code where practical.
1730
- 5. End with: fixed items, deferred/skipped items (with reasons), and verification results.`;
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";