Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions apps/hook/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ import {
startAnnotateServer,
handleAnnotateServerReady,
} from "@plannotator/server/annotate";
import { type DiffType, getVcsContext, runVcsDiff, gitRuntime } from "@plannotator/server/vcs";
import { type DiffType, getVcsContext, runVcsDiff, gitRuntime, detectManagedVcs } from "@plannotator/server/vcs";
import { loadConfig, resolveDefaultDiffType } from "@plannotator/shared/config";
import { fetchRef, createWorktree, removeWorktree, ensureObjectAvailable } from "@plannotator/shared/worktree";
import { parsePRUrl, checkPRAuth, fetchPR, getCliName, getCliInstallUrl, getMRLabel, getMRNumberLabel, getDisplayRepo } from "@plannotator/server/pr";
Expand All @@ -89,6 +89,7 @@ import {
} from "./cli";
import path from "path";
import { tmpdir } from "os";
import { buildWorkspaceLocalRepos, buildWorkspacePRRepos } from "@plannotator/server/review-workspace";

// Embed the built HTML at compile time
// @ts-ignore - Bun import attribute for text
Expand Down Expand Up @@ -196,8 +197,10 @@ if (args[0] === "sessions") {
const noLocalIdx = args.indexOf("--no-local");
if (noLocalIdx !== -1) args.splice(noLocalIdx, 1);

const urlArg = args[1];
const isPRMode = urlArg?.startsWith("http://") || urlArg?.startsWith("https://");
const urlArgs = args.slice(1).filter((arg) => arg.startsWith("http://") || arg.startsWith("https://"));
const urlArg = urlArgs[0];
const isPRMode = urlArgs.length > 0;
const isMultiPRMode = urlArgs.length > 1;
const useLocal = isPRMode && noLocalIdx === -1;

let rawPatch: string;
Expand All @@ -208,8 +211,13 @@ if (args[0] === "sessions") {
let initialDiffType: DiffType | undefined;
let agentCwd: string | undefined;
let worktreeCleanup: (() => void | Promise<void>) | undefined;
let workspaceRepos: Awaited<ReturnType<typeof buildWorkspaceLocalRepos>> | undefined;

if (isPRMode) {
if (isMultiPRMode) {
workspaceRepos = await buildWorkspacePRRepos(urlArgs);
rawPatch = "";
gitRef = "Workspace review";
} else if (isPRMode) {
// --- PR Review Mode ---
const prRef = parsePRUrl(urlArg);
if (!prRef) {
Expand Down Expand Up @@ -379,12 +387,22 @@ if (args[0] === "sessions") {
}
} else {
// --- Local Review Mode ---
gitContext = await getVcsContext();
initialDiffType = gitContext.vcsType === "p4" ? "p4-default" : resolveDefaultDiffType(loadConfig());
const diffResult = await runVcsDiff(initialDiffType, gitContext.defaultBranch);
rawPatch = diffResult.patch;
gitRef = diffResult.label;
diffError = diffResult.error;
const managedVcs = await detectManagedVcs(process.cwd());
if (managedVcs) {
gitContext = await getVcsContext();
initialDiffType = gitContext.vcsType === "p4" ? "p4-default" : resolveDefaultDiffType(loadConfig());
const diffResult = await runVcsDiff(initialDiffType, gitContext.defaultBranch);
rawPatch = diffResult.patch;
gitRef = diffResult.label;
diffError = diffResult.error;
} else {
workspaceRepos = await buildWorkspaceLocalRepos(process.cwd());
if (workspaceRepos.length === 0) {
throw new Error("Not in a git repo and no nested repositories were found.");
}
rawPatch = "";
gitRef = "Workspace review";
}
}

const reviewProject = (await detectProjectName()) ?? "_unknown";
Expand All @@ -398,6 +416,7 @@ if (args[0] === "sessions") {
diffType: gitContext ? (initialDiffType ?? "unstaged") : undefined,
gitContext,
prMetadata,
workspaceRepos,
agentCwd,
sharingEnabled,
shareBaseUrl,
Expand Down
47 changes: 36 additions & 11 deletions apps/opencode-plugin/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import {
handleAnnotateServerReady,
} from "@plannotator/server/annotate";
import { getGitContext, runGitDiffWithContext } from "@plannotator/server/git";
import { detectManagedVcs } from "@plannotator/server/vcs";
import { parsePRUrl, checkPRAuth, fetchPR, getCliName, getMRLabel, getMRNumberLabel, getDisplayRepo } from "@plannotator/server/pr";
import { loadConfig, resolveDefaultDiffType } from "@plannotator/shared/config";
import { resolveMarkdownFile } from "@plannotator/shared/resolve-file";
import { buildWorkspaceLocalRepos, buildWorkspacePRRepos } from "@plannotator/server/review-workspace";

/** Shared dependencies injected by the plugin */
export interface CommandDeps {
Expand All @@ -41,19 +43,26 @@ export async function handleReviewCommand(

// @ts-ignore - Event properties contain arguments
const urlArg: string = event.properties?.arguments || "";
const isPRMode = urlArg?.startsWith("http://") || urlArg?.startsWith("https://");
const urlArgs = urlArg.split(/\s+/).filter((arg: string) => arg.startsWith("http://") || arg.startsWith("https://"));
const isPRMode = urlArgs.length > 0;
const isMultiPRMode = urlArgs.length > 1;

let rawPatch: string;
let gitRef: string;
let diffError: string | undefined;
let userDiffType: import("@plannotator/shared/config").DefaultDiffType | undefined;
let gitContext: Awaited<ReturnType<typeof getGitContext>> | undefined;
let prMetadata: Awaited<ReturnType<typeof fetchPR>>["metadata"] | undefined;

if (isPRMode) {
const prRef = parsePRUrl(urlArg);
let workspaceRepos: Awaited<ReturnType<typeof buildWorkspaceLocalRepos>> | undefined;

if (isMultiPRMode) {
workspaceRepos = await buildWorkspacePRRepos(urlArgs);
rawPatch = "";
gitRef = "Workspace review";
} else if (isPRMode) {
const prRef = parsePRUrl(urlArgs[0]);
if (!prRef) {
client.app.log({ level: "error", message: `Invalid PR/MR URL: ${urlArg}` });
client.app.log({ level: "error", message: `Invalid PR/MR URL: ${urlArgs[0]}` });
return;
}

Expand All @@ -79,12 +88,27 @@ export async function handleReviewCommand(
} else {
client.app.log({ level: "info", message: "Opening code review UI..." });

gitContext = await getGitContext(directory);
userDiffType = resolveDefaultDiffType(loadConfig());
const diffResult = await runGitDiffWithContext(userDiffType, gitContext);
rawPatch = diffResult.patch;
gitRef = diffResult.label;
diffError = diffResult.error;
const managedVcs = await detectManagedVcs(directory);
if (managedVcs) {
gitContext = await getGitContext(directory);
userDiffType = resolveDefaultDiffType(loadConfig());
const diffResult = await runGitDiffWithContext(userDiffType, gitContext);
rawPatch = diffResult.patch;
gitRef = diffResult.label;
diffError = diffResult.error;
} else {
workspaceRepos = await buildWorkspaceLocalRepos(directory || process.cwd());
if (workspaceRepos.length === 0) {
client.app.log({ level: "error", message: "Not in a git repo and no nested repositories were found." });
return;
}
client.app.log({
level: "info",
message: `Workspace mode: found ${workspaceRepos.length} repos (${workspaceRepos.filter((repo) => repo.selected).length} selected with changes).`,
});
rawPatch = "";
gitRef = "Workspace review";
}
}

const server = await startReviewServer({
Expand All @@ -95,6 +119,7 @@ export async function handleReviewCommand(
diffType: isPRMode ? undefined : userDiffType,
gitContext,
prMetadata,
workspaceRepos,
sharingEnabled: await getSharingEnabled(),
shareBaseUrl: getShareBaseUrl(),
htmlContent: reviewHtmlContent,
Expand Down
6 changes: 3 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading