import { AsyncLocalStorage } from "node:async_hooks"; import { execFile } from "node:child_process"; import { appendFile, chmod, copyFile, mkdir, mkdtemp, rm, writeFile, } from "node:fs/promises"; import { tmpdir } from "node:os"; import path, { delimiter } from "node:path"; import { promisify } from "node:util"; import { type Context, fauxAssistantMessage, fauxProvider, } from "@earendil-works/pi-ai"; import { stripTerminalSequences } from "@earendil-works/pi-tui"; import { createTestSession } from "../../../test/harness"; import { cleanGitEnvironment } from "../git.js"; import type { Hunk, Plan } from "../types.js"; const nativeScope = new AsyncLocalStorage<{ signal: AbortSignal; pending: Set>; cancelling: Promise[]; cleanup: AsyncDisposableStack; }>(); // Used by Vitest aroundEach; no runner import, so Playwright can use this file too. export async function nativeTest( run: () => Promise, context: { signal: AbortSignal }, ) { const controller = new AbortController(); const scope = { signal: AbortSignal.any([context.signal, controller.signal]), pending: new Set>(), cancelling: [] as Promise[], cleanup: new AsyncDisposableStack(), }; await nativeScope.run(scope, async () => { try { await run(); } finally { controller.abort(); while (scope.pending.size) await Promise.allSettled([...scope.pending]); await scope.cleanup.disposeAsync(); } }); } export function testSignal(signal?: AbortSignal): AbortSignal { const parent = nativeScope.getStore()?.signal; return parent && signal ? AbortSignal.any([parent, signal]) : (parent ?? signal ?? new AbortController().signal); } export { own as nativeOperation }; function own(work: () => Promise): Promise { const scope = nativeScope.getStore(); let pending: Promise; try { pending = work(); } catch (error) { pending = Promise.reject(error); } scope?.pending.add(pending); void pending.then( () => scope?.pending.delete(pending), () => scope?.pending.delete(pending), ); return pending; } export function cleanupNative(cleanup: () => Promise): void { nativeScope.getStore()?.cleanup.defer(cleanup); } const exec = promisify(execFile); export function git(cwd: string, ...args: string[]) { return own(async () => { const signal = testSignal(); signal.throwIfAborted(); const command = exec("git", args, { cwd, signal, env: cleanGitEnvironment(), }); const closed = new Promise((resolve) => command.child.once("close", () => resolve()), ); try { return (await command).stdout.trim(); } finally { await closed; } }); } export function withFakeGh( program: string, check: () => Promise, ): Promise { return own(() => fakeGh(program, check)); } async function fakeGh( program: string, check: () => Promise, ): Promise { testSignal().throwIfAborted(); const root = await mkdtemp(path.join(tmpdir(), "strata-gh-")); try { const bin = path.join(root, "bin"); await mkdir(bin); const executable = path.join( bin, process.platform === "win32" ? "gh.exe" : "gh", ); await copyFile(process.execPath, executable); await chmod(executable, 0o755); const preload = path.join(root, "fake-gh.cjs"); await writeFile( preload, `if (/^gh(?:\\.exe)?$/i.test(require("node:path").basename(process.execPath)) && process.env.STRATA_FAKE_GH_PRELOAD === __filename) {\nconst original = process.argv.slice(1); original[0] = require("node:path").basename(original[0]); process.argv = [process.execPath, "fake-gh", ...(process.execArgv.includes("-c") ? ["-c"] : []), ...original]; const args = process.argv.slice(2);\n${program}\nprocess.exit(process.exitCode ?? 0);\n}`, ); const previousPath = process.env.PATH; const previousOptions = process.env.NODE_OPTIONS; const previousPreload = process.env.STRATA_FAKE_GH_PRELOAD; process.env.PATH = `${bin}${delimiter}${previousPath ?? ""}`; process.env.STRATA_FAKE_GH_PRELOAD = preload; process.env.NODE_OPTIONS = [ previousOptions, `--require=${JSON.stringify(preload)}`, ] .filter(Boolean) .join(" "); try { await check(); } finally { await Promise.allSettled(nativeScope.getStore()?.cancelling ?? []); if (previousPath === undefined) delete process.env.PATH; else process.env.PATH = previousPath; if (previousOptions === undefined) delete process.env.NODE_OPTIONS; else process.env.NODE_OPTIONS = previousOptions; if (previousPreload === undefined) delete process.env.STRATA_FAKE_GH_PRELOAD; else process.env.STRATA_FAKE_GH_PRELOAD = previousPreload; } } finally { await rm(root, { recursive: true, force: true }); } } export function withFakeExecutable( name: string, program: string, check: () => Promise, ): Promise { return own(() => fakeExecutable(name, program, check)); } async function fakeExecutable( name: string, program: string, check: () => Promise, ): Promise { testSignal().throwIfAborted(); const root = await mkdtemp(path.join(tmpdir(), "strata-executable-")); try { const bin = path.join(root, "bin"); await mkdir(bin); const executable = path.join( bin, process.platform === "win32" ? `${name}.exe` : name, ); await copyFile(process.execPath, executable); await chmod(executable, 0o755); const preload = path.join(root, "fake-executable.cjs"); await writeFile( preload, `const executable = require("node:path").basename(process.execPath).toLowerCase(); if (executable === ${JSON.stringify(name.toLowerCase())} || executable === ${JSON.stringify(`${name.toLowerCase()}.exe`)}) { const original = process.argv.slice(1); original[0] = require("node:path").basename(original[0]); process.argv = [process.execPath, ${JSON.stringify(name)}, ...(process.execArgv.includes("-c") ? ["-c"] : []), ...original]; const args = process.argv.slice(2); ${program} process.exit(process.exitCode ?? 0); }`, ); const previousPath = process.env.PATH; const previousOptions = process.env.NODE_OPTIONS; process.env.PATH = `${bin}${delimiter}${previousPath ?? ""}`; process.env.NODE_OPTIONS = [ previousOptions, `--require=${JSON.stringify(preload)}`, ] .filter(Boolean) .join(" "); try { await check(); } finally { await Promise.allSettled(nativeScope.getStore()?.cancelling ?? []); if (previousPath === undefined) delete process.env.PATH; else process.env.PATH = previousPath; if (previousOptions === undefined) delete process.env.NODE_OPTIONS; else process.env.NODE_OPTIONS = previousOptions; } } finally { await rm(root, { recursive: true, force: true }); } } export function fixturePlan(hunks: Array>): Plan { return { summary: "Bound retries before using them.", cohorts: [ { title: "Payment retries", layers: hunks.map((hunk, index) => ({ id: `layer-${index + 1}`, title: `Review ${hunk.path}`, summary: `Layer ${index + 1} explains the original captured change.`, hunks: [{ id: hunk.id, summary: `Explanation for ${hunk.path}.` }], flow: ["Contract", "Consumer", "Tests"], })), }, ], }; } export function openFixture(mode: "tui" | "rpc" | "json" = "tui") { return own(() => createFixture(mode)); } async function createFixture(mode: "tui" | "rpc" | "json") { const signal = testSignal(); signal.throwIfAborted(); const root = await mkdtemp(path.join(tmpdir(), "strata-pi-")); let t: Awaited> | undefined; let closing: Promise | undefined; const close = () => (closing ??= (async () => { try { // Harness dispose does not emit shutdown or await extension work. await t?.session.extensionRunner.emit({ type: "session_shutdown", reason: "quit", }); } finally { try { t?.dispose(); } finally { await rm(root, { recursive: true, force: true }); } } })()); cleanupNative(close); try { await git(root, "init", "--quiet"); await appendFile( path.join(root, ".git", "config"), "[user]\n\tname = Strata Test\n\temail = strata@example.invalid\n", ); await writeFile( path.join(root, "contract.ts"), "export const attempts = 1;\n", ); await writeFile( path.join(root, "consumer.ts"), "export const retry = false;\n", ); await git(root, "add", "."); await git(root, "commit", "--quiet", "-m", "base"); await writeFile( path.join(root, "contract.ts"), "export const attempts = 3;\n", ); await writeFile( path.join(root, "consumer.ts"), "export const retry = true;\n", ); t = await createTestSession({ cwd: root, env: { STRATA_NO_OPEN: "1" }, extensions: [path.resolve(import.meta.dirname, "../index.ts")], }); const session = t; signal.throwIfAborted(); const runner = t.session.extensionRunner; const emit = runner.emit.bind(runner); runner.emit = (...args: Parameters) => own(() => emit(...args)); const base = runner.createContext(); const faux = fauxProvider({ provider: "strata-integration", models: [{ id: "review", maxTokens: 16_384 }], }); let calls = 0; const prompts: string[] = []; const respond = (context: Context) => { calls++; const message = context.messages.findLast((item) => item.role === "user"); const text = typeof message?.content === "string" ? message.content : (message?.content ?? []) .filter((part) => part.type === "text") .map((part) => part.text) .join(""); prompts.push(text); if (text.startsWith("Organize")) { const hunks = [ ...text.matchAll( /\s* ({ id, path: filePath })); return fauxAssistantMessage(JSON.stringify(fixturePlan(hunks))); } return fauxAssistantMessage("Three attempts include the first call."); }; faux.setResponses(Array.from({ length: 20 }, () => respond)); base.modelRegistry.registerProvider(faux.provider); const model = base.modelRegistry.find("strata-integration", "review"); if (!model) throw new Error("Fixture model missing"); await t.session.setModel(model); const notifications: Array<{ text: string; severity: string }> = []; const widgets: Array<{ key: string; content: unknown; options: unknown; }> = []; const ctx = Object.create(base); Object.defineProperties(ctx, { model: { value: model }, hasUI: { value: mode !== "json" }, waitForIdle: { value: async () => {} }, mode: { value: mode }, ui: { value: { ...base.ui, notify: (text: string, severity = "info") => notifications.push({ text, severity }), setWidget: (key: string, content: unknown, options: unknown) => { widgets.push({ key, content, options }); }, }, }, }); const command = runner.extensions[0].commands.get("strata"); const scope = nativeScope.getStore(); const cancel = () => { const stopped = own(() => command.handler("cancel", ctx)); scope?.cancelling.push(stopped); }; signal.throwIfAborted(); signal.addEventListener("abort", cancel, { once: true }); cleanupNative(async () => { signal.removeEventListener("abort", cancel); }); const run = (args = "") => own(async () => { signal.throwIfAborted(); await command.handler(args, ctx); signal.throwIfAborted(); }); return { t, root, ctx, runner, command, faux, notifications, widgets, prompts, run, widgetLines(width = 120): string[] { const content = widgets.findLast( (item) => item.key === "strata", )?.content; if (content === undefined) return []; if (Array.isArray(content)) return content as string[]; if (typeof content !== "function") return []; const theme = { bold: (text: string) => text, fg: (_color: string, text: string) => text, }; return content({}, theme).render(width); }, widgetText(width = 120): string { return this.widgetLines(width).map(stripTerminalSequences).join("\n"); }, get calls() { return calls; }, get url(): string { const line = this.widgetLines(160)[1] ?? ""; const match = /\x1b]8;;([^\x1b]+)\x1b\\/.exec(line); if (match?.[1]) return match[1]; const notice = notifications.findLast((item) => item.text.startsWith("Strata review: "), ); if (notice) return notice.text.slice("Strata review: ".length); const message = session.events.messages.findLast( (item) => item.role === "custom" && item.customType === "strata-status" && typeof item.content === "string" && item.content.startsWith("Strata review: "), ); if (message?.role === "custom" && typeof message.content === "string") return message.content.slice("Strata review: ".length); throw new Error(`No review URL: ${line}`); }, close, }; } catch (error) { await close(); throw error; } }