Files
dev/third_party/pi/packages/coding-agent/test/system-prompt.test.ts
alexion 6081e3e325 chore(vendor): import pi 1.3.0
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree.

git-subtree-dir: third_party/pi

git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
2026-10-15 17:58:48 -04:00

239 lines
7.4 KiB
TypeScript

import { describe, expect, test } from "vitest";
import type { Skill } from "../src/core/skills.ts";
import { createSyntheticSourceInfo } from "../src/core/source-info.ts";
import { buildSystemPrompt } from "../src/core/system-prompt.ts";
const testSkill: Skill = {
name: "test-skill",
description: "A test skill.",
filePath: "/skills/test-skill/SKILL.md",
baseDir: "/skills/test-skill",
sourceInfo: createSyntheticSourceInfo("/skills/test-skill/SKILL.md", { source: "test" }),
disableModelInvocation: false,
};
describe("buildSystemPrompt", () => {
describe("empty tools", () => {
test("shows (none) for empty tools list", () => {
const prompt = buildSystemPrompt({
selectedTools: [],
contextFiles: [],
skills: [],
cwd: process.cwd(),
});
expect(prompt).toContain("<tools>\n(none)\n");
});
test("shows file paths guideline even with no tools", () => {
const prompt = buildSystemPrompt({
selectedTools: [],
contextFiles: [],
skills: [],
cwd: process.cwd(),
});
expect(prompt).toContain("Show file paths clearly");
});
});
describe("prompt structure", () => {
test("keeps the default and custom prompt prefixes exact", () => {
const defaultPrompt = buildSystemPrompt({ cwd: "/tmp", selectedTools: [], contextFiles: [], skills: [] });
const customPrompt = buildSystemPrompt({
customPrompt: "You are Exact.",
cwd: "/tmp",
selectedTools: [],
contextFiles: [],
skills: [],
});
expect(defaultPrompt.startsWith("You are an expert coding assistant operating inside pi")).toBe(true);
expect(customPrompt.startsWith("You are Exact.\n\n<cwd>")).toBe(true);
});
test("preserves an exact forced prompt without sections", () => {
expect(buildSystemPrompt({ forceSystemPrompt: "exact", cwd: "/tmp" })).toBe("exact");
});
test("maps appended instructions and project context to stable sections", () => {
const prompt = buildSystemPrompt({
customPrompt: "You are Exact.",
appendSystemPrompt: "Additional instructions.",
contextFiles: [{ path: "/tmp/AGENTS.md", content: "Project instructions." }],
selectedTools: [],
skills: [],
cwd: "/tmp",
});
expect(prompt).toContain("<addendum>\nAdditional instructions.\n</addendum>");
expect(prompt).toContain(
'<project_context>\nProject-specific instructions and guidelines:\n\n<project_instructions path="/tmp/AGENTS.md">',
);
expect(prompt).toContain("<cwd>\n/tmp\n</cwd>");
});
});
describe("default tools", () => {
test("includes all default tools when snippets are provided", () => {
const prompt = buildSystemPrompt({
toolSnippets: {
read: "Read file contents",
bash: "Execute bash commands",
edit: "Make surgical edits",
write: "Create or overwrite files",
},
contextFiles: [],
skills: [],
cwd: process.cwd(),
});
expect(prompt).toContain("- read:");
expect(prompt).toContain("- bash:");
expect(prompt).toContain("- edit:");
expect(prompt).toContain("- write:");
});
test.each([
[["powershell"], "Use PowerShell for file operations"],
[["bash", "powershell"], "Use bash or PowerShell for file operations"],
] as const)("uses shell-specific guidance for %j", (selectedTools, expected) => {
const prompt = buildSystemPrompt({
selectedTools: [...selectedTools],
contextFiles: [],
skills: [],
cwd: process.cwd(),
});
expect(prompt).toContain(expected);
});
test("instructs models to resolve pi docs and examples under absolute base paths", () => {
const prompt = buildSystemPrompt({
contextFiles: [],
skills: [],
cwd: process.cwd(),
});
expect(prompt).toContain(
"- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory",
);
expect(prompt).toContain("environment variables (docs/environment-variables.md), MCP servers (docs/mcp.md)");
});
});
describe("custom tool snippets", () => {
test("includes custom tools in available tools section when promptSnippet is provided", () => {
const prompt = buildSystemPrompt({
selectedTools: ["read", "dynamic_tool"],
toolSnippets: {
dynamic_tool: "Run dynamic test behavior",
},
contextFiles: [],
skills: [],
cwd: process.cwd(),
});
expect(prompt).toContain("- dynamic_tool: Run dynamic test behavior");
});
test("omits custom tools from available tools section when promptSnippet is not provided", () => {
const prompt = buildSystemPrompt({
selectedTools: ["read", "dynamic_tool"],
contextFiles: [],
skills: [],
cwd: process.cwd(),
});
expect(prompt).not.toContain("dynamic_tool");
});
});
describe("prompt guidelines", () => {
test("appends promptGuidelines to default guidelines", () => {
const prompt = buildSystemPrompt({
selectedTools: ["read", "dynamic_tool"],
promptGuidelines: ["Use dynamic_tool for project summaries."],
contextFiles: [],
skills: [],
cwd: process.cwd(),
});
expect(prompt).toContain("- Use dynamic_tool for project summaries.");
});
test("deduplicates and trims promptGuidelines", () => {
const prompt = buildSystemPrompt({
selectedTools: ["read", "dynamic_tool"],
promptGuidelines: ["Use dynamic_tool for summaries.", " Use dynamic_tool for summaries. ", " "],
contextFiles: [],
skills: [],
cwd: process.cwd(),
});
expect(prompt.match(/- Use dynamic_tool for summaries\./g)).toHaveLength(1);
});
});
describe("skills", () => {
test.each([
{ name: "default prompt", customPrompt: undefined },
{ name: "custom prompt", customPrompt: "Custom system prompt" },
])("includes skills with only bash in the $name", ({ customPrompt }) => {
const prompt = buildSystemPrompt({
customPrompt,
selectedTools: ["bash"],
contextFiles: [],
skills: [testSkill],
cwd: process.cwd(),
});
expect(prompt).toContain("<skills>");
expect(prompt).toContain("<available_skills>");
expect(prompt).toContain("<name>test-skill</name>");
expect(prompt).toContain("Use bash to load a skill's file");
});
test("omits skills without read or bash", () => {
const prompt = buildSystemPrompt({
selectedTools: ["write"],
contextFiles: [],
skills: [testSkill],
cwd: process.cwd(),
});
expect(prompt).not.toContain("<available_skills>");
});
});
// #10343
describe("hidden tools", () => {
const build = (hiddenTools: string[]) =>
buildSystemPrompt({
selectedTools: ["read", "bash", "run"],
hiddenTools,
toolSnippets: { read: "Read files", bash: "Run commands", run: "Run a task" },
toolGuidelines: { read: ["Use read for files."], run: ["Prefer run."] },
contextFiles: [],
skills: [testSkill],
cwd: process.cwd(),
});
test("leaves hidden tools out of the tool list and rules", () => {
const prompt = build(["read", "bash"]);
expect(prompt).toContain("<tools>\n- run: Run a task\n");
expect(prompt).not.toContain("- read: ");
expect(prompt).not.toContain("Use read for files.");
expect(prompt).not.toContain("Use bash for file operations");
expect(prompt).toContain("- Prefer run.");
});
test("keeps skills without naming a hidden reader", () => {
expect(build(["read", "bash"])).toContain("\nLoad a skill's file when the task matches its description.");
expect(build(["read"])).toContain("Use bash to load a skill's file");
expect(build([])).toContain("Use the read tool to load a skill's file");
});
});
});