perf-lens
Version:
AI-powered frontend performance optimizer
64 lines (63 loc) • 2.69 kB
JavaScript
import { z } from 'zod';
/**
* A single performance finding. Shared by the batch scan and the agent mode —
* both produce findings in this exact shape via structured outputs.
*/
export const FindingSchema = z.object({
file: z
.string()
.describe('Path to the file, relative to the project root, exactly as listed in the prompt'),
startLine: z.number().int().describe('First line of the issue (1-based, must exist in the file)'),
endLine: z.number().int().describe('Last line of the issue (1-based, must exist in the file)'),
severity: z
.enum(['critical', 'warning', 'suggestion'])
.describe('critical = memory leaks, blocking operations, major bottlenecks; warning = measurable suboptimal patterns; suggestion = worthwhile improvements'),
title: z.string().describe('One-line summary of the issue'),
description: z.string().describe('Clear description of the issue'),
impact: z.string().describe('Specific performance impact on the user'),
solution: z.string().describe('Concrete, actionable fix'),
codeExample: z.string().optional().describe('Corrected code snippet, when helpful'),
});
export const FindingsSchema = z.object({
findings: z.array(FindingSchema),
});
const SEVERITY_EMOJI = {
critical: '🚨',
warning: '⚠️',
suggestion: '💡',
};
/**
* Drops findings that reference files outside the analyzed set or line ranges
* that don't exist. The schema guarantees shape; this guards against hallucinated
* locations.
* @param findings - Findings returned by the model
* @param fileLineCounts - Map of relative file path -> number of lines
*/
export function validateFindings(findings, fileLineCounts) {
const valid = [];
const dropped = [];
for (const finding of findings) {
const lineCount = fileLineCounts[finding.file];
const inRange = lineCount !== undefined &&
finding.startLine >= 1 &&
finding.endLine >= finding.startLine &&
finding.endLine <= lineCount;
(inRange ? valid : dropped).push(finding);
}
return { valid, dropped };
}
/**
* Renders a finding as the markdown block shape the report layer expects.
*/
export function findingToMarkdown(finding) {
const parts = [
`${SEVERITY_EMOJI[finding.severity]} ${finding.file}:${finding.startLine}-${finding.endLine} — ${finding.title}`,
`**Description:** ${finding.description}`,
`**Impact:** ${finding.impact}`,
`**Solution:** ${finding.solution}`,
];
if (finding.codeExample) {
parts.push(`**Code Example:**\n\`\`\`\n${finding.codeExample}\n\`\`\``);
}
return parts.join('\n');
}