Skip to content
Merged
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ User runs /plannotator-review command
Claude Code: plannotator review subcommand runs
OpenCode: event handler intercepts command
git diff captures unstaged changes
VCS diff captures local changes (git diff or jj diff)
Review server starts, opens browser with diff viewer
Expand Down
2 changes: 1 addition & 1 deletion apps/hook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ The plugin registers three slash commands:

| Command | Description |
|---------|-------------|
| `/plannotator-review` | Open code review UI for uncommitted changes or a GitHub PR |
| `/plannotator-review [--git]` | Open code review UI for current changes or a GitHub PR; `--git` forces Git in JJ workspaces |
| `/plannotator-annotate <file.md>` | Annotate any markdown file |
| `/plannotator-last` | Annotate the agent's last message |

Expand Down
2 changes: 1 addition & 1 deletion apps/hook/commands/plannotator-review.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: Open interactive code review for current changes or a PR URL
description: Open interactive code review for current changes or a PR URL; pass --git to force Git in JJ workspaces
allowed-tools: Bash(plannotator:*)
disable-model-invocation: true
---
Expand Down
2 changes: 1 addition & 1 deletion apps/hook/server/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe("CLI top-level help", () => {

expect(output).toContain("plannotator --help");
expect(output).toContain("plannotator [--browser <name>]");
expect(output).toContain("plannotator review [PR_URL]");
expect(output).toContain("plannotator review [--git] [PR_URL]");
expect(output).toContain("plannotator annotate <file.md | file.html | https://... | folder/>");
expect(output).toContain("running 'plannotator' without arguments is for hook integration");
});
Expand Down
2 changes: 1 addition & 1 deletion apps/hook/server/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function formatTopLevelHelp(): string {
"Usage:",
" plannotator --help",
" plannotator [--browser <name>]",
" plannotator review [PR_URL]",
" plannotator review [--git] [PR_URL]",
" plannotator annotate <file.md | file.html | https://... | folder/> [--no-jina] [--gate] [--json] [--hook]",
" plannotator last",
" plannotator archive",
Expand Down
34 changes: 15 additions & 19 deletions apps/hook/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* - Reads hook event from stdin, extracts plan content
* - Serves UI, returns approve/deny decision to stdout
*
* 2. Code Review (`plannotator review`):
* 2. Code Review (`plannotator review`, `plannotator review --git`):
* - Triggered by /review slash command
* - Runs git diff, opens review UI
* - Outputs feedback to stdout (captured by slash command)
Expand Down Expand Up @@ -63,8 +63,9 @@ import {
startAnnotateServer,
handleAnnotateServerReady,
} from "@plannotator/server/annotate";
import { type DiffType, getVcsContext, runVcsDiff, gitRuntime } from "@plannotator/server/vcs";
import { type DiffType, prepareLocalReviewDiff, gitRuntime } from "@plannotator/server/vcs";
import { loadConfig, resolveDefaultDiffType, resolveUseJina } from "@plannotator/shared/config";
import { parseReviewArgs } from "@plannotator/shared/review-args";
import { stripAtPrefix, resolveAtReference } from "@plannotator/shared/at-reference";
import { htmlToMarkdown } from "@plannotator/shared/html-to-markdown";
import { urlToMarkdown, isConvertedSource } from "@plannotator/shared/url-to-markdown";
Expand Down Expand Up @@ -289,22 +290,15 @@ if (args[0] === "sessions") {
// CODE REVIEW MODE
// ============================================

// Parse local flags (strip before URL detection)
// --local is now the default for PR/MR reviews; --no-local opts out.
// --local kept for backwards compat (no-op).
const localIdx = args.indexOf("--local");
if (localIdx !== -1) args.splice(localIdx, 1);
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 useLocal = isPRMode && noLocalIdx === -1;
const reviewArgs = parseReviewArgs(args.slice(1));
const urlArg = reviewArgs.prUrl;
const isPRMode = urlArg !== undefined;
const useLocal = isPRMode && reviewArgs.useLocal;

let rawPatch: string;
let gitRef: string;
let diffError: string | undefined;
let gitContext: Awaited<ReturnType<typeof getVcsContext>> | undefined;
let gitContext: Awaited<ReturnType<typeof prepareLocalReviewDiff>>["gitContext"] | undefined;
let prMetadata: Awaited<ReturnType<typeof fetchPR>>["metadata"] | undefined;
let initialDiffType: DiffType | undefined;
let agentCwd: string | undefined;
Expand Down Expand Up @@ -499,14 +493,16 @@ if (args[0] === "sessions") {
}
} else {
// --- Local Review Mode ---
gitContext = await getVcsContext();
const config = loadConfig();
initialDiffType = gitContext.vcsType === "p4" ? "p4-default" : resolveDefaultDiffType(config);
const diffResult = await runVcsDiff(initialDiffType, gitContext.defaultBranch, undefined, {
const diffResult = await prepareLocalReviewDiff({
vcsType: reviewArgs.vcsType,
configuredDiffType: resolveDefaultDiffType(config),
hideWhitespace: config.diffOptions?.hideWhitespace ?? false,
});
rawPatch = diffResult.patch;
gitRef = diffResult.label;
gitContext = diffResult.gitContext;
initialDiffType = diffResult.diffType;
rawPatch = diffResult.rawPatch;
gitRef = diffResult.gitRef;
diffError = diffResult.error;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/src/content/docs/guides/ai-code-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ claude -p \
--no-session-persistence \
--model sonnet \
--tools Agent,Bash,Read,Glob,Grep \
--allowedTools Agent,Read,Glob,Grep,Bash(gh pr view:*),Bash(gh pr diff:*),Bash(gh pr list:*),Bash(gh issue view:*),Bash(gh issue list:*),Bash(gh api repos/*/*/pulls/*),Bash(gh api repos/*/*/pulls/*/files*),Bash(gh api repos/*/*/pulls/*/comments*),Bash(gh api repos/*/*/issues/*/comments*),Bash(glab mr view:*),Bash(glab mr diff:*),Bash(glab mr list:*),Bash(glab api:*),Bash(git status:*),Bash(git diff:*),Bash(git log:*),Bash(git show:*),Bash(git blame:*),Bash(git branch:*),Bash(git grep:*),Bash(git ls-remote:*),Bash(git ls-tree:*),Bash(git merge-base:*),Bash(git remote:*),Bash(git rev-parse:*),Bash(git show-ref:*),Bash(wc:*) \
--allowedTools Agent,Read,Glob,Grep,Bash(gh pr view:*),Bash(gh pr diff:*),Bash(gh pr list:*),Bash(gh issue view:*),Bash(gh issue list:*),Bash(gh api repos/*/*/pulls/*),Bash(gh api repos/*/*/pulls/*/files*),Bash(gh api repos/*/*/pulls/*/comments*),Bash(gh api repos/*/*/issues/*/comments*),Bash(glab mr view:*),Bash(glab mr diff:*),Bash(glab mr list:*),Bash(glab api:*),Bash(git status:*),Bash(git diff:*),Bash(git log:*),Bash(git show:*),Bash(git blame:*),Bash(git branch:*),Bash(git grep:*),Bash(git ls-remote:*),Bash(git ls-tree:*),Bash(git merge-base:*),Bash(git remote:*),Bash(git rev-parse:*),Bash(git show-ref:*),Bash(jj status:*),Bash(jj diff:*),Bash(jj log:*),Bash(jj show:*),Bash(jj file show:*),Bash(jj cat:*),Bash(jj bookmark list:*),Bash(wc:*) \
--disallowedTools Edit,Write,NotebookEdit,WebFetch,WebSearch,Bash(python:*),Bash(python3:*),Bash(node:*),Bash(npx:*),Bash(bun:*),Bash(bunx:*),Bash(sh:*),Bash(bash:*),Bash(zsh:*),Bash(curl:*),Bash(wget:*)
```

Expand Down
6 changes: 3 additions & 3 deletions apps/marketing/src/content/docs/reference/prompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ The user message Plannotator sends is always:

**Review prompt** is a long, static review instruction that lives in the repo as a TypeScript constant. It's distinct per provider.

**User prompt** is a short, dynamic line built per call from the diff type (`uncommitted`, `staged`, `last-commit`, `branch`, PR URL, and so on). The same builder is used for all providers.
**User prompt** is a short, dynamic line built per call from the diff type (`uncommitted`, `staged`, `last-commit`, `branch`, `jj-current`, PR URL, and so on). Review agents share one builder; Code Tour uses a tour-specific builder with the same diff instructions.

## Matrix

| | Claude review | Codex review | Code Tour (Claude or Codex) |
|---|---|---|---|
| **System prompt** | Owned by `claude` CLI. We don't touch it. | Owned by `codex` CLI. We don't touch it. | Same as whichever engine runs. |
| **Review prompt (static, ours)** | `CLAUDE_REVIEW_PROMPT` in `packages/server/claude-review.ts` | `CODEX_REVIEW_SYSTEM_PROMPT` in `packages/server/codex-review.ts` (misnamed; it's user content) | `TOUR_REVIEW_PROMPT` in `packages/server/tour-review.ts` |
| **User prompt (dynamic, ours)** | `buildCodexReviewUserMessage(patch, diffType, …)` | same function | same function |
| **Review prompt (static, ours)** | `CLAUDE_REVIEW_PROMPT` in `packages/server/claude-review.ts` | `CODEX_REVIEW_SYSTEM_PROMPT` in `packages/server/codex-review.ts` (misnamed; it's user content) | `TOUR_REVIEW_PROMPT` in `packages/server/tour/tour-review.ts` |
| **User prompt (dynamic, ours)** | `buildAgentReviewUserMessage(patch, diffType, …)` | same function | `buildTourUserMessage(patch, diffType, …)` |
| **Full user message** | `review prompt + "\n\n---\n\n" + user prompt` | same | same |
| **Delivered via** | stdin | last positional argv | stdin (Claude engine) or positional argv (Codex engine) |
| **Output schema flag** | `--json-schema <inline JSON>` | `--output-schema <file path>` | same as engine |
Expand Down
25 changes: 15 additions & 10 deletions apps/opencode-plugin/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
startAnnotateServer,
handleAnnotateServerReady,
} from "@plannotator/server/annotate";
import { getGitContext, runGitDiffWithContext } from "@plannotator/server/git";
import { type DiffType, prepareLocalReviewDiff } from "@plannotator/server/vcs";
import { parsePRUrl, checkPRAuth, fetchPR, getCliName, getMRLabel, getMRNumberLabel, getDisplayRepo } from "@plannotator/server/pr";
import { loadConfig, resolveDefaultDiffType, resolveUseJina } from "@plannotator/shared/config";
import {
Expand All @@ -30,6 +30,7 @@ import { resolveMarkdownFile, resolveUserPath, hasMarkdownFiles } from "@plannot
import { FILE_BROWSER_EXCLUDED } from "@plannotator/shared/reference-common";
import { htmlToMarkdown } from "@plannotator/shared/html-to-markdown";
import { parseAnnotateArgs } from "@plannotator/shared/annotate-args";
import { parseReviewArgs } from "@plannotator/shared/review-args";
import { urlToMarkdown, isConvertedSource } from "@plannotator/shared/url-to-markdown";
import { statSync } from "fs";
import path from "path";
Expand All @@ -52,14 +53,15 @@ export async function handleReviewCommand(
const { client, reviewHtmlContent, getSharingEnabled, getShareBaseUrl, directory } = deps;

// @ts-ignore - Event properties contain arguments
const urlArg: string = event.properties?.arguments || "";
const isPRMode = urlArg?.startsWith("http://") || urlArg?.startsWith("https://");
const reviewArgs = parseReviewArgs(event.properties?.arguments || "");
const urlArg = reviewArgs.prUrl;
const isPRMode = urlArg !== undefined;

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 userDiffType: DiffType | undefined;
let gitContext: Awaited<ReturnType<typeof prepareLocalReviewDiff>>["gitContext"] | undefined;
let prMetadata: Awaited<ReturnType<typeof fetchPR>>["metadata"] | undefined;

if (isPRMode) {
Expand Down Expand Up @@ -91,14 +93,17 @@ export async function handleReviewCommand(
} else {
client.app.log({ level: "info", message: "Opening code review UI..." });

gitContext = await getGitContext(directory);
const config = loadConfig();
userDiffType = resolveDefaultDiffType(config);
const diffResult = await runGitDiffWithContext(userDiffType, gitContext, {
const diffResult = await prepareLocalReviewDiff({
cwd: directory,
vcsType: reviewArgs.vcsType,
configuredDiffType: resolveDefaultDiffType(config),
hideWhitespace: config.diffOptions?.hideWhitespace ?? false,
});
rawPatch = diffResult.patch;
gitRef = diffResult.label;
gitContext = diffResult.gitContext;
userDiffType = diffResult.diffType;
rawPatch = diffResult.rawPatch;
gitRef = diffResult.gitRef;
diffError = diffResult.error;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/opencode-plugin/commands/plannotator-review.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: Open interactive code review for current changes or a PR URL
description: Open interactive code review for current changes or a PR URL; pass --git to force Git in JJ workspaces
---

The Plannotator Code Review has been triggered. Opening the review UI...
Expand Down
13 changes: 9 additions & 4 deletions apps/pi-extension/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
getAnnotateMessageFeedbackPrompt,
} from "./generated/prompts.js";
import { parseAnnotateArgs } from "./generated/annotate-args.js";
import { parseReviewArgs } from "./generated/review-args.js";
import { resolveAtReference } from "./generated/at-reference.js";
import {
hasPlanBrowserHtml,
Expand Down Expand Up @@ -404,7 +405,7 @@ export default function plannotator(pi: ExtensionAPI): void {
});

pi.registerCommand("plannotator-review", {
description: "Open interactive code review for current changes or a PR URL",
description: "Open interactive code review for current changes or a PR URL; pass --git to force Git in JJ workspaces",
handler: async (args, ctx) => {
if (!hasReviewBrowserHtml()) {
ctx.ui.notify(
Expand All @@ -418,9 +419,13 @@ export default function plannotator(pi: ExtensionAPI): void {
const origin = getPiSessionIdentity(ctx);

try {
const prUrl = args?.trim() || undefined;
const isPRReview = prUrl?.startsWith("http://") || prUrl?.startsWith("https://");
const session = await startCodeReviewBrowserSession(ctx, { prUrl });
const reviewArgs = parseReviewArgs(args ?? "");
const isPRReview = reviewArgs.prUrl !== undefined;
const session = await startCodeReviewBrowserSession(ctx, {
prUrl: reviewArgs.prUrl,
vcsType: reviewArgs.vcsType,
useLocal: reviewArgs.useLocal,
});
ctx.ui.notify("Code review opened. You can keep chatting while it runs.", "info");
void session
.waitForDecision()
Expand Down
13 changes: 13 additions & 0 deletions apps/pi-extension/plannotator-browser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { describe, expect, test } from "bun:test";
import { shouldUseLocalPrCheckout } from "./plannotator-browser";

describe("shouldUseLocalPrCheckout", () => {
test("uses local PR checkout by default", () => {
expect(shouldUseLocalPrCheckout({})).toBe(true);
expect(shouldUseLocalPrCheckout({ useLocal: true })).toBe(true);
});

test("honors the Pi --no-local opt-out", () => {
expect(shouldUseLocalPrCheckout({ useLocal: false })).toBe(false);
});
});
Loading
Loading