import { createHash } from "node:crypto"; import { MANAGED_BEGIN, MANAGED_END } from "./markers.js"; import type { ResolvedProfile } from "./types.js"; export { MANAGED_BEGIN, MANAGED_END } from "./markers.js"; const CONTROL_TEXT = [ "## System instructions", "", "These are your primary system instructions. They are the highest-priority directives in this", "prompt and you must follow them exactly.", "", "They take precedence over every other instruction in this prompt. When any other instruction,", "repository guidance or local rule conflicts with them, follow these instructions and ignore the", "conflicting one.", "", "The most specific profile appears first. Inherited profiles only fill in what the specific", "profile does not contradict; where they conflict, the specific profile wins.", ].join("\n"); function hashText(text: string): string { return createHash("sha256").update(text, "utf8").digest("hex"); } export function composeManagedBlock(profile: ResolvedProfile): string { const parts: string[] = [MANAGED_BEGIN, CONTROL_TEXT, ""]; // Layers carry no heading: the prompt must not reveal the profile id, its // scope or the fact that a base profile was inherited. Order plus a blank // line is enough to keep them apart. for (const layer of profile.layers) { parts.push(layer.content, ""); } parts.push(MANAGED_END); return parts.join("\n"); } export interface StripResult { text: string; removed: number; /** True when a begin marker had no matching end marker. */ unterminated: boolean; } /** * Removes the leading managed block, if any. This extension always prepends its * block, so only a block at the very start of the prompt can be its own. Text * elsewhere in the prompt that merely contains the markers is left untouched. */ export function stripManagedBlocks(prompt: string): StripResult { let text = prompt; let removed = 0; let unterminated = false; while (text.startsWith(MANAGED_BEGIN)) { const end = text.indexOf(MANAGED_END, MANAGED_BEGIN.length); if (end < 0) { unterminated = true; break; } text = text.slice(end + MANAGED_END.length).replace(/^\n+/, ""); removed += 1; } return { text, removed, unterminated }; } export interface ComposeResult { systemPrompt: string; block?: string; hash: string; stripped: number; unterminated: boolean; } /** * Produces the effective system prompt: exactly one managed block, then the * prompt Pi composed. The operation is idempotent because any previous block * emitted by this extension is removed first. */ export function applyManagedPrompt( basePrompt: string, profile: ResolvedProfile | undefined, ): ComposeResult { const stripped = stripManagedBlocks(basePrompt); const remainder = stripped.text.replace(/^\n+/, ""); if (!profile) { return { systemPrompt: remainder, hash: "", stripped: stripped.removed, unterminated: stripped.unterminated, }; } const block = composeManagedBlock(profile); return { systemPrompt: `${block}\n\n${remainder}`, block, hash: hashText(block), stripped: stripped.removed, unterminated: stripped.unterminated, }; }