mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
feat(review): add jj review workflows (#675)
* feat(review): add jj support for local diffs * feat(review): add jj review workflows * fix(review): tighten jj diff defaults * test(review): add jj manual sandbox * fix(review): share jj agent diff prompts * fix(review): quote jj agent revsets * feat(review): share jj vcs handling with pi * fix(review): tighten jj bookmark and pi pr handling * fix(review): tighten jj defaults and detection * fix(review): harden jj diff and vcs detection --------- Co-authored-by: Michael Ramos <mdramos8@gmail.com>
This commit is contained in:
+26
-19
@@ -11,8 +11,8 @@
|
||||
|
||||
import { isRemoteSession, getServerHostname, getServerPort } from "./remote";
|
||||
import type { Origin } from "@plannotator/shared/agents";
|
||||
import { type DiffType, type GitContext, runVcsDiff, getVcsFileContentsForDiff, canStageFiles, stageFile, unstageFile, resolveVcsCwd, validateFilePath, getVcsContext, gitRuntime } from "./vcs";
|
||||
import { parseWorktreeDiffType, detectRemoteDefaultBranch, resolveBaseBranch } from "@plannotator/shared/review-core";
|
||||
import { type DiffType, type GitContext, runVcsDiff, getVcsFileContentsForDiff, canStageFiles, stageFile, unstageFile, resolveVcsCwd, validateFilePath, getVcsContext, detectRemoteDefaultCompareTarget, gitRuntime } from "./vcs";
|
||||
import { parseWorktreeDiffType, resolveBaseBranch } from "@plannotator/shared/review-core";
|
||||
import {
|
||||
getPRDiffScopeOptions,
|
||||
getPRStackInfo,
|
||||
@@ -31,12 +31,12 @@ import { createExternalAnnotationHandler } from "./external-annotations";
|
||||
import { createAgentJobHandler } from "./agent-jobs";
|
||||
import {
|
||||
CODEX_REVIEW_SYSTEM_PROMPT,
|
||||
buildCodexReviewUserMessage,
|
||||
buildCodexCommand,
|
||||
generateOutputPath,
|
||||
parseCodexOutput,
|
||||
transformReviewFindings,
|
||||
} from "./codex-review";
|
||||
import { buildAgentReviewUserMessage } from "./agent-review-message";
|
||||
import {
|
||||
CLAUDE_REVIEW_PROMPT,
|
||||
buildClaudeCommand,
|
||||
@@ -139,6 +139,7 @@ export async function startReviewServer(
|
||||
let prMetadata = options.prMetadata;
|
||||
const isPRMode = !!prMetadata;
|
||||
const hasLocalAccess = !!gitContext;
|
||||
const sessionVcsType = gitContext?.vcsType;
|
||||
let draftKey = contentHash(options.rawPatch);
|
||||
const editorAnnotations = createEditorAnnotationHandler();
|
||||
const externalAnnotations = createExternalAnnotationHandler("review");
|
||||
@@ -164,15 +165,20 @@ export async function startReviewServer(
|
||||
// read this (not gitContext.defaultBranch) so they analyze the same diff
|
||||
// the reviewer is currently looking at. Honors an explicit initialBase from
|
||||
// the caller — e.g. programmatic Pi callers can request a non-detected base.
|
||||
let currentBase = options.initialBase || gitContext?.defaultBranch || "main";
|
||||
const detectedCompareTarget = (): string => gitContext?.defaultBranch || gitContext?.compareTarget?.fallback || "main";
|
||||
let currentBase = options.initialBase || detectedCompareTarget();
|
||||
let baseEverSwitched = false;
|
||||
|
||||
const resolveReviewBase = (requestedBase?: string): string => {
|
||||
return resolveBaseBranch(requestedBase, detectedCompareTarget());
|
||||
};
|
||||
|
||||
// Fire-and-forget: query the remote for its actual default branch. If it
|
||||
// arrives before the user interacts, quietly upgrade currentBase from the
|
||||
// local fallback (e.g. "main") to the upstream ref (e.g. "origin/main").
|
||||
// Non-blocking — the server is already listening by the time this resolves.
|
||||
if (gitContext && !options.initialBase && !isPRMode) {
|
||||
detectRemoteDefaultBranch(gitRuntime, gitContext.cwd).then((remote) => {
|
||||
detectRemoteDefaultCompareTarget(gitContext.cwd, sessionVcsType).then((remote) => {
|
||||
if (remote && !baseEverSwitched) currentBase = remote;
|
||||
});
|
||||
}
|
||||
@@ -225,7 +231,7 @@ export async function startReviewServer(
|
||||
return built ? { ...built, prUrl: launchPrUrl, diffScope: launchDiffScope, diffContext } : built;
|
||||
}
|
||||
|
||||
const userMessage = buildCodexReviewUserMessage(currentPatch, currentDiffType, userMessageOptions, prMetadata);
|
||||
const userMessage = buildAgentReviewUserMessage(currentPatch, currentDiffType, userMessageOptions, prMetadata);
|
||||
|
||||
if (provider === "codex") {
|
||||
const model = typeof config?.model === "string" && config.model ? config.model : undefined;
|
||||
@@ -415,6 +421,12 @@ export async function startReviewServer(
|
||||
let repoInfo = isPRMode && prMetadata
|
||||
? { display: getDisplayRepo(prMetadata), branch: `${getMRLabel(prMetadata)} ${getMRNumberLabel(prMetadata)}` }
|
||||
: await getRepoInfo();
|
||||
if (gitContext?.repository?.displayFallback) {
|
||||
repoInfo = {
|
||||
...repoInfo,
|
||||
display: repoInfo?.display || gitContext.repository.displayFallback,
|
||||
};
|
||||
}
|
||||
|
||||
// Fetch current platform user (for own-PR/MR detection)
|
||||
let prRef = isPRMode && prMetadata ? prRefFromMetadata(prMetadata) : null;
|
||||
@@ -560,12 +572,11 @@ export async function startReviewServer(
|
||||
currentHideWhitespace = body.hideWhitespace;
|
||||
}
|
||||
|
||||
const detectedBase = gitContext?.defaultBranch || "main";
|
||||
// Guard against non-string payloads — resolveBaseBranch calls
|
||||
// string methods and would throw a TypeError otherwise. Mirrors
|
||||
// Pi's guard so both runtimes validate identically.
|
||||
const requestedBase = typeof body.base === "string" ? body.base : undefined;
|
||||
const base = resolveBaseBranch(requestedBase, detectedBase);
|
||||
const base = resolveReviewBase(requestedBase);
|
||||
const defaultCwd = gitContext?.cwd;
|
||||
|
||||
// Run the new diff
|
||||
@@ -590,7 +601,7 @@ export async function startReviewServer(
|
||||
if (gitContext) {
|
||||
try {
|
||||
const effectiveCwd = resolveVcsCwd(newDiffType, gitContext.cwd);
|
||||
updatedContext = await getVcsContext(effectiveCwd);
|
||||
updatedContext = await getVcsContext(effectiveCwd, sessionVcsType);
|
||||
} catch {
|
||||
/* best-effort */
|
||||
}
|
||||
@@ -863,11 +874,8 @@ export async function startReviewServer(
|
||||
|
||||
// Local review: read file contents from local git
|
||||
if (hasLocalAccess) {
|
||||
const detectedBase = gitContext?.defaultBranch || "main";
|
||||
const base = resolveBaseBranch(
|
||||
url.searchParams.get("base") ?? undefined,
|
||||
detectedBase,
|
||||
);
|
||||
const requestedBase = url.searchParams.get("base") ?? undefined;
|
||||
const base = resolveReviewBase(requestedBase);
|
||||
const defaultCwd = gitContext?.cwd;
|
||||
const result = await getVcsFileContentsForDiff(
|
||||
currentDiffType,
|
||||
@@ -896,7 +904,8 @@ export async function startReviewServer(
|
||||
|
||||
// API: Stage / unstage a file (disabled when VCS doesn't support it)
|
||||
if (url.pathname === "/api/git-add" && req.method === "POST") {
|
||||
if (isPRMode || !canStageFiles(currentDiffType)) {
|
||||
const stageCwd = resolveVcsCwd(currentDiffType, gitContext?.cwd);
|
||||
if (isPRMode || !(await canStageFiles(currentDiffType, stageCwd))) {
|
||||
return Response.json(
|
||||
{ error: "Staging not available" },
|
||||
{ status: 400 },
|
||||
@@ -908,12 +917,10 @@ export async function startReviewServer(
|
||||
return Response.json({ error: "Missing filePath" }, { status: 400 });
|
||||
}
|
||||
|
||||
const cwd = resolveVcsCwd(currentDiffType, gitContext?.cwd);
|
||||
|
||||
if (body.undo) {
|
||||
await unstageFile(currentDiffType, body.filePath, cwd);
|
||||
await unstageFile(currentDiffType, body.filePath, stageCwd);
|
||||
} else {
|
||||
await stageFile(currentDiffType, body.filePath, cwd);
|
||||
await stageFile(currentDiffType, body.filePath, stageCwd);
|
||||
}
|
||||
|
||||
return Response.json({ ok: true });
|
||||
|
||||
Reference in New Issue
Block a user