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("\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")).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("\nAdditional instructions.\n"); expect(prompt).toContain( '\nProject-specific instructions and guidelines:\n\n', ); expect(prompt).toContain("\n/tmp\n"); }); }); 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(""); expect(prompt).toContain(""); expect(prompt).toContain("test-skill"); 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(""); }); }); // #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("\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"); }); }); });