Agent-JAE/default-extensions/pr-review.ts
jae a1b6e22c01 feat: add 16 default extensions for Agent JAE
New extensions shipping with Agent JAE:
- jae-rules: Per-project .jae/rules.md injection
- cost-tracker: Token/cost tracking with budget limits
- project-dna: Auto-analyze project fingerprint
- teach-mode: Pedagogical explanation mode
- bookmarks: File bookmark management
- replay: Session recording and asciicast export
- checkpoints: Git-based undo/time-travel
- pr-review: Autonomous PR diff review
- auto-docs: Auto-generate README and docs
- screenshot-context: Vision analysis for screenshots
- deploy: One-command deploy (Docker/Vercel/rsync/static)
- pair-programming: Real-time file watching with feedback
- dashboard: Terminal HUD with live stats
- swarm: Multi-agent parallel task execution
- skill-marketplace: Search/install/publish skills
- widget-api-demo: TUI widget API demonstration
2026-03-23 20:14:41 +01:00

112 lines
3.3 KiB
TypeScript

import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
import { Type } from "@sinclair/typebox";
export default function (pi: ExtensionAPI) {
async function reviewDiff(diff: string, ctx: any): Promise<string> {
if (!diff.trim()) return "No diff content to review.";
const lines = diff.split("\n");
const stats = {
filesChanged: 0,
additions: 0,
deletions: 0,
files: [] as string[],
};
for (const line of lines) {
if (line.startsWith("+++ b/")) {
stats.filesChanged++;
stats.files.push(line.replace("+++ b/", ""));
} else if (line.startsWith("+") && !line.startsWith("+++")) {
stats.additions++;
} else if (line.startsWith("-") && !line.startsWith("---")) {
stats.deletions++;
}
}
return [
`## PR Review Summary`,
``,
`**Files changed:** ${stats.filesChanged}`,
`**Additions:** +${stats.additions}`,
`**Deletions:** -${stats.deletions}`,
``,
`### Files:`,
...stats.files.map((f) => `- ${f}`),
``,
`### Diff Preview (first 200 lines):`,
"```diff",
lines.slice(0, 200).join("\n"),
"```",
``,
`> Full review requires LLM analysis. Send this diff as context to JAE for detailed review.`,
].join("\n");
}
pi.registerTool({
name: "pr_review",
label: "PR Review",
description: "Review a pull request or git diff. Provide a branch name, PR URL, or commit range.",
parameters: Type.Object({
target: Type.String({ description: "Branch name, PR URL, or commit range (e.g., 'main..feature', 'origin/main')" }),
}),
execute: async (args, ctx) => {
const { target } = args;
let diff = "";
// Try as branch comparison
const diffResult = await pi.exec(
"git",
["diff", target],
{ cwd: ctx.cwd },
);
if (diffResult.code === 0 && diffResult.stdout.trim()) {
diff = diffResult.stdout;
} else {
// Try as range
const rangeResult = await pi.exec(
"git",
["diff", `${target}`],
{ cwd: ctx.cwd },
);
if (rangeResult.code === 0) {
diff = rangeResult.stdout;
} else {
return { error: `Could not get diff for: ${target}. Error: ${diffResult.stderr || rangeResult.stderr}` };
}
}
const review = await reviewDiff(diff, ctx);
return { output: review };
},
});
pi.registerCommand("pr-review", {
description: "Review a PR or branch diff: /pr-review <branch|range>",
handler: async (args, ctx) => {
const target = args.trim();
if (!target) {
ctx.ui.notify("Usage: /pr-review <branch|commit-range>\nExamples:\n /pr-review main..feature\n /pr-review origin/main", "info");
return;
}
ctx.ui.notify(`\u{1F50D} Fetching diff for: ${target}...`, "info");
const diffResult = await pi.exec("git", ["diff", target], { cwd: ctx.cwd });
if (diffResult.code !== 0) {
ctx.ui.notify(`Failed to get diff: ${diffResult.stderr}`, "error");
return;
}
if (!diffResult.stdout.trim()) {
ctx.ui.notify("No differences found.", "info");
return;
}
const review = await reviewDiff(diffResult.stdout, ctx);
ctx.ui.notify(review, "info");
},
});
}