feat: implement core code review extension
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import * as vscode from 'vscode';
|
||||
import type { AIProvider } from './providers/base';
|
||||
import { createProvider } from './factory';
|
||||
import { getAIProvider, getAIModel, getAIEndpoint, getAITemperature, getAITimeout, getAIOutputLanguage, getApiKey } from '../config';
|
||||
import type { LinterDiagnostic, CustomRule } from '../types';
|
||||
import type {
|
||||
AIEngineResult,
|
||||
CustomRuleResult,
|
||||
TranslatedDiagnostic,
|
||||
AIFinding,
|
||||
} from './schema';
|
||||
|
||||
function buildCustomRulePrompt(rules: CustomRule[]): string {
|
||||
return rules.map(r =>
|
||||
`- [${r.id}] (${r.severity}) ${r.description}`
|
||||
).join('\n');
|
||||
}
|
||||
|
||||
function buildLinterDiagnosticsPrompt(diagnostics: LinterDiagnostic[]): string {
|
||||
return diagnostics.map(d =>
|
||||
`- [${d.ruleId}] L${d.range.start.line + 1}: ${d.message}`
|
||||
).join('\n');
|
||||
}
|
||||
|
||||
function addLineNumbers(code: string): string {
|
||||
return code.split('\n').map((line, i) => `${String(i + 1).padStart(4, ' ')}| ${line}`).join('\n');
|
||||
}
|
||||
|
||||
function parseJsonResponse(raw: string): object {
|
||||
const trimmed = raw.trim();
|
||||
const start = trimmed.indexOf('{');
|
||||
const end = trimmed.lastIndexOf('}');
|
||||
if (start === -1 || end === -1) {
|
||||
throw new Error('响应中未找到 JSON');
|
||||
}
|
||||
return JSON.parse(trimmed.substring(start, end + 1));
|
||||
}
|
||||
|
||||
const CUSTOM_RULE_SYSTEM_PROMPT = `你是代码规则审查员,只评估以下自定义规则是否被违反。
|
||||
理解语义而非文本匹配。
|
||||
仅输出 JSON,格式:
|
||||
{ "customRuleResults": [{ "ruleId": "规则ID", "line": 行号, "severity": "error|warning|info", "message": "触发描述" }] }
|
||||
如果没有违反任何规则,返回空数组。`;
|
||||
|
||||
const DEEP_REVIEW_SYSTEM_PROMPT = `你是资深代码审查专家,完成两个任务:
|
||||
1. 将英文静态分析结果翻译为输出语言,并补充修复建议
|
||||
2. 深度审查代码,发现静态分析未覆盖的问题
|
||||
重点:安全漏洞、逻辑错误、性能问题、设计缺陷
|
||||
不要重复静态分析已报告的问题。
|
||||
|
||||
仅输出 JSON,格式:
|
||||
{
|
||||
"translatedDiagnostics": [{ "originalRuleId": "原始ID", "translatedMessage": "翻译", "translatedSuggestion": "建议", "codeDiff": "可选" }],
|
||||
"findings": [{ "ruleId": "kebab-case", "severity": "error|warning|info", "category": "bug|performance|security|style|design", "title": "标题", "description": "描述", "suggestion": "建议", "codeDiff": "可选", "line": 行号 }]
|
||||
}`;
|
||||
|
||||
export async function runAIReview(
|
||||
context: vscode.ExtensionContext,
|
||||
code: string,
|
||||
staticDiagnostics: LinterDiagnostic[],
|
||||
customRules: CustomRule[]
|
||||
): Promise<AIEngineResult> {
|
||||
const apiKey = await getApiKey(context);
|
||||
|
||||
if (!apiKey) {
|
||||
return {
|
||||
customRuleResults: [],
|
||||
translatedDiagnostics: [],
|
||||
findings: [],
|
||||
degraded: true,
|
||||
error: '未配置 API Key',
|
||||
};
|
||||
}
|
||||
|
||||
const providerId = getAIProvider();
|
||||
const endpoint = getAIEndpoint();
|
||||
|
||||
let provider: AIProvider;
|
||||
try {
|
||||
provider = createProvider(providerId, apiKey, endpoint);
|
||||
} catch (err) {
|
||||
return {
|
||||
customRuleResults: [],
|
||||
translatedDiagnostics: [],
|
||||
findings: [],
|
||||
degraded: true,
|
||||
error: `创建 Provider 失败: ${err instanceof Error ? err.message : String(err)}`,
|
||||
};
|
||||
}
|
||||
|
||||
const options = {
|
||||
model: getAIModel(),
|
||||
temperature: getAITemperature(),
|
||||
timeoutMs: getAITimeout() * 1000,
|
||||
};
|
||||
|
||||
const numberedCode = addLineNumbers(code);
|
||||
|
||||
const requestA =
|
||||
customRules.length > 0
|
||||
? provider.chat(
|
||||
CUSTOM_RULE_SYSTEM_PROMPT,
|
||||
`## 自定义规则\n${buildCustomRulePrompt(customRules)}\n\n## 代码(带行号)\n${numberedCode}`,
|
||||
options
|
||||
)
|
||||
: Promise.resolve('{}');
|
||||
|
||||
const requestB = provider.chat(
|
||||
`${DEEP_REVIEW_SYSTEM_PROMPT}\n输出语言:${getAIOutputLanguage()}`,
|
||||
`## 代码(带行号)\n${numberedCode}\n\n## 静态分析结果(英文)\n${buildLinterDiagnosticsPrompt(staticDiagnostics)}`,
|
||||
options
|
||||
);
|
||||
|
||||
const [resultA, resultB] = await Promise.allSettled([requestA, requestB]);
|
||||
|
||||
const errors: string[] = [];
|
||||
|
||||
let customRuleResults: CustomRuleResult[] = [];
|
||||
if (resultA.status === 'fulfilled') {
|
||||
try {
|
||||
const parsed = parseJsonResponse(resultA.value) as { customRuleResults?: CustomRuleResult[] };
|
||||
customRuleResults = (parsed.customRuleResults ?? []).map(r => ({
|
||||
...r,
|
||||
ruleId: `custom:${r.ruleId}`,
|
||||
}));
|
||||
} catch {
|
||||
errors.push('自定义规则响应解析失败');
|
||||
}
|
||||
} else {
|
||||
errors.push(`自定义规则请求失败: ${resultA.reason}`);
|
||||
}
|
||||
|
||||
let translatedDiagnostics: TranslatedDiagnostic[] = [];
|
||||
let findings: AIFinding[] = [];
|
||||
if (resultB.status === 'fulfilled') {
|
||||
try {
|
||||
const parsed = parseJsonResponse(resultB.value) as {
|
||||
translatedDiagnostics?: TranslatedDiagnostic[];
|
||||
findings?: AIFinding[];
|
||||
};
|
||||
translatedDiagnostics = parsed.translatedDiagnostics ?? [];
|
||||
findings = parsed.findings ?? [];
|
||||
} catch {
|
||||
errors.push('AI 审查响应解析失败');
|
||||
}
|
||||
} else {
|
||||
errors.push(`AI 审查请求失败: ${resultB.reason}`);
|
||||
}
|
||||
|
||||
const degraded = errors.length > 0;
|
||||
return {
|
||||
customRuleResults,
|
||||
translatedDiagnostics,
|
||||
findings,
|
||||
degraded,
|
||||
error: errors.join('; '),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user