@plannotator/pi-extension 0.16.7 → 0.17.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/generated/agent-jobs.ts +11 -0
- package/generated/claude-review.ts +345 -0
- package/generated/codex-review.ts +382 -0
- package/generated/external-annotation.ts +6 -0
- package/generated/path-utils.ts +19 -0
- package/generated/pr-github.ts +100 -2
- package/generated/pr-gitlab.ts +1 -0
- package/generated/pr-provider.ts +26 -0
- package/generated/resolve-file.ts +56 -3
- package/generated/review-core.ts +32 -0
- package/generated/worktree.ts +120 -0
- package/package.json +1 -1
- package/plannotator.html +78 -77
- package/review-editor.html +586 -269
- package/server/agent-jobs.ts +139 -12
- package/server/external-annotations.ts +8 -0
- package/server/network.test.ts +96 -0
- package/server/network.ts +21 -4
- package/server/serverReview.ts +90 -6
|
@@ -79,6 +79,20 @@ function isSearchableMarkdownPath(input: string): boolean {
|
|
|
79
79
|
return MARKDOWN_PATH_REGEX.test(input.trim());
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
function stripWrappingQuotes(input: string): string {
|
|
83
|
+
if (input.length < 2) {
|
|
84
|
+
return input;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const first = input[0];
|
|
88
|
+
const last = input[input.length - 1];
|
|
89
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
90
|
+
return input.slice(1, -1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return input;
|
|
94
|
+
}
|
|
95
|
+
|
|
82
96
|
/** Check if a path looks like a Windows absolute path (e.g. C:\ or C:/) */
|
|
83
97
|
function hasWindowsDriveLetter(input: string): boolean {
|
|
84
98
|
return /^[a-zA-Z]:[/\\]/.test(input);
|
|
@@ -156,12 +170,10 @@ export function isAbsoluteMarkdownPath(
|
|
|
156
170
|
* @param input - User-provided path (absolute, relative, or bare filename)
|
|
157
171
|
* @param projectRoot - Project root directory to search within
|
|
158
172
|
*/
|
|
159
|
-
|
|
173
|
+
function resolveMarkdownFileCore(
|
|
160
174
|
input: string,
|
|
161
175
|
projectRoot: string,
|
|
162
176
|
): ResolveResult {
|
|
163
|
-
// Trim whitespace/CR that may leak from Windows shell pipelines
|
|
164
|
-
input = input.trim();
|
|
165
177
|
const normalizedInput = normalizeMarkdownPathInput(input);
|
|
166
178
|
const searchInput = normalizeSeparators(normalizedInput);
|
|
167
179
|
const isBareFilename = !searchInput.includes("/");
|
|
@@ -219,6 +231,47 @@ export function resolveMarkdownFile(
|
|
|
219
231
|
return { kind: "not_found", input };
|
|
220
232
|
}
|
|
221
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Resolve a markdown file path within a project root.
|
|
236
|
+
*
|
|
237
|
+
* @param input - User-provided path (absolute, relative, or bare filename)
|
|
238
|
+
* @param projectRoot - Project root directory to search within
|
|
239
|
+
*/
|
|
240
|
+
export function resolveMarkdownFile(
|
|
241
|
+
input: string,
|
|
242
|
+
projectRoot: string,
|
|
243
|
+
): ResolveResult {
|
|
244
|
+
const originalInput = input.trim();
|
|
245
|
+
const unquotedInput = stripWrappingQuotes(originalInput);
|
|
246
|
+
|
|
247
|
+
const primary = resolveMarkdownFileCore(unquotedInput, projectRoot);
|
|
248
|
+
if (primary.kind === "found") {
|
|
249
|
+
return primary;
|
|
250
|
+
}
|
|
251
|
+
if (primary.kind === "ambiguous") {
|
|
252
|
+
return { ...primary, input: originalInput };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!unquotedInput.startsWith("@")) {
|
|
256
|
+
return { kind: "not_found", input: originalInput };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const normalizedInput = unquotedInput.replace(/^@+/, "");
|
|
260
|
+
if (!normalizedInput) {
|
|
261
|
+
return { kind: "not_found", input: originalInput };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const fallback = resolveMarkdownFileCore(normalizedInput, projectRoot);
|
|
265
|
+
if (fallback.kind === "found") {
|
|
266
|
+
return fallback;
|
|
267
|
+
}
|
|
268
|
+
if (fallback.kind === "ambiguous") {
|
|
269
|
+
return { ...fallback, input: originalInput };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { kind: "not_found", input: originalInput };
|
|
273
|
+
}
|
|
274
|
+
|
|
222
275
|
/**
|
|
223
276
|
* Check if a directory contains at least one markdown file.
|
|
224
277
|
* Used to validate folder annotation targets.
|
package/generated/review-core.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type DiffType =
|
|
|
14
14
|
| "unstaged"
|
|
15
15
|
| "last-commit"
|
|
16
16
|
| "branch"
|
|
17
|
+
| "merge-base"
|
|
17
18
|
| `worktree:${string}`
|
|
18
19
|
| "p4-default"
|
|
19
20
|
| `p4-changelist:${string}`;
|
|
@@ -151,6 +152,7 @@ export async function getGitContext(
|
|
|
151
152
|
|
|
152
153
|
if (currentBranch !== defaultBranch) {
|
|
153
154
|
diffOptions.push({ id: "branch", label: `vs ${defaultBranch}` });
|
|
155
|
+
diffOptions.push({ id: "merge-base", label: `Current PR Diff` });
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
const [worktrees, currentTreePathResult] = await Promise.all([
|
|
@@ -386,6 +388,28 @@ export async function runGitDiff(
|
|
|
386
388
|
break;
|
|
387
389
|
}
|
|
388
390
|
|
|
391
|
+
case "merge-base": {
|
|
392
|
+
const mergeBaseResult = assertGitSuccess(
|
|
393
|
+
await runtime.runGit(["merge-base", defaultBranch, "HEAD"], { cwd }),
|
|
394
|
+
["merge-base", defaultBranch, "HEAD"],
|
|
395
|
+
);
|
|
396
|
+
const mergeBase = mergeBaseResult.stdout.trim();
|
|
397
|
+
const mergeBaseDiffArgs = [
|
|
398
|
+
"diff",
|
|
399
|
+
"--no-ext-diff",
|
|
400
|
+
`${mergeBase}..HEAD`,
|
|
401
|
+
"--src-prefix=a/",
|
|
402
|
+
"--dst-prefix=b/",
|
|
403
|
+
];
|
|
404
|
+
const mergeBaseDiff = assertGitSuccess(
|
|
405
|
+
await runtime.runGit(mergeBaseDiffArgs, { cwd }),
|
|
406
|
+
mergeBaseDiffArgs,
|
|
407
|
+
);
|
|
408
|
+
patch = mergeBaseDiff.stdout;
|
|
409
|
+
label = `PR diff vs ${defaultBranch}`;
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
|
|
389
413
|
default:
|
|
390
414
|
return { patch: "", label: "Unknown diff type" };
|
|
391
415
|
}
|
|
@@ -475,6 +499,14 @@ export async function getFileContentsForDiff(
|
|
|
475
499
|
oldContent: await gitShow(defaultBranch, oldFilePath),
|
|
476
500
|
newContent: await gitShow("HEAD", filePath),
|
|
477
501
|
};
|
|
502
|
+
case "merge-base": {
|
|
503
|
+
const mbResult = await runtime.runGit(["merge-base", defaultBranch, "HEAD"], { cwd });
|
|
504
|
+
const mb = mbResult.exitCode === 0 ? mbResult.stdout.trim() : defaultBranch;
|
|
505
|
+
return {
|
|
506
|
+
oldContent: await gitShow(mb, oldFilePath),
|
|
507
|
+
newContent: await gitShow("HEAD", filePath),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
478
510
|
default:
|
|
479
511
|
return { oldContent: null, newContent: null };
|
|
480
512
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// @generated — DO NOT EDIT. Source: packages/shared/worktree.ts
|
|
2
|
+
/**
|
|
3
|
+
* Worktree — runtime-agnostic git worktree primitives.
|
|
4
|
+
*
|
|
5
|
+
* Uses ReviewGitRuntime so both Bun and Node runtimes can use the same logic.
|
|
6
|
+
* Lives in packages/shared/ and gets vendored to Pi via vendor.sh.
|
|
7
|
+
*
|
|
8
|
+
* Designed as composable primitives, not tied to any specific use case.
|
|
9
|
+
* PR local checkout, agent sandboxes, parallel sessions — all compose from these.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ReviewGitRuntime } from "./review-core";
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Types
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export interface CreateWorktreeOptions {
|
|
19
|
+
/** Git ref to check out (branch name, SHA, FETCH_HEAD, etc.) */
|
|
20
|
+
ref: string;
|
|
21
|
+
/** Absolute path where the worktree will be created. */
|
|
22
|
+
path: string;
|
|
23
|
+
/** Create in detached HEAD mode (no branch). Default: false. */
|
|
24
|
+
detach?: boolean;
|
|
25
|
+
/** CWD of the source repository. Defaults to process.cwd(). */
|
|
26
|
+
cwd?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface RemoveWorktreeOptions {
|
|
30
|
+
/** Force removal even if the worktree has modifications. Default: false. */
|
|
31
|
+
force?: boolean;
|
|
32
|
+
/** CWD of the source repository. Required if the worktree was created from a different cwd. */
|
|
33
|
+
cwd?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Primitives
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Fetch a ref from origin.
|
|
42
|
+
* Runs: `git fetch origin <ref>`
|
|
43
|
+
* Throws on failure.
|
|
44
|
+
*/
|
|
45
|
+
export async function fetchRef(
|
|
46
|
+
runtime: ReviewGitRuntime,
|
|
47
|
+
ref: string,
|
|
48
|
+
options?: { cwd?: string },
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
const result = await runtime.runGit(["fetch", "origin", "--", ref], { cwd: options?.cwd });
|
|
51
|
+
if (result.exitCode !== 0) {
|
|
52
|
+
throw new Error(`git fetch origin ${ref} failed: ${result.stderr.trim() || `exit code ${result.exitCode}`}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Ensure a git object (commit SHA) is available locally.
|
|
58
|
+
* Checks with `git cat-file -t`, fetches from origin if missing.
|
|
59
|
+
* Returns true if the object is available after the attempt.
|
|
60
|
+
*/
|
|
61
|
+
export async function ensureObjectAvailable(
|
|
62
|
+
runtime: ReviewGitRuntime,
|
|
63
|
+
sha: string,
|
|
64
|
+
options?: { cwd?: string },
|
|
65
|
+
): Promise<boolean> {
|
|
66
|
+
const check = await runtime.runGit(["cat-file", "-t", sha], { cwd: options?.cwd });
|
|
67
|
+
if (check.exitCode === 0) return true;
|
|
68
|
+
|
|
69
|
+
// Object missing locally — try fetching it
|
|
70
|
+
const fetch = await runtime.runGit(["fetch", "origin", "--", sha], { cwd: options?.cwd });
|
|
71
|
+
if (fetch.exitCode !== 0) return false;
|
|
72
|
+
|
|
73
|
+
// Verify it's now available
|
|
74
|
+
const recheck = await runtime.runGit(["cat-file", "-t", sha], { cwd: options?.cwd });
|
|
75
|
+
return recheck.exitCode === 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create a git worktree.
|
|
80
|
+
* Runs: `git worktree add [--detach] <path> <ref>`
|
|
81
|
+
* Throws on failure with a descriptive error.
|
|
82
|
+
*/
|
|
83
|
+
export async function createWorktree(
|
|
84
|
+
runtime: ReviewGitRuntime,
|
|
85
|
+
options: CreateWorktreeOptions,
|
|
86
|
+
): Promise<{ worktreePath: string }> {
|
|
87
|
+
const args = ["worktree", "add"];
|
|
88
|
+
if (options.detach) args.push("--detach");
|
|
89
|
+
args.push(options.path, options.ref);
|
|
90
|
+
|
|
91
|
+
const result = await runtime.runGit(args, { cwd: options.cwd });
|
|
92
|
+
if (result.exitCode !== 0) {
|
|
93
|
+
throw new Error(`git worktree add failed: ${result.stderr.trim() || `exit code ${result.exitCode}`}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { worktreePath: options.path };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Remove a git worktree. Best-effort — logs errors but does not throw.
|
|
101
|
+
* Runs: `git worktree remove [--force] <path>`
|
|
102
|
+
*/
|
|
103
|
+
export async function removeWorktree(
|
|
104
|
+
runtime: ReviewGitRuntime,
|
|
105
|
+
worktreePath: string,
|
|
106
|
+
options?: RemoveWorktreeOptions,
|
|
107
|
+
): Promise<void> {
|
|
108
|
+
const args = ["worktree", "remove"];
|
|
109
|
+
if (options?.force) args.push("--force");
|
|
110
|
+
args.push(worktreePath);
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const result = await runtime.runGit(args, { cwd: options?.cwd });
|
|
114
|
+
if (result.exitCode !== 0) {
|
|
115
|
+
console.error(`Warning: git worktree remove failed for ${worktreePath}: ${result.stderr.trim()}`);
|
|
116
|
+
}
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.error(`Warning: worktree cleanup error: ${err instanceof Error ? err.message : String(err)}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
package/package.json
CHANGED