@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.
@@ -1,4 +1,4 @@
1
- import { execSync, spawn } from "node:child_process";
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
- let currentBase = options.initialBase || options.gitContext?.defaultBranch || "main";
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
- detectRemoteDefaultBranch(reviewRuntime, options.gitContext.cwd).then((remote) => {
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
- if (currentDiffType.startsWith("worktree:")) {
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 = buildCodexReviewUserMessage(currentPatch, currentDiffType, userMessageOptions, prMeta);
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 = options.gitContext?.defaultBranch || "main";
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 runGitDiff(newType, base, defaultCwd, {
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 worktreeParsed = parseWorktreeDiffType(newType);
670
- const effectiveCwd = worktreeParsed?.path ?? options.gitContext.cwd;
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 = options.gitContext?.defaultBranch || "main";
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 getFileContentsForDiffCore(
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
- // Staging only available for local diff types that support it (not PR mode, not branch diffs).
1047
- // Worktree diff types use composite format "worktree:/path:uncommitted" — extract the base type.
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 gitResetFileCore(reviewRuntime, filePath, cwd);
1000
+ await unstageFile(currentDiffType, filePath, stageCwd);
1073
1001
  } else {
1074
- await gitAddFileCore(reviewRuntime, filePath, cwd);
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";