/** * Prompts used by the code-review graph in `graph/code-review.ts`. * * The graph runs a small ensemble of focused steps; each step gets a tightly * scoped user prompt while sharing one reviewer system prompt, so the model * keeps a consistent voice and rubric across nodes. */ export const CODE_REVIEW_SYSTEM_PROMPT = `You are a senior staff engineer performing a code review. Operating principles: - Be precise. Cite the file and (when possible) the symbol or hunk you are referring to. - Prefer correctness > security > performance > readability > style. - Do not invent code that is not in the diff. If something is unclear, say so explicitly. - When suggesting a change, show the smallest reasonable patch (a few lines) rather than rewriting whole files. - Severity levels: "blocker" (must fix before merge), "major" (should fix), "minor" (nice to fix), "nit" (style/preference). - Never fabricate test results, runtime behavior, or external API contracts.`; export type FileChangeInput = { path: string; status: "added" | "modified" | "deleted" | "renamed"; oldPath?: string; /** Unified diff for this file, or full file contents for added files. */ patch: string; }; function fenceForPath(path: string): string { const ext = path.split(".").pop()?.toLowerCase() ?? ""; switch (ext) { case "ts": case "tsx": return "ts"; case "js": case "jsx": return "js"; case "py": return "python"; case "go": return "go"; case "rs": return "rust"; case "json": return "json"; case "md": return "markdown"; default: return "diff"; } } export function buildSummarizeChangesPrompt(files: FileChangeInput[]): string { const fileList = files .map((f) => `- ${f.status.toUpperCase()} ${f.path}${f.oldPath ? ` (was ${f.oldPath})` : ""}`) .join("\n"); const bodies = files .map( (f) => `### ${f.path}\n\n\`\`\`${fenceForPath(f.path)}\n${f.patch.trim()}\n\`\`\``, ) .join("\n\n"); return `Summarize the intent of this change set in 2-4 sentences. Then list the most affected modules. Files: ${fileList} Diffs: ${bodies} Respond with: 1) "Summary:" — what the change does and why (inferred). 2) "Affected modules:" — bullet list of high-level areas (e.g., "auth/session", "tasks/api").`; } export function buildAnalyzeFilePrompt(file: FileChangeInput, summary: string): string { return `Review the following file change against the change-set summary below. Identify concrete issues only — do not list praise or generic advice. Change-set summary: ${summary} File: ${file.path} (${file.status}${file.oldPath ? `, was ${file.oldPath}` : ""}) \`\`\`${fenceForPath(file.path)} ${file.patch.trim()} \`\`\` For each issue, choose exactly one category: "correctness", "security", "performance", "readability", "style", "test-coverage". For each issue, choose exactly one severity: "blocker", "major", "minor", "nit". If there are no issues, return an empty list.`; } export function buildComposeReviewPrompt(input: { summary: string; affectedModules: string[]; prioritizedIssues: Array<{ file: string; severity: string; category: string; title: string; detail: string; suggestion?: string; }>; }): string { const issueLines = input.prioritizedIssues .map( (i, idx) => `${idx + 1}. [${i.severity}/${i.category}] ${i.file} — ${i.title}\n ${i.detail}${i.suggestion ? `\n Suggested: ${i.suggestion}` : ""}`, ) .join("\n\n"); const modules = input.affectedModules.length ? input.affectedModules.map((m) => `- ${m}`).join("\n") : "- (none identified)"; return `Compose the final review as Markdown. Audience: the PR author. Required structure: # Code Review ## Summary ${input.summary} ## Affected modules ${modules} ## Findings (For each finding below, render as a Markdown subsection with severity/category badges. Group findings by severity in this order: blocker, major, minor, nit. Skip groups that are empty.) Findings to render: ${issueLines || "(none)"} ## Verdict End with one of: "Approve", "Approve with comments", "Request changes", or "Block". Pick the strictest verdict justified by the findings (any blocker => "Block"; any major => "Request changes"). Do not add any sections beyond those listed.`; }