Files
oh-my-pi/packages/coding-agent/test/system-prompt-inventory.test.ts
T
can1357 d46bd0139b refactor(coding-agent): fixed system prompt customization path
- Fixed `SYSTEM.md` integration to correctly include custom-rendered sections like rules and skills.
- Consolidated system prompt validation by requiring `<skills>` tag presence instead of specific prose.
- Removed redundant system prompt math-formatting tests and orphaned task batch documentation tests.
2026-06-19 16:16:37 +02:00

126 lines
4.1 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { buildSystemPrompt, type SystemPromptToolMetadata } from "@oh-my-pi/pi-coding-agent/system-prompt";
import { cleanupTempHome } from "./helpers/temp-home-cleanup";
const EMPTY_TREE = {
rootPath: "",
rendered: "",
truncated: false,
totalLines: 0,
agentsMdFiles: [],
};
const TOOLS = new Map<string, SystemPromptToolMetadata>([
[
"read",
{
label: "Read",
description: "Reads files from disk.",
parameters: { type: "object", properties: { path: { type: "string" } } },
},
],
[
"bash",
{
label: "Bash",
description: "Executes a shell command.",
parameters: { type: "object", properties: { command: { type: "string" } } },
},
],
]);
describe("system prompt tool inventory", () => {
let tempDir = "";
let tempHomeDir = "";
let originalHome: string | undefined;
beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-prompt-inv-"));
tempHomeDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-prompt-inv-home-"));
originalHome = process.env.HOME;
process.env.HOME = tempHomeDir;
});
afterEach(cleanupTempHome(() => ({ tempDir, tempHomeDir, originalHome })));
async function render(opts: { nativeTools: boolean; inlineToolDescriptors: boolean }): Promise<string> {
const { systemPrompt } = await buildSystemPrompt({
cwd: tempDir,
contextFiles: [],
skills: [],
rules: [],
toolNames: ["read", "bash"],
tools: TOOLS,
workspaceTree: { ...EMPTY_TREE, rootPath: tempDir },
nativeTools: opts.nativeTools,
inlineToolDescriptors: opts.inlineToolDescriptors,
});
return systemPrompt.join("\n\n");
}
it("renders a compact name list only when native tools are active and descriptors stay in schemas", async () => {
const text = await render({ nativeTools: true, inlineToolDescriptors: false });
expect(text).toContain("- Read: `read`");
expect(text).toContain("- Bash: `bash`");
// No full per-tool sections in list mode.
expect(text).not.toContain("# Tool: read");
expect(text).not.toContain("Reads files from disk.");
});
it("renders `# Tool:` sections (not a name list) when tools are not native", async () => {
const text = await render({ nativeTools: false, inlineToolDescriptors: false });
expect(text).toContain("# Tool: read");
expect(text).toContain("# Tool: bash");
expect(text).toContain("Reads files from disk.");
expect(text).not.toContain("- Read: `read`");
// The legacy `<tool>` wrapper is gone.
expect(text).not.toContain("<tool name=");
});
it("renders `# Tool:` sections when descriptors are inlined even with native tools", async () => {
const text = await render({ nativeTools: true, inlineToolDescriptors: true });
expect(text).toContain("# Tool: read");
expect(text).toContain("Executes a shell command.");
expect(text).not.toContain("- Read: `read`");
});
it("tells the agent to read matching skills before work", async () => {
const { systemPrompt } = await buildSystemPrompt({
cwd: tempDir,
contextFiles: [],
skills: [
{
name: "frontend-design",
description: "Frontend UI workflow",
filePath: path.join(tempDir, "SKILL.md"),
baseDir: tempDir,
source: "test",
},
],
rules: [],
toolNames: ["read"],
tools: TOOLS,
workspaceTree: { ...EMPTY_TREE, rootPath: tempDir },
});
const text = systemPrompt.join("\n\n");
expect(text).toContain("<skills>");
expect(text).toContain("- frontend-design: Frontend UI workflow");
});
it("places the inventory at the bottom of the TOOLS section (after I/O and Exploration)", async () => {
const text = await render({ nativeTools: true, inlineToolDescriptors: false });
const inventoryIdx = text.indexOf("# Inventory");
const ioIdx = text.indexOf("# I/O");
const explorationIdx = text.indexOf("# Exploration");
expect(inventoryIdx).toBeGreaterThan(-1);
expect(ioIdx).toBeGreaterThan(-1);
expect(explorationIdx).toBeGreaterThan(-1);
expect(inventoryIdx).toBeGreaterThan(ioIdx);
expect(inventoryIdx).toBeGreaterThan(explorationIdx);
});
});