@plannotator/pi-extension 0.19.10 → 0.19.11
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/generated/agent-review-message.ts +148 -0
- package/generated/claude-review.ts +5 -1
- package/generated/codex-review.ts +0 -82
- package/generated/jj-core.ts +329 -0
- package/generated/review-args.ts +86 -0
- package/generated/review-core.ts +73 -1
- package/generated/tour-review.ts +17 -33
- package/generated/vcs-core.ts +452 -0
- package/index.ts +9 -4
- package/package.json +1 -1
- package/plannotator-browser.ts +143 -133
- package/plannotator-events.ts +5 -1
- package/plannotator.html +3 -3
- package/review-editor.html +98 -51
- package/server/serverReview.ts +30 -102
- package/server/vcs.ts +120 -0
- package/server.ts +17 -3
package/server/serverReview.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
2
|
import { readFileSync, existsSync } from "node:fs";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import os from "node:os";
|
|
@@ -25,18 +25,10 @@ import {
|
|
|
25
25
|
} from "../generated/pr-provider.js";
|
|
26
26
|
import {
|
|
27
27
|
type DiffType,
|
|
28
|
-
type GitCommandResult,
|
|
29
28
|
type GitContext,
|
|
30
|
-
type GitDiffOptions,
|
|
31
|
-
detectRemoteDefaultBranch,
|
|
32
29
|
getFileContentsForDiff as getFileContentsForDiffCore,
|
|
33
|
-
getGitContext as getGitContextCore,
|
|
34
|
-
gitAddFile as gitAddFileCore,
|
|
35
|
-
gitResetFile as gitResetFileCore,
|
|
36
30
|
parseWorktreeDiffType,
|
|
37
31
|
resolveBaseBranch,
|
|
38
|
-
type ReviewGitRuntime,
|
|
39
|
-
runGitDiff as runGitDiffCore,
|
|
40
32
|
validateFilePath,
|
|
41
33
|
} from "../generated/review-core.js";
|
|
42
34
|
import {
|
|
@@ -79,12 +71,12 @@ import {
|
|
|
79
71
|
import { getRepoInfo } from "./project.js";
|
|
80
72
|
import {
|
|
81
73
|
CODEX_REVIEW_SYSTEM_PROMPT,
|
|
82
|
-
buildCodexReviewUserMessage,
|
|
83
74
|
buildCodexCommand,
|
|
84
75
|
generateOutputPath,
|
|
85
76
|
parseCodexOutput,
|
|
86
77
|
transformReviewFindings,
|
|
87
78
|
} from "../generated/codex-review.js";
|
|
79
|
+
import { buildAgentReviewUserMessage } from "../generated/agent-review-message.js";
|
|
88
80
|
import {
|
|
89
81
|
CLAUDE_REVIEW_PROMPT,
|
|
90
82
|
buildClaudeCommand,
|
|
@@ -92,6 +84,17 @@ import {
|
|
|
92
84
|
transformClaudeFindings,
|
|
93
85
|
} from "../generated/claude-review.js";
|
|
94
86
|
import { createTourSession, TOUR_EMPTY_OUTPUT_ERROR } from "../generated/tour-review.js";
|
|
87
|
+
import {
|
|
88
|
+
canStageFiles,
|
|
89
|
+
detectRemoteDefaultCompareTarget,
|
|
90
|
+
getVcsContext,
|
|
91
|
+
getVcsFileContentsForDiff,
|
|
92
|
+
resolveVcsCwd,
|
|
93
|
+
reviewRuntime,
|
|
94
|
+
runVcsDiff,
|
|
95
|
+
stageFile,
|
|
96
|
+
unstageFile,
|
|
97
|
+
} from "./vcs.js";
|
|
95
98
|
|
|
96
99
|
/** Detect if running inside WSL (Windows Subsystem for Linux) */
|
|
97
100
|
function detectWSL(): boolean {
|
|
@@ -121,65 +124,6 @@ export interface ReviewServerResult {
|
|
|
121
124
|
stop: () => void;
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
export const reviewRuntime: ReviewGitRuntime = {
|
|
125
|
-
runGit(
|
|
126
|
-
args: string[],
|
|
127
|
-
options?: { cwd?: string; timeoutMs?: number },
|
|
128
|
-
): Promise<GitCommandResult> {
|
|
129
|
-
return new Promise((resolve) => {
|
|
130
|
-
const proc = spawn("git", ["-c", "core.quotePath=false", ...args], {
|
|
131
|
-
cwd: options?.cwd,
|
|
132
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
136
|
-
if (options?.timeoutMs) {
|
|
137
|
-
timer = setTimeout(() => proc.kill(), options.timeoutMs);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const stdoutChunks: Buffer[] = [];
|
|
141
|
-
const stderrChunks: Buffer[] = [];
|
|
142
|
-
proc.stdout!.on("data", (chunk: Buffer) => stdoutChunks.push(chunk));
|
|
143
|
-
proc.stderr!.on("data", (chunk: Buffer) => stderrChunks.push(chunk));
|
|
144
|
-
|
|
145
|
-
proc.on("close", (code) => {
|
|
146
|
-
if (timer) clearTimeout(timer);
|
|
147
|
-
resolve({
|
|
148
|
-
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
149
|
-
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
150
|
-
exitCode: code ?? 1,
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
proc.on("error", () => {
|
|
155
|
-
if (timer) clearTimeout(timer);
|
|
156
|
-
resolve({ stdout: "", stderr: "git not found", exitCode: 1 });
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
async readTextFile(path: string): Promise<string | null> {
|
|
162
|
-
try {
|
|
163
|
-
return readFileSync(path, "utf-8");
|
|
164
|
-
} catch {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
export function getGitContext(cwd?: string): Promise<GitContext> {
|
|
171
|
-
return getGitContextCore(reviewRuntime, cwd);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function runGitDiff(
|
|
175
|
-
diffType: DiffType,
|
|
176
|
-
defaultBranch = "main",
|
|
177
|
-
cwd?: string,
|
|
178
|
-
options?: GitDiffOptions,
|
|
179
|
-
): Promise<{ patch: string; label: string; error?: string }> {
|
|
180
|
-
return runGitDiffCore(reviewRuntime, diffType, defaultBranch, cwd, options);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
127
|
export async function startReviewServer(options: {
|
|
184
128
|
rawPatch: string;
|
|
185
129
|
gitRef: string;
|
|
@@ -214,6 +158,7 @@ export async function startReviewServer(options: {
|
|
|
214
158
|
let prMeta = options.prMetadata;
|
|
215
159
|
const isPRMode = !!prMeta;
|
|
216
160
|
const hasLocalAccess = !!options.gitContext;
|
|
161
|
+
const sessionVcsType = options.gitContext?.vcsType;
|
|
217
162
|
const isRemote = isRemoteSession();
|
|
218
163
|
const wslFlag = detectWSL();
|
|
219
164
|
let prRef = prMeta ? prRefFromMetadata(prMeta) : null;
|
|
@@ -280,12 +225,14 @@ export async function startReviewServer(options: {
|
|
|
280
225
|
// read this (not gitContext.defaultBranch) so they analyze the same diff
|
|
281
226
|
// the reviewer is currently looking at. Honors an explicit initialBase from
|
|
282
227
|
// the caller — e.g. programmatic Pi callers can request a non-detected base.
|
|
283
|
-
|
|
228
|
+
const detectedCompareTarget = (): string =>
|
|
229
|
+
options.gitContext?.defaultBranch || options.gitContext?.compareTarget?.fallback || "main";
|
|
230
|
+
let currentBase = options.initialBase || detectedCompareTarget();
|
|
284
231
|
let baseEverSwitched = false;
|
|
285
232
|
|
|
286
233
|
// Fire-and-forget: query the remote for its actual default branch.
|
|
287
234
|
if (options.gitContext && !options.initialBase && !isPRMode) {
|
|
288
|
-
|
|
235
|
+
detectRemoteDefaultCompareTarget(options.gitContext.cwd, sessionVcsType).then((remote) => {
|
|
289
236
|
if (remote && !baseEverSwitched) currentBase = remote;
|
|
290
237
|
});
|
|
291
238
|
}
|
|
@@ -298,11 +245,7 @@ export async function startReviewServer(options: {
|
|
|
298
245
|
if (poolPath) return poolPath;
|
|
299
246
|
}
|
|
300
247
|
if (options.agentCwd) return options.agentCwd;
|
|
301
|
-
|
|
302
|
-
const parsed = parseWorktreeDiffType(currentDiffType);
|
|
303
|
-
if (parsed) return parsed.path;
|
|
304
|
-
}
|
|
305
|
-
return options.gitContext?.cwd ?? process.cwd();
|
|
248
|
+
return resolveVcsCwd(currentDiffType, options.gitContext?.cwd) ?? process.cwd();
|
|
306
249
|
}
|
|
307
250
|
const tour = createTourSession();
|
|
308
251
|
|
|
@@ -344,7 +287,7 @@ export async function startReviewServer(options: {
|
|
|
344
287
|
return built ? { ...built, prUrl: launchPrUrl, diffScope: launchDiffScope, diffContext } : built;
|
|
345
288
|
}
|
|
346
289
|
|
|
347
|
-
const userMessage =
|
|
290
|
+
const userMessage = buildAgentReviewUserMessage(currentPatch, currentDiffType, userMessageOptions, prMeta);
|
|
348
291
|
|
|
349
292
|
if (provider === "codex") {
|
|
350
293
|
const model = typeof config?.model === "string" && config.model ? config.model : undefined;
|
|
@@ -644,13 +587,13 @@ export async function startReviewServer(options: {
|
|
|
644
587
|
if (typeof body.hideWhitespace === "boolean") {
|
|
645
588
|
currentHideWhitespace = body.hideWhitespace;
|
|
646
589
|
}
|
|
647
|
-
const detectedBase =
|
|
590
|
+
const detectedBase = detectedCompareTarget();
|
|
648
591
|
const base = resolveBaseBranch(
|
|
649
592
|
typeof body.base === "string" ? body.base : undefined,
|
|
650
593
|
detectedBase,
|
|
651
594
|
);
|
|
652
595
|
const defaultCwd = options.gitContext?.cwd;
|
|
653
|
-
const result = await
|
|
596
|
+
const result = await runVcsDiff(newType, base, defaultCwd, {
|
|
654
597
|
hideWhitespace: currentHideWhitespace,
|
|
655
598
|
});
|
|
656
599
|
currentPatch = result.patch;
|
|
@@ -666,9 +609,8 @@ export async function startReviewServer(options: {
|
|
|
666
609
|
let updatedContext: GitContext | undefined;
|
|
667
610
|
if (options.gitContext) {
|
|
668
611
|
try {
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
updatedContext = await getGitContextCore(reviewRuntime, effectiveCwd);
|
|
612
|
+
const effectiveCwd = resolveVcsCwd(newType, options.gitContext.cwd);
|
|
613
|
+
updatedContext = await getVcsContext(effectiveCwd, sessionVcsType);
|
|
672
614
|
} catch {
|
|
673
615
|
/* best-effort */
|
|
674
616
|
}
|
|
@@ -981,14 +923,13 @@ export async function startReviewServer(options: {
|
|
|
981
923
|
|
|
982
924
|
// Local mode first (matches Bun server priority)
|
|
983
925
|
if (hasLocalAccess && !isPRMode) {
|
|
984
|
-
const detectedBase =
|
|
926
|
+
const detectedBase = detectedCompareTarget();
|
|
985
927
|
const base = resolveBaseBranch(
|
|
986
928
|
url.searchParams.get("base") ?? undefined,
|
|
987
929
|
detectedBase,
|
|
988
930
|
);
|
|
989
931
|
const defaultCwd = options.gitContext?.cwd;
|
|
990
|
-
const result = await
|
|
991
|
-
reviewRuntime,
|
|
932
|
+
const result = await getVcsFileContentsForDiff(
|
|
992
933
|
currentDiffType,
|
|
993
934
|
base,
|
|
994
935
|
filePath,
|
|
@@ -1043,13 +984,8 @@ export async function startReviewServer(options: {
|
|
|
1043
984
|
} else if (url.pathname === "/api/agents" && req.method === "GET") {
|
|
1044
985
|
json(res, { agents: [] });
|
|
1045
986
|
} else if (url.pathname === "/api/git-add" && req.method === "POST") {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
const baseDiffType = currentDiffType.startsWith("worktree:")
|
|
1049
|
-
? (parseWorktreeDiffType(currentDiffType)?.subType ?? currentDiffType)
|
|
1050
|
-
: currentDiffType;
|
|
1051
|
-
const canStage = baseDiffType === "uncommitted" || baseDiffType === "unstaged";
|
|
1052
|
-
if (isPRMode || !canStage) {
|
|
987
|
+
const stageCwd = resolveVcsCwd(currentDiffType, options.gitContext?.cwd);
|
|
988
|
+
if (isPRMode || !(await canStageFiles(currentDiffType, stageCwd))) {
|
|
1053
989
|
json(res, { error: "Staging not available" }, 400);
|
|
1054
990
|
return;
|
|
1055
991
|
}
|
|
@@ -1060,18 +996,10 @@ export async function startReviewServer(options: {
|
|
|
1060
996
|
json(res, { error: "Missing filePath" }, 400);
|
|
1061
997
|
return;
|
|
1062
998
|
}
|
|
1063
|
-
let cwd: string | undefined;
|
|
1064
|
-
if (currentDiffType.startsWith("worktree:")) {
|
|
1065
|
-
const parsed = parseWorktreeDiffType(currentDiffType);
|
|
1066
|
-
if (parsed) cwd = parsed.path;
|
|
1067
|
-
}
|
|
1068
|
-
if (!cwd) {
|
|
1069
|
-
cwd = options.gitContext?.cwd;
|
|
1070
|
-
}
|
|
1071
999
|
if (body.undo) {
|
|
1072
|
-
await
|
|
1000
|
+
await unstageFile(currentDiffType, filePath, stageCwd);
|
|
1073
1001
|
} else {
|
|
1074
|
-
await
|
|
1002
|
+
await stageFile(currentDiffType, filePath, stageCwd);
|
|
1075
1003
|
}
|
|
1076
1004
|
json(res, { ok: true });
|
|
1077
1005
|
} catch (err) {
|
package/server/vcs.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
type DiffResult,
|
|
5
|
+
type DiffType,
|
|
6
|
+
type GitCommandResult,
|
|
7
|
+
type GitContext,
|
|
8
|
+
type GitDiffOptions,
|
|
9
|
+
type ReviewGitRuntime,
|
|
10
|
+
getGitContext as getGitContextCore,
|
|
11
|
+
runGitDiff as runGitDiffCore,
|
|
12
|
+
} from "../generated/review-core.js";
|
|
13
|
+
import {
|
|
14
|
+
type ReviewJjRuntime,
|
|
15
|
+
} from "../generated/jj-core.js";
|
|
16
|
+
import {
|
|
17
|
+
type VcsSelection,
|
|
18
|
+
createGitProvider,
|
|
19
|
+
createJjProvider,
|
|
20
|
+
createVcsApi,
|
|
21
|
+
resolveInitialDiffType,
|
|
22
|
+
} from "../generated/vcs-core.js";
|
|
23
|
+
|
|
24
|
+
function runCommand(
|
|
25
|
+
command: string,
|
|
26
|
+
args: string[],
|
|
27
|
+
notFoundMessage: string,
|
|
28
|
+
options?: { cwd?: string; timeoutMs?: number },
|
|
29
|
+
): Promise<GitCommandResult> {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const proc = spawn(command, args, {
|
|
32
|
+
cwd: options?.cwd,
|
|
33
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
37
|
+
if (options?.timeoutMs) {
|
|
38
|
+
timer = setTimeout(() => proc.kill(), options.timeoutMs);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const stdoutChunks: Buffer[] = [];
|
|
42
|
+
const stderrChunks: Buffer[] = [];
|
|
43
|
+
proc.stdout!.on("data", (chunk: Buffer) => stdoutChunks.push(chunk));
|
|
44
|
+
proc.stderr!.on("data", (chunk: Buffer) => stderrChunks.push(chunk));
|
|
45
|
+
|
|
46
|
+
proc.on("close", (code) => {
|
|
47
|
+
if (timer) clearTimeout(timer);
|
|
48
|
+
resolve({
|
|
49
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
50
|
+
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
51
|
+
exitCode: code ?? 1,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
proc.on("error", () => {
|
|
56
|
+
if (timer) clearTimeout(timer);
|
|
57
|
+
resolve({ stdout: "", stderr: notFoundMessage, exitCode: 1 });
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const reviewRuntime: ReviewGitRuntime = {
|
|
63
|
+
runGit(
|
|
64
|
+
args: string[],
|
|
65
|
+
options?: { cwd?: string; timeoutMs?: number },
|
|
66
|
+
): Promise<GitCommandResult> {
|
|
67
|
+
return runCommand("git", ["-c", "core.quotePath=false", ...args], "git not found", options);
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async readTextFile(path: string): Promise<string | null> {
|
|
71
|
+
try {
|
|
72
|
+
return readFileSync(path, "utf-8");
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const jjRuntime: ReviewJjRuntime = {
|
|
80
|
+
runJj(
|
|
81
|
+
args: string[],
|
|
82
|
+
options?: { cwd?: string; timeoutMs?: number },
|
|
83
|
+
): Promise<GitCommandResult> {
|
|
84
|
+
return runCommand("jj", args, "jj not found", options);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const api = createVcsApi([
|
|
89
|
+
createJjProvider(jjRuntime),
|
|
90
|
+
createGitProvider(reviewRuntime),
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
export const {
|
|
94
|
+
detectVcs,
|
|
95
|
+
getVcsContext,
|
|
96
|
+
detectRemoteDefaultCompareTarget,
|
|
97
|
+
prepareLocalReviewDiff,
|
|
98
|
+
runVcsDiff,
|
|
99
|
+
getVcsFileContentsForDiff,
|
|
100
|
+
canStageFiles,
|
|
101
|
+
stageFile,
|
|
102
|
+
unstageFile,
|
|
103
|
+
resolveVcsCwd,
|
|
104
|
+
} = api;
|
|
105
|
+
|
|
106
|
+
export { resolveInitialDiffType };
|
|
107
|
+
export type { VcsSelection };
|
|
108
|
+
|
|
109
|
+
export function getGitContext(cwd?: string): Promise<GitContext> {
|
|
110
|
+
return getGitContextCore(reviewRuntime, cwd);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function runGitDiff(
|
|
114
|
+
diffType: DiffType,
|
|
115
|
+
defaultBranch = "main",
|
|
116
|
+
cwd?: string,
|
|
117
|
+
options?: GitDiffOptions,
|
|
118
|
+
): Promise<DiffResult> {
|
|
119
|
+
return runGitDiffCore(reviewRuntime, diffType, defaultBranch, cwd, options);
|
|
120
|
+
}
|
package/server.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type {
|
|
|
11
11
|
DiffType,
|
|
12
12
|
GitContext,
|
|
13
13
|
} from "./generated/review-core.js";
|
|
14
|
+
export type { VcsSelection } from "./server/vcs.js";
|
|
14
15
|
export {
|
|
15
16
|
type AnnotateServerResult,
|
|
16
17
|
startAnnotateServer,
|
|
@@ -20,9 +21,22 @@ export {
|
|
|
20
21
|
startPlanReviewServer,
|
|
21
22
|
} from "./server/serverPlan.js";
|
|
22
23
|
export {
|
|
23
|
-
getGitContext,
|
|
24
|
-
reviewRuntime,
|
|
25
24
|
type ReviewServerResult,
|
|
26
|
-
runGitDiff,
|
|
27
25
|
startReviewServer,
|
|
28
26
|
} from "./server/serverReview.js";
|
|
27
|
+
export {
|
|
28
|
+
canStageFiles,
|
|
29
|
+
detectRemoteDefaultCompareTarget,
|
|
30
|
+
detectVcs,
|
|
31
|
+
getGitContext,
|
|
32
|
+
getVcsContext,
|
|
33
|
+
getVcsFileContentsForDiff,
|
|
34
|
+
prepareLocalReviewDiff,
|
|
35
|
+
resolveInitialDiffType,
|
|
36
|
+
resolveVcsCwd,
|
|
37
|
+
reviewRuntime,
|
|
38
|
+
runGitDiff,
|
|
39
|
+
runVcsDiff,
|
|
40
|
+
stageFile,
|
|
41
|
+
unstageFile,
|
|
42
|
+
} from "./server/vcs.js";
|