// Packet -> prompt rendering, per act. This is the only place where a typed // packet becomes model-facing text; harnesses transport it verbatim. import { type UCP } from '../packet/ucp.js' import { type CodeChunk } from '../core/types.js' const WORK_PREAMBLE = `Generate unified diff patch for given UCP task. Output ONLY: \`\`\`diff --- a/file +++ b/file @@ ... @@ \`\`\` No explanations before or after. If impossible, output ONLY: IMPOSSIBLE: ` // The oracle contract from mother-escalate, verbatim: read-only judgment, // one JSON object out, never edits. const JUDGMENT_PREAMBLE = `You are a read-only judgment oracle. Input: one JSON packet {t,act,q,c,ctx,r}. Read every ctx.f pointer and every spec:/diff: fact in ctx.d from the working tree before answering — the packet carries pointers so you do the legwork. Never edit, write, or commit anything. Reply with exactly one JSON object, no prose outside it: - act ask -> {"a":"...","why":"..."} ; a matches r.out: decision = one line, design = structure sketch, patch = code sketch the caller applies. If tried:/failed: pairs are present, the caller failed twice the same way — diagnose why the attempts failed before proposing a third path; never re-suggest a tried approach. - act review -> judge the diff against the spec on five axes: correctness, spec fit, security, architecture, simplicity. Examine every changed hunk on each axis. {"v":"PASS"} only when no axis fires; otherwise {"v":"ISSUES","i":[["R|Y|G","path#L","one-line finding"]]} — R blocking, Y important, G nice-to-have; dedupe to the most specific finding. - Question hinges on product intent (what SHOULD it do)? -> {"q":"..."} instead of guessing. - Bad pointer, unreadable diff, spec too thin to judge -> {"fail":"reason"} — never fill gaps with assumptions.` export function buildPrompt(ucp: UCP, chunks: CodeChunk[]): string { const preamble = ucp.act === 'work' ? WORK_PREAMBLE : JUDGMENT_PREAMBLE const parts: string[] = [preamble] parts.push(`${JSON.stringify(ucp)}`) if (chunks.length > 0) { parts.push('') for (const c of chunks) { parts.push(`### ${c.file}`) parts.push(`${c.kind} ${c.name} (L${c.startLine}-L${c.endLine}) score:${(c.score ?? 0).toFixed(2)}`) const body = c.source.length > 600 ? c.source.slice(0, 600) + '\n// ...' : c.source parts.push(body) parts.push('') } parts.push('') } return parts.join('\n') }