@diegopetrucci/pi-review 0.1.3 → 0.1.7
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/.pi-fleet-tested-version +1 -1
- package/README.md +1 -1
- package/index.ts +255 -201
- package/package.json +1 -1
package/.pi-fleet-tested-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.80.6
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ Then reload pi:
|
|
|
31
31
|
|
|
32
32
|
- Behavior is intentionally kept equivalent to the upstream source, with only packaging/attribution changes for this repository.
|
|
33
33
|
- PR review requires `gh` access and a clean working tree for tracked files.
|
|
34
|
-
- If the project is trusted and a `REVIEW_GUIDELINES.md` file exists next to the repo's
|
|
34
|
+
- If the project is trusted and a `REVIEW_GUIDELINES.md` file exists next to the repo's `<pi-config-dir>` directory, its contents are appended to the review prompt. `<pi-config-dir>` is Pi's runtime config directory name (`CONFIG_DIR_NAME`; `.pi` by default).
|
|
35
35
|
|
|
36
36
|
## License and attribution
|
|
37
37
|
|
package/index.ts
CHANGED
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
* - `/review --extra "focus on performance regressions"` - add extra review instruction (works with any mode)
|
|
30
30
|
*
|
|
31
31
|
* Project-specific review guidelines:
|
|
32
|
-
* - If the project is trusted and a REVIEW_GUIDELINES.md file exists
|
|
33
|
-
*
|
|
32
|
+
* - If the project is trusted and a REVIEW_GUIDELINES.md file exists next to
|
|
33
|
+
* the project config directory, its contents are appended to the review prompt.
|
|
34
34
|
*
|
|
35
35
|
* Note: PR review requires a clean working tree (no uncommitted changes to tracked files).
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
import type { ExtensionAPI, ExtensionContext, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
39
|
-
import { DynamicBorder, BorderedLoader } from "@earendil-works/pi-coding-agent";
|
|
39
|
+
import { CONFIG_DIR_NAME, DynamicBorder, BorderedLoader } from "@earendil-works/pi-coding-agent";
|
|
40
40
|
import {
|
|
41
41
|
Container,
|
|
42
42
|
fuzzyFilter,
|
|
@@ -49,7 +49,6 @@ import {
|
|
|
49
49
|
import path from "node:path";
|
|
50
50
|
import { promises as fs } from "node:fs";
|
|
51
51
|
|
|
52
|
-
const CONFIG_DIR_NAME = ".pi";
|
|
53
52
|
|
|
54
53
|
// State to track fresh session review (where we branched from).
|
|
55
54
|
// Module-level state means only one review can be active at a time.
|
|
@@ -60,6 +59,24 @@ let reviewLoopFixingEnabled = false;
|
|
|
60
59
|
let reviewCustomInstructions: string | undefined = undefined;
|
|
61
60
|
let reviewLoopInProgress = false;
|
|
62
61
|
|
|
62
|
+
function getReviewRuntimeState() {
|
|
63
|
+
return {
|
|
64
|
+
reviewOriginId,
|
|
65
|
+
endReviewInProgress,
|
|
66
|
+
reviewLoopFixingEnabled,
|
|
67
|
+
reviewCustomInstructions,
|
|
68
|
+
reviewLoopInProgress,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resetReviewRuntimeState() {
|
|
73
|
+
reviewOriginId = undefined;
|
|
74
|
+
endReviewInProgress = false;
|
|
75
|
+
reviewLoopFixingEnabled = false;
|
|
76
|
+
reviewCustomInstructions = undefined;
|
|
77
|
+
reviewLoopInProgress = false;
|
|
78
|
+
}
|
|
79
|
+
|
|
63
80
|
const REVIEW_STATE_TYPE = "review-session";
|
|
64
81
|
const REVIEW_ANCHOR_TYPE = "review-anchor";
|
|
65
82
|
const REVIEW_SETTINGS_TYPE = "review-settings";
|
|
@@ -88,7 +105,7 @@ function canReadProjectConfig(ctx: ProjectConfigContext): boolean {
|
|
|
88
105
|
|
|
89
106
|
function setReviewWidget(ctx: ExtensionContext, active: boolean) {
|
|
90
107
|
if (!ctx.hasUI) return;
|
|
91
|
-
if (!active) {
|
|
108
|
+
if (!active || ctx.mode !== "tui") {
|
|
92
109
|
ctx.ui.setWidget("review", undefined);
|
|
93
110
|
return;
|
|
94
111
|
}
|
|
@@ -630,18 +647,20 @@ async function hasPendingChanges(pi: ExtensionAPI): Promise<boolean> {
|
|
|
630
647
|
function parsePrReference(ref: string): number | null {
|
|
631
648
|
const trimmed = ref.trim();
|
|
632
649
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
650
|
+
if (/^\d+$/.test(trimmed)) {
|
|
651
|
+
const num = Number(trimmed);
|
|
652
|
+
if (Number.isSafeInteger(num) && num > 0) {
|
|
653
|
+
return num;
|
|
654
|
+
}
|
|
637
655
|
}
|
|
638
656
|
|
|
639
657
|
// Try to extract from GitHub URL
|
|
640
658
|
// Formats: https://github.com/owner/repo/pull/123
|
|
641
659
|
// github.com/owner/repo/pull/123
|
|
642
|
-
const urlMatch = trimmed.match(
|
|
660
|
+
const urlMatch = trimmed.match(/^(?:https?:\/\/)?(?:www\.)?github\.com\/[^/]+\/[^/]+\/pull\/(\d+)(?:[/?#]|$)/i);
|
|
643
661
|
if (urlMatch) {
|
|
644
|
-
|
|
662
|
+
const num = Number(urlMatch[1]);
|
|
663
|
+
return Number.isSafeInteger(num) && num > 0 ? num : null;
|
|
645
664
|
}
|
|
646
665
|
|
|
647
666
|
return null;
|
|
@@ -763,6 +782,30 @@ async function buildReviewPrompt(
|
|
|
763
782
|
/**
|
|
764
783
|
* Get user-facing hint for the review target
|
|
765
784
|
*/
|
|
785
|
+
type ComposeReviewPromptOptions = {
|
|
786
|
+
customInstructions?: string;
|
|
787
|
+
extraInstruction?: string;
|
|
788
|
+
projectGuidelines?: string | null;
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
function composeReviewPrompt(prompt: string, options: ComposeReviewPromptOptions = {}): string {
|
|
792
|
+
let fullPrompt = `${REVIEW_RUBRIC}\n\n---\n\nPlease perform a code review with the following focus:\n\n${prompt}`;
|
|
793
|
+
|
|
794
|
+
if (options.customInstructions?.trim()) {
|
|
795
|
+
fullPrompt += `\n\nShared custom review instructions (applies to all reviews):\n\n${options.customInstructions.trim()}`;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (options.extraInstruction?.trim()) {
|
|
799
|
+
fullPrompt += `\n\nAdditional user-provided review instruction:\n\n${options.extraInstruction.trim()}`;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (options.projectGuidelines?.trim()) {
|
|
803
|
+
fullPrompt += `\n\nThis project has additional instructions for code reviews:\n\n${options.projectGuidelines.trim()}`;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return fullPrompt;
|
|
807
|
+
}
|
|
808
|
+
|
|
766
809
|
function getUserFacingHint(target: ReviewTarget): string {
|
|
767
810
|
switch (target.type) {
|
|
768
811
|
case "uncommitted":
|
|
@@ -833,6 +876,130 @@ function sleep(ms: number): Promise<void> {
|
|
|
833
876
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
834
877
|
}
|
|
835
878
|
|
|
879
|
+
function tokenizeArgs(value: string): string[] {
|
|
880
|
+
const tokens: string[] = [];
|
|
881
|
+
let current = "";
|
|
882
|
+
let quote: '"' | "'" | null = null;
|
|
883
|
+
|
|
884
|
+
for (let i = 0; i < value.length; i++) {
|
|
885
|
+
const char = value[i];
|
|
886
|
+
|
|
887
|
+
if (quote) {
|
|
888
|
+
if (char === "\\" && i + 1 < value.length) {
|
|
889
|
+
current += value[i + 1];
|
|
890
|
+
i += 1;
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
if (char === quote) {
|
|
894
|
+
quote = null;
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
current += char;
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (char === '"' || char === "'") {
|
|
902
|
+
quote = char;
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
if (/\s/.test(char)) {
|
|
907
|
+
if (current.length > 0) {
|
|
908
|
+
tokens.push(current);
|
|
909
|
+
current = "";
|
|
910
|
+
}
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
current += char;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (current.length > 0) {
|
|
918
|
+
tokens.push(current);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
return tokens;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function parseReviewPaths(value: string): string[] {
|
|
925
|
+
return tokenizeArgs(value)
|
|
926
|
+
.map((item) => item.trim())
|
|
927
|
+
.filter((item) => item.length > 0);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
type ParsedReviewArgs = {
|
|
931
|
+
target: ReviewTarget | { type: "pr"; ref: string } | null;
|
|
932
|
+
extraInstruction?: string;
|
|
933
|
+
error?: string;
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
function parseArgs(args: string | undefined): ParsedReviewArgs {
|
|
937
|
+
if (!args?.trim()) return { target: null };
|
|
938
|
+
|
|
939
|
+
const rawParts = tokenizeArgs(args.trim());
|
|
940
|
+
const parts: string[] = [];
|
|
941
|
+
let extraInstruction: string | undefined;
|
|
942
|
+
|
|
943
|
+
for (let i = 0; i < rawParts.length; i++) {
|
|
944
|
+
const part = rawParts[i];
|
|
945
|
+
if (part === "--extra") {
|
|
946
|
+
const next = rawParts[i + 1];
|
|
947
|
+
if (!next) {
|
|
948
|
+
return { target: null, error: "Missing value for --extra" };
|
|
949
|
+
}
|
|
950
|
+
extraInstruction = next;
|
|
951
|
+
i += 1;
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
if (part.startsWith("--extra=")) {
|
|
956
|
+
extraInstruction = part.slice("--extra=".length);
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
parts.push(part);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (parts.length === 0) {
|
|
964
|
+
return { target: null, extraInstruction };
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
const subcommand = parts[0]?.toLowerCase();
|
|
968
|
+
|
|
969
|
+
switch (subcommand) {
|
|
970
|
+
case "uncommitted":
|
|
971
|
+
return { target: { type: "uncommitted" }, extraInstruction };
|
|
972
|
+
|
|
973
|
+
case "branch": {
|
|
974
|
+
const branch = parts[1];
|
|
975
|
+
if (!branch) return { target: null, extraInstruction };
|
|
976
|
+
return { target: { type: "baseBranch", branch }, extraInstruction };
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
case "commit": {
|
|
980
|
+
const sha = parts[1];
|
|
981
|
+
if (!sha) return { target: null, extraInstruction };
|
|
982
|
+
const title = parts.slice(2).join(" ") || undefined;
|
|
983
|
+
return { target: { type: "commit", sha, title }, extraInstruction };
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
case "folder": {
|
|
987
|
+
const paths = parts.slice(1).map((item) => item.trim()).filter((item) => item.length > 0);
|
|
988
|
+
if (paths.length === 0) return { target: null, extraInstruction };
|
|
989
|
+
return { target: { type: "folder", paths }, extraInstruction };
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
case "pr": {
|
|
993
|
+
const ref = parts[1];
|
|
994
|
+
if (!ref) return { target: null, extraInstruction };
|
|
995
|
+
return { target: { type: "pr", ref }, extraInstruction };
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
default:
|
|
999
|
+
return { target: null, extraInstruction };
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
836
1003
|
async function waitForLoopTurnToStart(ctx: ExtensionContext, previousAssistantId?: string): Promise<boolean> {
|
|
837
1004
|
const deadline = Date.now() + REVIEW_LOOP_START_TIMEOUT_MS;
|
|
838
1005
|
|
|
@@ -863,6 +1030,69 @@ type ReviewPresetValue =
|
|
|
863
1030
|
| typeof TOGGLE_LOOP_FIXING_VALUE
|
|
864
1031
|
| typeof TOGGLE_CUSTOM_INSTRUCTIONS_VALUE;
|
|
865
1032
|
|
|
1033
|
+
const REVIEW_SUMMARY_PROMPT = `We are leaving a code-review branch and returning to the main coding branch.
|
|
1034
|
+
Create a structured handoff that can be used immediately to implement fixes.
|
|
1035
|
+
|
|
1036
|
+
You MUST summarize the review that happened in this branch so findings can be acted on.
|
|
1037
|
+
Do not omit findings: include every actionable issue that was identified.
|
|
1038
|
+
|
|
1039
|
+
Required sections (in order):
|
|
1040
|
+
|
|
1041
|
+
## Review Scope
|
|
1042
|
+
- What was reviewed (files/paths, changes, and scope)
|
|
1043
|
+
|
|
1044
|
+
## Verdict
|
|
1045
|
+
- "correct" or "needs attention"
|
|
1046
|
+
|
|
1047
|
+
## Findings
|
|
1048
|
+
For EACH finding, include:
|
|
1049
|
+
- Priority tag ([P0]..[P3]) and short title
|
|
1050
|
+
- File location (\`path/to/file.ext:line\`)
|
|
1051
|
+
- Why it matters (brief)
|
|
1052
|
+
- What should change (brief, actionable)
|
|
1053
|
+
|
|
1054
|
+
## Fix Queue
|
|
1055
|
+
1. Ordered implementation checklist (highest priority first)
|
|
1056
|
+
|
|
1057
|
+
## Constraints & Preferences
|
|
1058
|
+
- Any constraints or preferences mentioned during review
|
|
1059
|
+
- Or "(none)"
|
|
1060
|
+
|
|
1061
|
+
## Human Reviewer Callouts (Non-Blocking)
|
|
1062
|
+
Include only applicable callouts (no yes/no lines):
|
|
1063
|
+
- **This change adds a database migration:** <files/details>
|
|
1064
|
+
- **This change introduces a new dependency:** <package(s)/details>
|
|
1065
|
+
- **This change changes a dependency (or the lockfile):** <files/package(s)/details>
|
|
1066
|
+
- **This change modifies auth/permission behavior:** <what changed and where>
|
|
1067
|
+
- **This change introduces backwards-incompatible public schema/API/contract changes:** <what changed and where>
|
|
1068
|
+
- **This change includes irreversible or destructive operations:** <operation and scope>
|
|
1069
|
+
|
|
1070
|
+
If none apply, write "- (none)".
|
|
1071
|
+
|
|
1072
|
+
These are informational callouts for humans and are not fix items by themselves.
|
|
1073
|
+
|
|
1074
|
+
Preserve exact file paths, function names, and error messages where available.`;
|
|
1075
|
+
|
|
1076
|
+
export const __test__ = {
|
|
1077
|
+
applyReviewSettings,
|
|
1078
|
+
applyReviewState,
|
|
1079
|
+
buildReviewPrompt,
|
|
1080
|
+
composeReviewPrompt,
|
|
1081
|
+
getReviewRuntimeState,
|
|
1082
|
+
getReviewSettings,
|
|
1083
|
+
getReviewState,
|
|
1084
|
+
hasBlockingReviewFindings,
|
|
1085
|
+
hasNeedsAttentionVerdict,
|
|
1086
|
+
getUserFacingHint,
|
|
1087
|
+
parseArgs,
|
|
1088
|
+
parsePrReference,
|
|
1089
|
+
parseReviewPaths,
|
|
1090
|
+
REVIEW_SUMMARY_PROMPT,
|
|
1091
|
+
loadProjectReviewGuidelines,
|
|
1092
|
+
resetReviewRuntimeState,
|
|
1093
|
+
tokenizeArgs,
|
|
1094
|
+
};
|
|
1095
|
+
|
|
866
1096
|
export default function reviewExtension(pi: ExtensionAPI) {
|
|
867
1097
|
function persistReviewSettings() {
|
|
868
1098
|
pi.appendEntry(REVIEW_SETTINGS_TYPE, {
|
|
@@ -1276,13 +1506,6 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1276
1506
|
}
|
|
1277
1507
|
|
|
1278
1508
|
|
|
1279
|
-
function parseReviewPaths(value: string): string[] {
|
|
1280
|
-
return value
|
|
1281
|
-
.split(/\s+/)
|
|
1282
|
-
.map((item) => item.trim())
|
|
1283
|
-
.filter((item) => item.length > 0);
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
1509
|
/**
|
|
1287
1510
|
* Show folder input
|
|
1288
1511
|
*/
|
|
@@ -1433,20 +1656,11 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1433
1656
|
const hint = getUserFacingHint(target);
|
|
1434
1657
|
const projectGuidelines = canReadProjectConfig(ctx) ? await loadProjectReviewGuidelines(ctx.cwd) : null;
|
|
1435
1658
|
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
if (options?.extraInstruction?.trim()) {
|
|
1444
|
-
fullPrompt += `\n\nAdditional user-provided review instruction:\n\n${options.extraInstruction.trim()}`;
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
if (projectGuidelines) {
|
|
1448
|
-
fullPrompt += `\n\nThis project has additional instructions for code reviews:\n\n${projectGuidelines}`;
|
|
1449
|
-
}
|
|
1659
|
+
const fullPrompt = composeReviewPrompt(prompt, {
|
|
1660
|
+
customInstructions: reviewCustomInstructions,
|
|
1661
|
+
extraInstruction: options?.extraInstruction,
|
|
1662
|
+
projectGuidelines,
|
|
1663
|
+
});
|
|
1450
1664
|
|
|
1451
1665
|
const modeHint = useFreshSession ? " (fresh session)" : "";
|
|
1452
1666
|
ctx.ui.notify(`Starting review: ${hint}${modeHint}`, "info");
|
|
@@ -1456,129 +1670,6 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1456
1670
|
return true;
|
|
1457
1671
|
}
|
|
1458
1672
|
|
|
1459
|
-
/**
|
|
1460
|
-
* Parse command arguments for direct invocation
|
|
1461
|
-
* Returns the target or a special marker for PR that needs async handling
|
|
1462
|
-
*/
|
|
1463
|
-
type ParsedReviewArgs = {
|
|
1464
|
-
target: ReviewTarget | { type: "pr"; ref: string } | null;
|
|
1465
|
-
extraInstruction?: string;
|
|
1466
|
-
error?: string;
|
|
1467
|
-
};
|
|
1468
|
-
|
|
1469
|
-
function tokenizeArgs(value: string): string[] {
|
|
1470
|
-
const tokens: string[] = [];
|
|
1471
|
-
let current = "";
|
|
1472
|
-
let quote: '"' | "'" | null = null;
|
|
1473
|
-
|
|
1474
|
-
for (let i = 0; i < value.length; i++) {
|
|
1475
|
-
const char = value[i];
|
|
1476
|
-
|
|
1477
|
-
if (quote) {
|
|
1478
|
-
if (char === "\\" && i + 1 < value.length) {
|
|
1479
|
-
current += value[i + 1];
|
|
1480
|
-
i += 1;
|
|
1481
|
-
continue;
|
|
1482
|
-
}
|
|
1483
|
-
if (char === quote) {
|
|
1484
|
-
quote = null;
|
|
1485
|
-
continue;
|
|
1486
|
-
}
|
|
1487
|
-
current += char;
|
|
1488
|
-
continue;
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
if (char === '"' || char === "'") {
|
|
1492
|
-
quote = char;
|
|
1493
|
-
continue;
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
if (/\s/.test(char)) {
|
|
1497
|
-
if (current.length > 0) {
|
|
1498
|
-
tokens.push(current);
|
|
1499
|
-
current = "";
|
|
1500
|
-
}
|
|
1501
|
-
continue;
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
current += char;
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
if (current.length > 0) {
|
|
1508
|
-
tokens.push(current);
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
return tokens;
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
function parseArgs(args: string | undefined): ParsedReviewArgs {
|
|
1515
|
-
if (!args?.trim()) return { target: null };
|
|
1516
|
-
|
|
1517
|
-
const rawParts = tokenizeArgs(args.trim());
|
|
1518
|
-
const parts: string[] = [];
|
|
1519
|
-
let extraInstruction: string | undefined;
|
|
1520
|
-
|
|
1521
|
-
for (let i = 0; i < rawParts.length; i++) {
|
|
1522
|
-
const part = rawParts[i];
|
|
1523
|
-
if (part === "--extra") {
|
|
1524
|
-
const next = rawParts[i + 1];
|
|
1525
|
-
if (!next) {
|
|
1526
|
-
return { target: null, error: "Missing value for --extra" };
|
|
1527
|
-
}
|
|
1528
|
-
extraInstruction = next;
|
|
1529
|
-
i += 1;
|
|
1530
|
-
continue;
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
if (part.startsWith("--extra=")) {
|
|
1534
|
-
extraInstruction = part.slice("--extra=".length);
|
|
1535
|
-
continue;
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
parts.push(part);
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
if (parts.length === 0) {
|
|
1542
|
-
return { target: null, extraInstruction };
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
const subcommand = parts[0]?.toLowerCase();
|
|
1546
|
-
|
|
1547
|
-
switch (subcommand) {
|
|
1548
|
-
case "uncommitted":
|
|
1549
|
-
return { target: { type: "uncommitted" }, extraInstruction };
|
|
1550
|
-
|
|
1551
|
-
case "branch": {
|
|
1552
|
-
const branch = parts[1];
|
|
1553
|
-
if (!branch) return { target: null, extraInstruction };
|
|
1554
|
-
return { target: { type: "baseBranch", branch }, extraInstruction };
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
case "commit": {
|
|
1558
|
-
const sha = parts[1];
|
|
1559
|
-
if (!sha) return { target: null, extraInstruction };
|
|
1560
|
-
const title = parts.slice(2).join(" ") || undefined;
|
|
1561
|
-
return { target: { type: "commit", sha, title }, extraInstruction };
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
case "folder": {
|
|
1566
|
-
const paths = parseReviewPaths(parts.slice(1).join(" "));
|
|
1567
|
-
if (paths.length === 0) return { target: null, extraInstruction };
|
|
1568
|
-
return { target: { type: "folder", paths }, extraInstruction };
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
case "pr": {
|
|
1572
|
-
const ref = parts[1];
|
|
1573
|
-
if (!ref) return { target: null, extraInstruction };
|
|
1574
|
-
return { target: { type: "pr", ref }, extraInstruction };
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
default:
|
|
1578
|
-
return { target: null, extraInstruction };
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
1673
|
/**
|
|
1583
1674
|
* Handle PR checkout and return a ReviewTarget (or null on failure)
|
|
1584
1675
|
*/
|
|
@@ -1754,7 +1845,7 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1754
1845
|
pi.registerCommand("review", {
|
|
1755
1846
|
description: "Review code changes (PR, uncommitted, branch, commit, or folder)",
|
|
1756
1847
|
handler: async (args, ctx) => {
|
|
1757
|
-
if (
|
|
1848
|
+
if (ctx.mode !== "tui") {
|
|
1758
1849
|
ctx.ui.notify("Review requires interactive mode", "error");
|
|
1759
1850
|
return;
|
|
1760
1851
|
}
|
|
@@ -1859,50 +1950,6 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1859
1950
|
},
|
|
1860
1951
|
});
|
|
1861
1952
|
|
|
1862
|
-
// Custom prompt for review summaries - focuses on preserving actionable findings
|
|
1863
|
-
const REVIEW_SUMMARY_PROMPT = `We are leaving a code-review branch and returning to the main coding branch.
|
|
1864
|
-
Create a structured handoff that can be used immediately to implement fixes.
|
|
1865
|
-
|
|
1866
|
-
You MUST summarize the review that happened in this branch so findings can be acted on.
|
|
1867
|
-
Do not omit findings: include every actionable issue that was identified.
|
|
1868
|
-
|
|
1869
|
-
Required sections (in order):
|
|
1870
|
-
|
|
1871
|
-
## Review Scope
|
|
1872
|
-
- What was reviewed (files/paths, changes, and scope)
|
|
1873
|
-
|
|
1874
|
-
## Verdict
|
|
1875
|
-
- "correct" or "needs attention"
|
|
1876
|
-
|
|
1877
|
-
## Findings
|
|
1878
|
-
For EACH finding, include:
|
|
1879
|
-
- Priority tag ([P0]..[P3]) and short title
|
|
1880
|
-
- File location (\`path/to/file.ext:line\`)
|
|
1881
|
-
- Why it matters (brief)
|
|
1882
|
-
- What should change (brief, actionable)
|
|
1883
|
-
|
|
1884
|
-
## Fix Queue
|
|
1885
|
-
1. Ordered implementation checklist (highest priority first)
|
|
1886
|
-
|
|
1887
|
-
## Constraints & Preferences
|
|
1888
|
-
- Any constraints or preferences mentioned during review
|
|
1889
|
-
- Or "(none)"
|
|
1890
|
-
|
|
1891
|
-
## Human Reviewer Callouts (Non-Blocking)
|
|
1892
|
-
Include only applicable callouts (no yes/no lines):
|
|
1893
|
-
- **This change adds a database migration:** <files/details>
|
|
1894
|
-
- **This change introduces a new dependency:** <package(s)/details>
|
|
1895
|
-
- **This change changes a dependency (or the lockfile):** <files/package(s)/details>
|
|
1896
|
-
- **This change modifies auth/permission behavior:** <what changed and where>
|
|
1897
|
-
- **This change introduces backwards-incompatible public schema/API/contract changes:** <what changed and where>
|
|
1898
|
-
- **This change includes irreversible or destructive operations:** <operation and scope>
|
|
1899
|
-
|
|
1900
|
-
If none apply, write "- (none)".
|
|
1901
|
-
|
|
1902
|
-
These are informational callouts for humans and are not fix items by themselves.
|
|
1903
|
-
|
|
1904
|
-
Preserve exact file paths, function names, and error messages where available.`;
|
|
1905
|
-
|
|
1906
1953
|
const REVIEW_FIX_FINDINGS_PROMPT = `Use the latest review summary in this session and implement the review findings now.
|
|
1907
1954
|
|
|
1908
1955
|
Instructions:
|
|
@@ -1954,7 +2001,7 @@ Instructions:
|
|
|
1954
2001
|
originId: string,
|
|
1955
2002
|
showLoader: boolean,
|
|
1956
2003
|
): Promise<{ cancelled: boolean; error?: string } | null> {
|
|
1957
|
-
if (showLoader && ctx.
|
|
2004
|
+
if (showLoader && ctx.mode === "tui") {
|
|
1958
2005
|
return ctx.ui.custom<{ cancelled: boolean; error?: string } | null>((tui, theme, _kb, done) => {
|
|
1959
2006
|
const loader = new BorderedLoader(tui, theme, "Returning and summarizing review branch...");
|
|
1960
2007
|
loader.onAbort = () => done(null);
|
|
@@ -2062,6 +2109,13 @@ Instructions:
|
|
|
2062
2109
|
return;
|
|
2063
2110
|
}
|
|
2064
2111
|
|
|
2112
|
+
if (!getActiveReviewOrigin(ctx)) {
|
|
2113
|
+
if (!getReviewState(ctx)?.active) {
|
|
2114
|
+
ctx.ui.notify("Not in a review branch (use /review first, or review was started in current session mode)", "info");
|
|
2115
|
+
}
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2065
2119
|
if (endReviewInProgress) {
|
|
2066
2120
|
ctx.ui.notify("/end-review is already running", "info");
|
|
2067
2121
|
return;
|
package/package.json
CHANGED