Created
September 11, 2026 11:51
-
-
Save jprochazk/deec51d6928d2db9cc6fc049a8800793 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { spawn, spawnSync } from "node:child_process"; | |
| import fs from "node:fs"; | |
| import os from "node:os"; | |
| import path from "node:path"; | |
| import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; | |
| type RevdiffResult = { status: "complete"; content: string } | { status: "failed"; exitCode: number | null }; | |
| function splitArgs(input: string): string[] { | |
| const args: string[] = []; | |
| const re = /"([^"]*)"|'([^']*)'|(\S+)/g; | |
| let match: RegExpExecArray | null; | |
| while ((match = re.exec(input)) !== null) { | |
| args.push(match[1] ?? match[2] ?? match[3]); | |
| } | |
| return args; | |
| } | |
| function defaultArgs(cwd: string): string[] { | |
| const status = spawnSync("git", ["status", "--porcelain"], { cwd, encoding: "utf-8" }); | |
| if (status.status === 0 && status.stdout.trim() === "") { | |
| return ["HEAD~1", "HEAD"]; | |
| } | |
| return ["HEAD", "--untracked"]; | |
| } | |
| async function runRevdiff(args: string[], cwd: string): Promise<RevdiffResult> { | |
| const directory = fs.mkdtempSync(path.join(os.tmpdir(), "pi-revdiff-")); | |
| const outputPath = path.join(directory, "annotations.md"); | |
| try { | |
| const exitCode = await new Promise<number | null>((resolve) => { | |
| const child = spawn("revdiff", [...args, "-o", outputPath], { | |
| cwd, | |
| stdio: "inherit", | |
| env: process.env, | |
| }); | |
| child.on("error", () => resolve(null)); | |
| child.on("close", (code) => resolve(code)); | |
| }); | |
| if (exitCode !== 0 && exitCode !== 10) { | |
| return { status: "failed", exitCode }; | |
| } | |
| if (!fs.existsSync(outputPath)) { | |
| return { status: "complete", content: "" }; | |
| } | |
| return { status: "complete", content: fs.readFileSync(outputPath, "utf-8").replace(/\n$/, "") }; | |
| } finally { | |
| try { | |
| fs.rmSync(directory, { recursive: true, force: true }); | |
| } catch {} | |
| } | |
| } | |
| export default function (pi: ExtensionAPI) { | |
| pi.registerCommand("revdiff", { | |
| description: "Review changes in revdiff; annotations go into the prompt", | |
| handler: async (args, ctx) => { | |
| if (ctx.mode !== "tui") { | |
| ctx.ui.notify("revdiff requires interactive mode", "error"); | |
| return; | |
| } | |
| const result = await ctx.ui.custom<RevdiffResult>((tui, _theme, _kb, done) => { | |
| tui.stop(); | |
| process.stdout.write("\x1b[2J\x1b[H"); | |
| const revdiffArgs = args.trim() ? splitArgs(args) : defaultArgs(ctx.cwd); | |
| void runRevdiff(revdiffArgs, ctx.cwd).then( | |
| (res) => { | |
| tui.start(); | |
| tui.requestRender(true); | |
| done(res); | |
| }, | |
| () => { | |
| tui.start(); | |
| tui.requestRender(true); | |
| done({ status: "failed", exitCode: null }); | |
| }, | |
| ); | |
| return { render: () => [], invalidate: () => {} }; | |
| }); | |
| if (result.status === "failed") { | |
| ctx.ui.notify( | |
| result.exitCode === null | |
| ? "Failed to launch revdiff (is it installed?)" | |
| : `revdiff exited with status ${result.exitCode}`, | |
| "error", | |
| ); | |
| return; | |
| } | |
| if (!result.content.trim()) { | |
| ctx.ui.notify("revdiff produced no annotations", "info"); | |
| return; | |
| } | |
| const existing = ctx.ui.getEditorText(); | |
| ctx.ui.setEditorText(existing.trim() ? `${existing.replace(/\n$/, "")}\n\n${result.content}` : result.content); | |
| ctx.ui.notify("Loaded revdiff annotations into the editor", "info"); | |
| }, | |
| }); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment