import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import type { Strata } from "./implementation.js"; import { closeDebug, dbg, span } from "./src/debug.ts"; import { findExecutable } from "./src/pi-ext-executable.ts"; export default function strata(pi: ExtensionAPI) { let runtime: Strata | undefined; let generation = 0; const openings = new Set>(); // Shared so repeated invocations evaluate ./implementation.js once. // Pi's jiti loader (module cache disabled) can otherwise hand one caller a // partially initialized module whose imports are still undefined. let implementationModule: | Promise | undefined; const loadImplementation = (): Promise< typeof import("./implementation.js") > => { implementationModule ??= import("./implementation.js"); return implementationModule; }; pi.on("session_start", (_event, ctx) => { dbg?.("session.start"); const version = ++generation; void hasGit().then((available) => { if (!available && generation === version) ctx.ui.notify("Strata requires Git on PATH.", "warning"); }); }); pi.on("session_shutdown", async () => { dbg?.("session.shutdown"); generation++; const stopped = runtime?.dispose(); runtime = undefined; await Promise.allSettled([...openings, stopped]); closeDebug(); }); pi.registerCommand("strata", { description: "Review Git changes as semantic layers in a local browser", getArgumentCompletions(prefix) { return ["--staged", "--base", "--pr", "cancel"] .filter((value) => value.startsWith(prefix)) .map((value) => ({ value, label: value })); }, async handler(args, ctx) { const finish = span?.("review.open"); if (args.trim() === "cancel") { generation++; await runtime?.close(); finish?.("finish", { outcome: "cancelled" }); return; } try { const version = generation; const { Strata } = await loadImplementation(); if (generation !== version) { finish?.("finish", { outcome: "superseded" }); return; } runtime ??= new Strata(pi); const opening = runtime.open(args, ctx); openings.add(opening); let outcome: Awaited; try { outcome = await opening; } finally { openings.delete(opening); } finish?.("finish", { outcome }); } catch (error) { finish?.("error"); throw error; } }, }); } async function hasGit(): Promise { return Boolean( findExecutable(process.platform === "win32" ? ["git.exe"] : ["git"]), ); }