From dca148a125abd4768a3f24847b0d659c75d7c1de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Wed, 22 Apr 2026 18:39:56 -0400 Subject: [PATCH] fix(prompt): render skills without pi shape casts --- src/main/prompt-builder/system-prompt.ts | 49 ++++++++++++------- .../prompt-manager-classification.test.ts | 16 ++++++ 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/main/prompt-builder/system-prompt.ts b/src/main/prompt-builder/system-prompt.ts index 89ac596d..8e81b2dc 100644 --- a/src/main/prompt-builder/system-prompt.ts +++ b/src/main/prompt-builder/system-prompt.ts @@ -1,6 +1,5 @@ import { DEPARTMENT_LABELS } from "@shared/constants"; import type { DepartmentId } from "@shared/types"; -import { formatSkillsForPrompt } from "@mariozechner/pi-coding-agent"; import { buildTaskSection, buildReviewSection, @@ -30,27 +29,39 @@ function isLeadershipRole(ctx: PromptContext): boolean { } /** - * Render the skill manifest block using pi-coding-agent's XML format. - * - * The full SKILL.md content is NOT injected — only name/description/filePath. - * The agent loads SKILL.md on demand via the `read` tool (progressive - * disclosure). Pi's `formatSkillsForPrompt` expects its full `Skill` shape, so - * we supply synthetic values for the two fields we don't track (`sourceInfo`, - * `disableModelInvocation`). Cast is safe because pi's XML renderer only reads - * `name`, `description`, `filePath`, and `disableModelInvocation`. + * Render the Agent Skills manifest from the runtime projection. The prompt + * carries only name, description, and SKILL.md location; the agent reads the + * full file on demand through the filesystem tool. */ function buildSkillManifest(skills: ProjectedSkill[] | undefined): string { if (!skills || skills.length === 0) return ""; - const piShapedSkills = skills.map((s) => ({ - name: s.name, - description: s.description, - filePath: s.filePath, - baseDir: s.baseDir, - disableModelInvocation: false, - // sourceInfo is required by pi's Skill interface but never read by - // formatSkillsForPrompt. Leaving it undefined and asserting the shape. - })); - return formatSkillsForPrompt(piShapedSkills as never); + const lines = [ + "\n\nThe following skills provide specialized instructions for specific tasks.", + "Use the read tool to load a skill's file when the task matches its description.", + "When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.", + "", + "", + ]; + + for (const skill of skills) { + lines.push(" "); + lines.push(` ${escapeSkillXml(skill.name)}`); + lines.push(` ${escapeSkillXml(skill.description)}`); + lines.push(` ${escapeSkillXml(skill.filePath)}`); + lines.push(" "); + } + + lines.push(""); + return lines.join("\n"); +} + +function escapeSkillXml(value: string): string { + return value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); } export function buildSystemPrompt(ctx: PromptContext): string { diff --git a/tests/unit/prompt-manager-classification.test.ts b/tests/unit/prompt-manager-classification.test.ts index ff8affee..322679a6 100644 --- a/tests/unit/prompt-manager-classification.test.ts +++ b/tests/unit/prompt-manager-classification.test.ts @@ -79,4 +79,20 @@ describe("buildSystemPrompt leadership classification", () => { expect(prompt).toContain("New hires for your team should report to you by default"); expect(prompt).not.toContain("Operating Instructions (Individual Contributor)"); }); + + it("renders projected skills without pi skill shape casts", () => { + const prompt = buildSystemPrompt(buildCtx({ + skills: [{ + name: "ui&qa", + description: "Use for A&B \"checks\" and 'snapshots'.", + filePath: "/tmp/skills/ui&qa/SKILL.md", + baseDir: "/tmp/skills/ui&qa", + }], + })); + + expect(prompt).toContain(""); + expect(prompt).toContain("ui<review>&qa"); + expect(prompt).toContain("Use for A&B "checks" and 'snapshots'."); + expect(prompt).toContain("/tmp/skills/ui<review>&qa/SKILL.md"); + }); });