ubiquitous-invention/packages/ai/src/prompts/code-review.ts

140 lines
4.3 KiB
TypeScript
Raw Normal View History

/**
* 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.`;
}