import { lstatSync, mkdtempSync, readdirSync, readFileSync, rmSync, } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { createTestSession, type TestSession } from "../../../test/harness"; import { groupSystemPrompt } from "../index.ts"; describe("prompt-autopsy pi-test-harness", () => { let t: TestSession | undefined; afterEach(() => { t?.dispose(); t = undefined; }); it("groups Pi 0.86 top-level prompt sections without mistaking nested XML", () => { const grouped = groupSystemPrompt(`Custom identity - read: Read files - Be concise General guidance Read carefully Project-specific instructions and guidelines: PROJECT RULE /repo CUSTOM `); expect(grouped).toContain("[PREAMBLE]\nCustom identity"); expect(grouped).toContain("[TOOLS]\n"); expect(grouped).toContain("[RULES]\n"); expect(grouped).toContain("[GENERAL GUIDELINES]\n"); expect(grouped).toContain("[TOOL GUIDELINES]\n"); expect(grouped).toContain("[CTX]\n"); expect(grouped).toContain("[SKILLS]\n"); expect(grouped).toContain("[CWD]\n"); expect(grouped).toContain("[SECTION custom_context]\n"); expect(grouped).not.toContain("[SECTION project_instructions]"); }); it("keeps nested same-name sections intact", () => { const grouped = groupSystemPrompt(` outer inner outer tail /repo `); expect(grouped).toBe(`[APPEND] outer inner outer tail [CWD] /repo `); }); it("writes a private output file and opens it", async () => { const extensionPath = path.resolve(import.meta.dirname, "../index.ts"); t = await createTestSession({ extensions: [extensionPath], env: { VISUAL: `${process.execPath} -e "process.exit(0)"` }, }); await t.session.extensionRunner.emitBeforeAgentStart("prompt", undefined, { cwd: t.cwd, forceSystemPrompt: "\n- read\n", }); await t.session.extensionRunner.emit({ type: "agent_start" } as never); const command = t.session.extensionRunner.extensions[0].commands.get("prompt-autopsy"); await command.handler("", t.session.extensionRunner.createCommandContext()); const notification = t.events.uiCallsFor("notify").at(-1)?.args; expect(notification?.[1]).toBe("info"); const outputPath = String(notification?.[0]).replace(/^wrote /, ""); expect(outputPath).not.toBe("/tmp/sysprompt-autopsy.md"); expect(lstatSync(path.dirname(outputPath)).mode & 0o777).toBe(0o700); expect(lstatSync(outputPath).mode & 0o777).toBe(0o600); expect(readFileSync(outputPath, "utf8")).toContain("[TOOLS]"); rmSync(path.dirname(outputPath), { recursive: true, force: true }); }); it("writes its opt-in lifecycle trace inside the harness sandbox", async () => { const root = mkdtempSync( path.join(tmpdir(), "pi-ext-prompt-autopsy-debug-"), ); try { const extensionPath = path.resolve(import.meta.dirname, "../index.ts"); t = await createTestSession({ cwd: root, extensions: [extensionPath], env: { PI_PROMPT_AUTOPSY_DEBUG: "1" }, }); const runner = t.session.extensionRunner; await runner.emit({ type: "session_start", reason: "startup" }); await runner.extensions[0].commands .get("prompt-autopsy") ?.handler("", runner.createCommandContext()); await runner.emit({ type: "session_shutdown", reason: "quit" }); const stateHome = t.env.XDG_STATE_HOME; if (!stateHome) throw new Error("missing sandbox state home"); const directory = path.join( stateHome, "pi-ext", "debug", "prompt-autopsy", ); const [file] = readdirSync(directory); const events = readFileSync(path.join(directory, file), "utf8") .trim() .split("\n") .map((line) => JSON.parse(line).event); expect(events).toEqual( expect.arrayContaining([ "session.start", "session.shutdown", "prompt.skip", ]), ); } finally { t?.dispose(); t = undefined; rmSync(root, { recursive: true, force: true }); } }); it("loads in real Pi extension runtime", async () => { const extensionPath = path.resolve(import.meta.dirname, "../index.ts"); t = await createTestSession({ extensions: [extensionPath] }); expect(t.session.extensionRunner.extensions).toHaveLength(1); expect(t.session.extensionRunner.extensions[0].path).toBe(extensionPath); const command = t.session.extensionRunner.extensions[0].commands.get("prompt-autopsy"); await command.handler("", t.session.extensionRunner.createCommandContext()); expect(t.events.uiCallsFor("notify").at(-1)?.args).toEqual([ "No provider-effective prompt captured since session start or reload. Send a prompt, then retry.", "warning", ]); }); });