import { afterEach, expect, test } from "bun:test";
import type { Skill } from "@earendil-works/pi-coding-agent";
import {
composeDeveloperMessages,
getDeveloperMessageContributionRegistry,
registerDeveloperMessageContribution,
renderDeveloperMessages,
} from "../src/developer-messages.ts";
import { buildProviderInstructions } from "../src/provider-instructions.ts";
const context = {
provider: "chosen",
activeTools: ["read"],
sessionId: "session-1",
prompt: "Current request.",
systemPromptOptions: { cwd: "/repo" },
};
afterEach(() => getDeveloperMessageContributionRegistry().clear());
test("keeps SYSTEM.md and append-system text in provider instructions", () => {
expect(
buildProviderInstructions({
cwd: "/repo",
customPrompt: "Owned prompt.",
appendSystemPrompt: "Additional instructions.",
}),
).toBe("Owned prompt.\n\nAdditional instructions.");
});
test("keeps Pi's prompt when SYSTEM.md is absent", () => {
expect(
buildProviderInstructions(
{
cwd: "/repo",
appendSystemPrompt: "Already in the Pi prompt.",
},
"Pi fallback.\n\nAlready in the Pi prompt.",
),
).toBe("Pi fallback.\n\nAlready in the Pi prompt.");
});
test("keeps tool guidance out of separate developer messages", () => {
registerDeveloperMessageContribution({ id: "mode", content: "Developer mode." });
const messages = composeDeveloperMessages(
{
...context,
systemPromptOptions: {
cwd: "/repo",
promptGuidelines: ["Inspect before editing.", "Inspect before editing.", ""],
},
},
"C:\\repo",
{
currentDate: "2026-08-18",
timezone: "America/Los_Angeles",
shell: "zsh",
},
);
expect(messages).toEqual([
{ id: "mode", content: "Developer mode." },
{
id: "environment",
content: [
"",
" C:/repo",
" zsh",
" 2026-08-18",
" America/Los_Angeles",
"",
].join("\n"),
},
]);
});
test("reports exec_command's compatible shell instead of Fish", () => {
const messages = composeDeveloperMessages({ ...context, activeTools: ["exec_command"] }, "/repo", {
currentDate: "2026-08-26",
timezone: "America/Los_Angeles",
shell: "/opt/homebrew/bin/fish",
});
const environment = messages.find((message) => message.id === "environment")?.content;
const expectedShell = process.platform === "darwin" ? "zsh" : process.platform === "win32" ? "bash.exe" : "bash";
expect(environment).toContain(`${expectedShell}`);
expect(environment).not.toContain("fish");
});
test("renders gated extension contributions in stable order", () => {
registerDeveloperMessageContribution({ id: "z-last", priority: 20, content: "LAST" });
registerDeveloperMessageContribution({
id: "a-first",
priority: 10,
content: ({ sessionId }) => `FIRST ${sessionId}`,
});
registerDeveloperMessageContribution({ id: "wrong-provider", content: "WRONG", providers: ["other"] });
registerDeveloperMessageContribution({ id: "missing-tool", content: "MISSING", activeTools: ["write"] });
expect(renderDeveloperMessages(context)).toEqual([
{ id: "a-first", content: "FIRST session-1" },
{ id: "z-last", content: "LAST" },
]);
});
test("passes the current prompt to conditional contributions", () => {
registerDeveloperMessageContribution({
id: "conditional",
content: ({ prompt }) => (prompt === "Annotated request." ? "Annotation guidance." : undefined),
});
expect(renderDeveloperMessages({ ...context, prompt: "Ordinary request." })).toEqual([]);
expect(renderDeveloperMessages({ ...context, prompt: "Annotated request." })).toEqual([
{ id: "conditional", content: "Annotation guidance." },
]);
});
test("does not own the skill catalogue", () => {
const skill = {
name: "review",
description: "Review code.",
filePath: "/skills/review/SKILL.md",
disableModelInvocation: false,
} as Skill;
expect(
renderDeveloperMessages({
...context,
activeTools: ["read", "skill"],
systemPromptOptions: { cwd: "/repo", skills: [skill] },
}),
).toEqual([]);
});