# 方法级代码审查功能实施计划书 > 面向 AI 编码 agent 的技术实施文档。本文档包含完整的架构设计、文件清单、代码模板和验收标准,可直接据此编码。 ## 项目上下文 - 仓库:`sdjndaq/2026-ai-b3`(Gitee 私有仓库) - 分支:`vscode-code-reviewer` - 项目名:**代码审查官 · Code Purifier**(`vscode-code-reviewer`) - 版本:1.2.0 - 类型:VS Code 扩展插件(TypeScript) - 技术栈:TypeScript + VS Code Extension API + DeepSeek AI ## 功能目标 为现有代码审查插件新增**方法级触发**能力。用户在每个函数声明行上方看到 CodeLens 按钮,点击后对该方法执行**自定义规则审查 + AI 深度审查**。方法级审查不执行静态分析(IDE 已实时提供 linter 诊断),但加载自定义规则与 AI 协同工作:AI 先按自定义规则做匹配,再以 6 维度增强策略做深度审查,单次调用同时产出两类结果。 ### 核心设计决策 | 决策 | 选择 | 理由 | |------|------|------| | 审查流程 | 自定义规则 + AI(不走静态分析) | IDE 已实时提供 linter 诊断,静态分析冗余;自定义规则保留是因为团队规则仍需在方法级生效 | | AI 调用模式 | 单次调用合并规则匹配 + 深度审查 | 方法代码短,token 预算充足,合并为单次调用减少延迟 | | 触发方式 | CodeLens 行内按钮 | 最直观的交互,用户无需记忆命令或快捷键 | | 方法提取 | VS Code Symbol API | 原生支持,覆盖 TS/JS/Java/Python 等主流语言 | | 调用链 | 同文件粗匹配 | 首版限制在同文件内,控制实现复杂度 | | AI 审查范围 | 6 维度增强 | AI 同时承担规则匹配和深度审查,维度覆盖比全文件更广 | ## 现有架构摘要 编码前必须理解以下现有代码结构,所有新代码需与这些模式保持一致。 ### 入口与注册 `src/extension.ts` 的 `activate(context)` 函数完成三件事: 1. 创建 `Orchestrator` 实例 2. 注册 `SetupViewProvider`(Webview View) 3. 调用 `registerCommands(context, orchestrator)` 注册所有命令 4. 监听 `onDidSaveTextDocument` 执行防抖静态分析 5. 监听 `onDidChangeConfiguration` 切换 i18n 语言 ### 命令注册 `src/activation/commands.ts` 的 `registerCommands(context, orchestrator)` 注册命令。现有命令通过 `vscode.window.withProgress` 包裹执行,使用 `t()` 函数做 i18n。核心命令 `codeReviewer.review` 的执行流程: ``` 获取 document + workingDir → withProgress: → orchestrator.runStaticAnalysis(document, workingDir) → loadActiveRules(workspaceRoot) + filterAndSummarize → runAIReview(context, code, staticDiagnostics, relevantRules) → mergeResults(...) → ReviewPanel.createOrShow + panel.update(currentReport) ``` ### AI 引擎 `src/ai/engine.ts` 导出 `runAIReview(context, code, staticDiagnostics, customRules)` 函数。内部流程: 1. `getApiKey(context)` 获取 API Key 2. `createProvider(providerId, apiKey, baseUrl, extensionUri)` 创建 AI Provider 3. 并行发起两个请求: - `requestA`:自定义规则匹配(`buildCustomRuleSystemPrompt` + `buildUserPromptCustomRules`) - `requestB`:深度审查(`buildDeepReviewSystemPrompt` + `buildUserPromptDeepReview`) 4. `parseJsonResponse` 解析 JSON 5. 返回 `AIEngineResult` 现有 `buildDeepReviewSystemPrompt()` 的定位是"补充静态分析的盲区",任务包含翻译静态诊断和发现额外问题。方法级审查不复用这个 prompt。 ### 类型定义 `src/types.ts` 定义核心类型: ```typescript interface CustomRule { id: string; severity: Severity; description: string; message: string; languages?: string[]; excludeLanguages?: string[]; } interface LinterDiagnostic { severity: Severity; ruleId: string; message: string; range: vscode.Range; suggestion?: string; } interface LinterAdapter { id: string; supportedLanguages: string[]; check(document: vscode.TextDocument, workingDir: string): Promise; isAvailable(): boolean; } ``` `src/ai/schema.ts` 定义 AI 输出类型: ```typescript interface AIFinding { ruleId: string; severity: 'error' | 'warning' | 'info'; category: 'bug' | 'performance' | 'security' | 'style' | 'design'; title: string; description: string; suggestion: string; codeDiff?: string; line: number; } interface AIEngineResult { customRuleResults: CustomRuleResult[]; translatedDiagnostics: TranslatedDiagnostic[]; findings: AIFinding[]; degraded: boolean; error?: string; } ``` ### 结果合并与展示 `src/merger/merger.ts` 的 `mergeResults(...)` 将静态诊断、AI 发现等聚合成 `MergedReport`。`src/panel/webview.ts` 的 `ReviewPanel` 类负责 Webview 面板的创建和更新。 ### i18n `src/i18n/messages.ts` 导出 `t(key, params?)` 和 `setLanguage(lang)` 函数。所有用户可见文案必须通过 `t()` 获取。 ## 改动总览 ### 新增文件(3 个) | 文件路径 | 职责 | |----------|------| | `src/scope/method-extractor.ts` | 从文档中提取方法符号、代码、签名、调用链 | | `src/views/codeLensProvider.ts` | CodeLens Provider,在函数声明上方渲染审查按钮 | | `src/scope/status-cache.ts` | 方法审查状态缓存,驱动 CodeLens 按钮文案刷新 | ### 修改文件(4 个 + package.json) | 文件路径 | 改动内容 | |----------|----------| | `src/activation/commands.ts` | 新增 `codeReviewer.reviewMethod` 命令 | | `src/ai/engine.ts` | 新增 `runMethodReview` 函数 + 方法级 prompt builder | | `src/ai/schema.ts` | 新增 `MethodFinding` 类型 + 扩展 category | | `src/extension.ts` | 注册 CodeLensProvider | | `package.json` | 新增 command、codelens 配置项 | ### 不修改的文件 `src/orchestrator/orchestrator.ts`、`src/merger/merger.ts`、`src/panel/webview.ts`、`src/ai/providers/` 目录、`src/i18n/` 目录均不需要修改。方法级审查的执行路径不经过 Orchestrator,直接从命令层调用 engine.ts。自定义规则的加载复用现有 `src/rules/rule-loader.ts` 的 `loadActiveRules` 函数,过滤复用 `src/rules/rule-filter.ts` 的 `filterForDocument` 函数,均无需修改。 --- ## 新增文件详细设计 ### 1. `src/scope/method-extractor.ts` #### 职责 从 VS Code 文档中提取方法级别的信息,包括方法代码、签名和同文件内的调用链。 #### 导出类型 ```typescript import * as vscode from 'vscode'; export interface MethodScope { /** 方法名称 */ name: string; /** 方法在文档中的完整范围 */ range: vscode.Range; /** 方法代码文本(带行号) */ code: string; /** 方法签名(一行摘要,如 "processOrder(order: Order): Promise") */ signature: string; /** 调用者方法名列表(同文件内引用了本方法的其他方法) */ callers: string[]; /** 被调用方法名列表(本方法体内调用的其他方法) */ callees: string[]; /** 方法在业务流中的角色描述(由符号层级推断) */ role: string; } export interface MethodSymbol { /** 方法名称 */ name: string; /** 方法范围 */ range: vscode.Range; /** 方法所在类/模块名(如果有) */ containerName?: string; } ``` #### 导出函数 ```typescript /** * 获取文档中所有方法/函数符号 * 使用 VS Code Symbol API,无 Provider 时回退到正则匹配 */ export async function getMethodSymbols( document: vscode.TextDocument ): Promise; /** * 提取指定位置所在方法的完整 MethodScope * @param document 文档 * @param position 光标位置或 Symbol 范围 */ export async function extractMethodScope( document: vscode.TextDocument, range: vscode.Range ): Promise; ``` #### 实现要点 **Symbol API 获取符号树:** ```typescript const symbols = await vscode.commands.executeCommand( 'vscode.executeDocumentSymbolProvider', document.uri ); ``` 过滤 `SymbolKind.Function`、`SymbolKind.Method`、`SymbolKind.Constructor` 类型的节点。递归遍历子符号(类内的方法)。 **正则回退(无 Symbol Provider 时):** 针对 JavaScript/TypeScript 使用正则 `/(?:async\s+)?function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\(/g` 匹配函数声明。针对 Java 使用 `/((?:public|private|protected|static)\s+)*\w+(?:<[^>]+>)?\s+(\w+)\s*\(/g`。 **调用链粗匹配:** 获取同文件内所有方法符号后,对每个方法: - **被调用者**:在本方法代码文本中搜索其他方法名是否出现(正则 `\b方法名\s*\(`) - **调用者**:遍历其他所有方法的代码,检查本方法名是否出现 **签名提取:** 取方法代码的第一行(到 `{` 或 `)` 结束),去除注释和多余空白。 **role 字段推断:** 根据 `containerName` 和方法名粗略推断,例如: - `containerName` 包含 "Controller" → "HTTP 请求处理入口" - `containerName` 包含 "Service" → "业务逻辑处理" - 方法名以 "get/set/is" 开头 → "属性访问器" - 默认 → "通用方法" ### 2. `src/views/codeLensProvider.ts` #### 职责 实现 `vscode.CodeLensProvider`,在每个函数声明行上方渲染审查按钮。 #### 类定义 ```typescript import * as vscode from 'vscode'; import { getMethodSymbols } from '../scope/method-extractor'; import { ReviewStatusCache } from '../scope/status-cache'; import { t } from '../i18n/messages'; export class MethodCodeLensProvider implements vscode.CodeLensProvider { private _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter(); readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event; constructor(private statusCache: ReviewStatusCache) {} /** 触发 CodeLens 刷新 */ refresh(): void { this._onDidChangeCodeLenses.fire(); } async provideCodeLenses( document: vscode.TextDocument, token: vscode.CancellationToken ): Promise { // 1. 检查配置是否启用 const config = vscode.workspace.getConfiguration('vscode-code-reviewer'); const enabled = config.get('codelens.enabled', true); if (!enabled) return []; // 2. 检查语言是否在允许列表内 const languages = config.get('codelens.languages', [ 'typescript', 'javascript', 'java', 'python' ]); if (!languages.includes(document.languageId)) return []; // 3. 获取方法符号 const symbols = await getMethodSymbols(document); if (symbols.length === 0) return []; // 4. 方法数量上限保护 if (symbols.length > 50) return []; // 5. 为每个方法生成 CodeLens const lenses: vscode.CodeLens[] = []; for (const symbol of symbols) { const status = this.statusCache.get(document.uri, symbol.name); const title = this.buildLensTitle(status); lenses.push(new vscode.CodeLens(symbol.range.start, { command: 'codeReviewer.reviewMethod', title: title, arguments: [symbol.range], })); } return lenses; } private buildLensTitle(status: ReviewStatus | null): string { if (!status) { return t('codelens.reviewMethod'); } if (status.issueCount === 0) { return t('codelens.reviewedClean'); } return t('codelens.reviewedWithIssues', { 0: String(status.issueCount) }); } } ``` #### i18n key 约定 需要在 `src/i18n/messages.ts` 中新增以下 key(中英文): | key | 中文 | English | |-----|------|---------| | `codelens.reviewMethod` | `🔍 Code Purifier: 审查此方法` | `🔍 Code Purifier: Review This Method` | | `codelens.reviewedClean` | `✓ Code Purifier: 已审查(无问题)` | `✓ Code Purifier: Reviewed (No Issues)` | | `codelens.reviewedWithIssues` | `✓ Code Purifier: 已审查({0} 个问题)` | `✓ Code Purifier: Reviewed ({0} Issues)` | | `methodReview.running` | `Code Purifier 正在审查方法:{0}` | `Code Purifier: Reviewing method: {0}` | | `methodReview.noMethod` | `当前位置未检测到方法` | `No method detected at current position` | | `methodReview.complete` | `方法审查完成,发现 {0} 个问题` | `Method review complete, {0} issues found` | ### 3. `src/scope/status-cache.ts` #### 职责 内存缓存,记录每个方法最近一次的审查结果,驱动 CodeLens 按钮文案刷新。 #### 类定义 ```typescript import * as vscode from 'vscode'; export interface ReviewStatus { /** 发现的问题数量 */ issueCount: number; /** 审查时间戳 */ timestamp: number; } export class ReviewStatusCache { /** key 格式:documentUri.toString() + '::' + methodName */ private cache = new Map(); get(uri: vscode.Uri, methodName: string): ReviewStatus | null { const key = this.buildKey(uri, methodName); return this.cache.get(key) ?? null; } set(uri: vscode.Uri, methodName: string, issueCount: number): void { const key = this.buildKey(uri, methodName); this.cache.set(key, { issueCount, timestamp: Date.now(), }); } /** 清除指定文档的所有缓存(文档关闭时调用) */ clearDocument(uri: vscode.Uri): void { const prefix = uri.toString() + '::'; for (const key of this.cache.keys()) { if (key.startsWith(prefix)) { this.cache.delete(key); } } } private buildKey(uri: vscode.Uri, methodName: string): string { return uri.toString() + '::' + methodName; } } ``` --- ## 修改文件详细设计 ### 4. `src/ai/schema.ts` 在现有类型基础上新增方法级审查的类型定义,不修改现有类型。 #### 新增内容 ```typescript /** 方法级审查的 category 类型(6 维度) */ export type MethodFindingCategory = | 'correctness' // 正确性:分支覆盖、边界条件、异常路径 | 'security' // 安全性:输入校验、注入风险、权限检查 | 'design' // 设计:职责单一、参数合理性、调用链适配 | 'convention' // 规范:命名、复杂度、魔法数字、注释 | 'performance' // 性能:时间/空间复杂度、资源泄漏 | 'testability'; // 可测试性:副作用隔离、依赖可 Mock 性 /** 方法级审查的 finding,扩展 AIFinding */ export interface MethodFinding { ruleId: string; severity: 'error' | 'warning' | 'info'; category: MethodFindingCategory; title: string; description: string; suggestion: string; codeDiff?: string; line: number; /** 触发路径描述,如 "if(order == null) → NPE on .getId()" */ path?: string; } /** 方法级审查的 AI 返回结构 */ export interface MethodReviewResult { /** 自定义规则匹配结果(AI 按规则做语义判断) */ customRuleResults: CustomRuleResult[]; /** AI 深度审查发现 */ findings: MethodFinding[]; degraded: boolean; error?: string; } ``` ### 5. `src/ai/engine.ts` 新增方法级审查的入口函数和专用 prompt builder。不修改现有 `runAIReview` 及其相关函数。 #### 新增函数 ```typescript /** * 方法级 AI 审查 * 不依赖静态诊断,但加载自定义规则与 AI 协同工作 * 单次 AI 调用同时完成规则匹配和 6 维度深度审查 */ export async function runMethodReview( context: vscode.ExtensionContext, scope: MethodScope, customRules: CustomRule[], ): Promise; ``` #### 内部实现流程 ```typescript export async function runMethodReview( context: vscode.ExtensionContext, scope: MethodScope, customRules: CustomRule[], ): Promise { // 1. 获取 API Key const apiKey = await getApiKey(context); if (!apiKey) { return { customRuleResults: [], findings: [], degraded: true, error: t('adapter.noApiKey'), }; } // 2. 创建 Provider(复用现有工厂) const providerId = getAIProvider(); const baseUrl = getAIBaseUrl(); let provider: AIProvider; try { provider = createProvider(providerId, apiKey, baseUrl, context.extensionUri); } catch (err) { return { customRuleResults: [], findings: [], degraded: true, error: t('adapter.createProviderFail', { 0: err instanceof Error ? err.message : String(err) }), }; } // 3. 构建请求 const options = { model: getAIModel(), temperature: getAITemperature(), maxTokens: getAIMaxTokens(), timeoutMs: getAITimeout() * 1000, }; const numberedCode = addLineNumbers(scope.code); // 4. 单次 AI 调用(规则匹配 + 深度审查合并为一次) // 有规则时 prompt 包含规则匹配任务,无规则时只做深度审查 const hasRules = customRules.length > 0; const result = await provider.chat( buildMethodReviewSystemPrompt(hasRules), buildMethodUserPrompt(scope, numberedCode, customRules), options, ); // 5. 解析结果 const errors: string[] = []; let customRuleResults: CustomRuleResult[] = []; let findings: MethodFinding[] = []; if (result.status === 'fulfilled') { try { const parsed = parseJsonResponse(result.value) as { customRuleResults?: CustomRuleResult[]; findings?: MethodFinding[]; }; customRuleResults = (parsed.customRuleResults ?? []).map(r => ({ ...r, ruleId: r.ruleId.startsWith('custom:') ? r.ruleId : `custom:${r.ruleId}`, })); findings = (parsed.findings ?? []).map(f => ({ ...f, ruleId: f.ruleId.startsWith('method:') ? f.ruleId : `method:${f.ruleId}`, })); } catch (e) { errors.push(t('adapter.aiReviewParseFail', { 0: e instanceof Error ? e.message : String(e) })); } } else { errors.push(t('adapter.aiReviewRequestFail', { 0: result.reason })); } return { customRuleResults, findings, degraded: errors.length > 0, error: errors.join('; ') || undefined, }; } ``` #### 新增 prompt builder ```typescript function buildMethodReviewSystemPrompt(hasRules: boolean): string { const lang = getLanguage(); if (lang === 'en') { const ruleSection = hasRules ? `## Task 1: Custom Rule Matching Evaluate whether the method violates any of the provided custom rules. Understand semantics, not text matching. Report violations in "customRuleResults".\n\n` : ''; const ruleOutput = hasRules ? ` "customRuleResults": [ { "ruleId": "original rule id", "line": line_number, "severity": "error|warning|info", "message": "violation description" } ],\n` : ''; return `You are a senior code review expert reviewing a single method. There is no static analysis before you — you handle rule matching AND deep review. ${ruleSection}## Review Strategy: Path Enumeration - Walk through every if/else/switch branch, note coverage and gaps - Enumerate boundary values for every parameter (null, empty collection, extreme values, wrong types) - Check every throw/catch path for proper fallback strategy - Trace the method's role in its call chain ## Required Dimensions (do not skip any) A. Correctness: branch coverage, boundary conditions, exception path completeness B. Security: input validation, injection risk, permission check, sensitive data leakage C. Design: single responsibility, parameter design, return value contract, call chain adaptation D. Convention: naming, cyclomatic complexity, magic numbers, missing comments E. Performance: time/space complexity, resource leaks, unnecessary computation F. Testability: side effect isolation, dependency mockability, deterministic output ## Call Chain Analysis - Check whether callers' arguments match this method's expectations - Check whether this method's return value is correctly handled by callers - Check whether exceptions are caught or declared by callers Output JSON only. Double quotes in strings must be escaped with \\". Format: { ${ruleOutput} "findings": [ { "ruleId": "method-boundary-null", "severity": "error|warning|info", "category": "correctness|security|design|convention|performance|testability", "title": "issue title", "description": "detailed description", "suggestion": "fix suggestion", "codeDiff": "optional fix diff", "line": line_number, "path": "trigger path description, e.g. if(order==null) -> NPE on .getId()" } ] } If no issues found, return empty arrays. Output language: en`; } // 中文 prompt(默认) const ruleSection = hasRules ? `## 任务一:自定义规则匹配 评估方法是否违反了提供的自定义规则。 理解语义,而非文本匹配。 在 "customRuleResults" 中报告违规。\n\n` : ''; const ruleOutput = hasRules ? ` "customRuleResults": [ { "ruleId": "原始规则 ID", "line": 行号, "severity": "error|warning|info", "message": "违规描述" } ],\n` : ''; return `你是资深代码审查专家,正在审查单个方法。 没有静态分析的前置过滤——你同时负责规则匹配和深度审查。 ${ruleSection}## 审查策略:逐路径枚举 - 遍历每个 if/else/switch 分支,标注覆盖与遗漏 - 枚举每个入参的边界值(null、空集合、极值、错误类型) - 检查每个 throw/catch 路径的降级策略 - 追踪方法在调用链中的角色 ## 必须覆盖的维度(不可跳过) A. 正确性:分支覆盖、边界条件、异常路径完整性 B. 安全性:输入校验、注入风险、权限检查、敏感信息泄露 C. 设计:职责单一性、参数设计合理性、返回值契约、调用链适配 D. 规范:命名、圈复杂度、魔法数字、注释缺失 E. 性能:时间/空间复杂度、资源泄漏、不必要的计算 F. 可测试性:副作用隔离、依赖可 Mock 性、确定性输出 ## 调用链分析 - 检查调用者传入的参数是否符合本方法预期 - 检查本方法的返回值是否被调用者正确处理 - 检查异常是否被调用者捕获或声明 输出 JSON,字符串中的双引号必须用 \\" 转义。 格式: { ${ruleOutput} "findings": [ { "ruleId": "method-boundary-null", "severity": "error|warning|info", "category": "correctness|security|design|convention|performance|testability", "title": "问题标题", "description": "详细描述", "suggestion": "修复建议", "codeDiff": "可选的修复 diff", "line": 行号, "path": "触发路径描述,如 if(order==null) -> NPE on .getId()" } ] } 如果未发现问题,返回空数组。 输出语言:zh-CN`; } function buildMethodUserPrompt( scope: MethodScope, numberedCode: string, customRules: CustomRule[], ): string { const lang = getLanguage(); const codeLabel = lang === 'en' ? 'Method Code (with line numbers)' : '方法代码(带行号)'; const sigLabel = lang === 'en' ? 'Method Signature' : '方法签名'; const chainLabel = lang === 'en' ? 'Call Chain Context' : '调用链上下文'; const roleLabel = lang === 'en' ? 'Role in Business Flow' : '业务流中的角色'; const callersLabel = lang === 'en' ? 'Callers' : '调用者'; const calleesLabel = lang === 'en' ? 'Callees' : '被调用者'; const noneLabel = lang === 'en' ? '(none)' : '(无)'; // 自定义规则块:有规则时注入,无规则时省略 let ruleBlock = ''; if (customRules.length > 0) { const ruleLabel = lang === 'en' ? 'Custom Rules to Match' : '需匹配的自定义规则'; const ruleLines = customRules .map((r, i) => `${i + 1}. [${r.id}] (${r.severity}) ${r.description}\n ${r.message}`) .join('\n'); ruleBlock = `\n## ${ruleLabel}\n${ruleLines}\n`; } return `## ${sigLabel} ${scope.signature} ## ${codeLabel} ${numberedCode} ${ruleBlock} ## ${chainLabel} ${roleLabel}: ${scope.role} ${callersLabel}: ${scope.callers.length > 0 ? scope.callers.join(', ') : noneLabel} ${calleesLabel}: ${scope.callees.length > 0 ? scope.callees.join(', ') : noneLabel}`; } ``` #### 与现有 `runAIReview` 的关键差异 | 对比项 | 现有 `runAIReview` | 新增 `runMethodReview` | |--------|--------------------|-----------------------| | 输入参数 | `code, staticDiagnostics, customRules` | `scope: MethodScope, customRules` | | AI 调用次数 | 2 次(requestA + requestB 并行) | 1 次(规则匹配 + 深度审查合并) | | prompt 定位 | 补充静态分析盲区 | 唯一审查者,全覆盖 | | 输出结构 | `customRuleResults + translatedDiagnostics + findings` | `customRuleResults + findings` | | category | 5 类 | 6 类(新增 correctness、testability) | | path 字段 | 无 | 有(触发路径描述) | | 规则匹配方式 | 独立 prompt + 独立请求 | 与深度审查合并为单次 prompt | ### 6. `src/activation/commands.ts` 新增 `codeReviewer.reviewMethod` 命令。不修改现有命令。 #### 新增命令注册 在 `registerCommands` 函数内追加: ```typescript context.subscriptions.push( vscode.commands.registerCommand( 'codeReviewer.reviewMethod', async (symbolRange: vscode.Range) => { const editor = vscode.window.activeTextEditor; if (!editor) return; const document = editor.document; const workspaceRoot = vscode.workspace.getWorkspaceFolder(document.uri)?.uri.fsPath; // 1. 提取方法 const scope = await extractMethodScope(document, symbolRange); if (!scope) { vscode.window.showWarningMessage(t('methodReview.noMethod')); return; } // 2. 加载并过滤自定义规则(复用现有 rule-filter) let customRules: CustomRule[] = []; if (workspaceRoot) { const allRules = await loadActiveRules(workspaceRoot); customRules = filterForDocument(allRules, document); } // 3. 执行 AI 审查(传入自定义规则) await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, title: t('methodReview.running', { 0: scope.name }), cancellable: false, }, async () => { const result = await runMethodReview(context, scope, customRules); // 4. 构造 MergedReport(复用现有 merger) // customRuleResults 和 findings 均来自 AI 单次调用 const totalIssues = result.customRuleResults.length + result.findings.length; currentReport = mergeResults({ staticDiagnostics: [], customRuleResults: result.customRuleResults, translatedDiagnostics: [], aiFindings: result.findings, // MethodFinding[] 兼容 AIFinding[] errors: result.error ? [result.error] : [], degraded: result.degraded, startTime: Date.now(), filePath: document.uri.fsPath, language: document.languageId, adapterIds: [], customRuleFilterInfo: undefined, }); // 5. 更新状态缓存并刷新 CodeLens statusCache.set(document.uri, scope.name, totalIssues); codeLensProvider.refresh(); // 6. 面板展示 const panel = ReviewPanel.createOrShow(context.extensionUri); panel.update(currentReport); // 7. 提示 vscode.window.showInformationMessage( t('methodReview.complete', { 0: String(totalIssues) }) ); } ); } ) ); ``` #### 函数签名变更 `registerCommands` 需要新增两个参数: ```typescript export function registerCommands( context: vscode.ExtensionContext, orchestrator: Orchestrator, codeLensProvider: MethodCodeLensProvider, // 新增 statusCache: ReviewStatusCache, // 新增 ): void { ``` #### 新增 import `commands.ts` 顶部需追加以下 import(现有 import 不变): ```typescript import { loadActiveRules } from '../rules/rule-loader'; import { filterForDocument } from '../rules/rule-filter'; import { CustomRule } from '../types'; ``` ### 7. `src/extension.ts` 注册 CodeLensProvider 和 StatusCache。 #### 修改内容 在 `activate` 函数中,`registerCommands` 调用之前新增: ```typescript export function activate(context: vscode.ExtensionContext) { const lang = getAIOutputLanguage() as Language; setLanguage(lang); console.log(t('extension.activated')); orchestrator = new Orchestrator(); const setupProvider = new SetupViewProvider(context); context.subscriptions.push( vscode.window.registerWebviewViewProvider('codeReviewer.setupView', setupProvider) ); // ===== 新增:方法级审查基础设施 ===== const statusCache = new ReviewStatusCache(); const codeLensProvider = new MethodCodeLensProvider(statusCache); context.subscriptions.push( vscode.languages.registerCodeLensProvider( { scheme: 'file' }, codeLensProvider ) ); // 文档关闭时清理状态缓存 context.subscriptions.push( vscode.workspace.onDidCloseTextDocument((document) => { statusCache.clearDocument(document.uri); }) ); // ===== 新增结束 ===== registerCommands(context, orchestrator, codeLensProvider, statusCache); // ... 保留现有的 onDidSaveTextDocument 和 onDidChangeConfiguration 逻辑不变 ... } ``` ### 8. `package.json` #### 新增 command 在 `contributes.commands` 数组中追加: ```json { "command": "codeReviewer.reviewMethod", "title": "Code Purifier: 审查此方法" } ``` #### 新增 configuration 在 `contributes.configuration.properties` 中追加: ```json "vscode-code-reviewer.codelens.enabled": { "type": "boolean", "default": true, "description": "在函数声明上方显示方法级审查按钮" }, "vscode-code-reviewer.codelens.languages": { "type": "array", "default": ["typescript", "javascript", "java", "python"], "description": "启用方法级 CodeLens 的语言列表" } ``` #### 新增 menu(可选) 在 `contributes.menus` 中追加右键菜单入口(作为 CodeLens 的补充): ```json "editor/context": [ { "command": "codeReviewer.reviewMethod", "when": "editorTextFocus", "group": "navigation" } ] ``` 注意:右键菜单触发时 `symbolRange` 参数为 `undefined`,命令内需要处理此情况——使用 `editor.selection.active` 作为 fallback 位置传入 `extractMethodScope`。 --- ## 实施顺序 按以下顺序实施,每步完成后可独立验证: ### Phase 1:基础设施 | 步骤 | 文件 | 验证方式 | |------|------|----------| | 1.1 | `src/scope/status-cache.ts` | 单元测试:set/get/clearDocument 行为正确 | | 1.2 | `src/scope/method-extractor.ts` | 手动测试:在 TS 文件中调用 `getMethodSymbols` 和 `extractMethodScope`,确认能提取方法名、代码、调用链 | | 1.3 | `src/ai/schema.ts` | TypeScript 编译通过,类型无冲突 | ### Phase 2:AI 引擎 | 步骤 | 文件 | 验证方式 | |------|------|----------| | 2.1 | `src/ai/engine.ts` 新增 `buildMethodReviewSystemPrompt` | 确认 prompt 文本包含 6 维度、逐路径枚举策略,且有规则时包含 Task 1 规则匹配任务 | | 2.2 | `src/ai/engine.ts` 新增 `buildMethodUserPrompt` | 确认输出包含签名、代码、调用链,且传入 `customRules` 时包含规则列表块 | | 2.3 | `src/ai/engine.ts` 新增 `runMethodReview` | 配置 API Key + 自定义规则后手动调用,确认返回 `customRuleResults` 和 `findings` 两类结果 | ### Phase 3:命令与集成 | 步骤 | 文件 | 验证方式 | |------|------|----------| | 3.1 | `src/activation/commands.ts` 新增 `reviewMethod` 命令 | 通过命令面板触发,确认加载自定义规则并传入 `runMethodReview`,面板展示审查结果 | | 3.2 | `src/extension.ts` 注册 CodeLensProvider | 打开 TS 文件,确认函数上方出现 CodeLens 按钮 | | 3.3 | `src/views/codeLensProvider.ts` | 点击 CodeLens 按钮,确认触发方法级审查 | | 3.4 | `package.json` 新增配置项 | 在设置中修改 `codelens.enabled`,确认 CodeLens 消失/出现 | ### Phase 4:i18n 与打磨 | 步骤 | 文件 | 验证方式 | |------|------|----------| | 4.1 | `src/i18n/messages.ts` 新增 key | 切换输出语言为 en,确认 CodeLens 文案变为英文 | | 4.2 | 右键菜单 fallback 处理 | 右键触发 `reviewMethod`,确认能用光标位置定位方法 | | 4.3 | 状态缓存刷新 | 审查完成后确认 CodeLens 文案变为 `✓ 已审查(N 个问题)` | --- ## 验收标准 ### 功能验收 1. 打开任意 `.ts` / `.js` / `.java` / `.py` 文件,每个函数声明上方出现 `🔍 Code Purifier: 审查此方法` CodeLens 按钮 2. 点击按钮后,右下角弹出进度通知 `Code Purifier 正在审查方法:xxx` 3. 审查完成后,CodeLens 按钮文案变为 `✓ Code Purifier: 已审查(N 个问题)` 或 `✓ Code Purifier: 已审查(无问题)`,其中 N = 自定义规则违规数 + AI 深度审查发现数 4. 审查面板自动打开,展示方法级的 AI 审查结果 5. 审查结果中,自定义规则违规的 `ruleId` 以 `custom:` 前缀标识,AI 深度审查发现的 `ruleId` 以 `method:` 前缀标识 6. 审查结果覆盖 6 个维度(correctness / security / design / convention / performance / testability) 7. 审查结果中的 `path` 字段描述了触发路径 8. 当工作区配置了自定义规则时,审查结果中包含 `customRuleResults`,且违规内容与规则语义相关(非文本匹配) 9. 当工作区无自定义规则时,审查结果中 `customRuleResults` 为空数组,AI 仅做 6 维度深度审查 ### 边界验收 1. 在没有 Symbol Provider 的纯文本文件中,CodeLens 不出现(不报错) 2. 文件内方法数超过 50 个时,CodeLens 不出现(性能保护) 3. 在设置中关闭 `codelens.enabled` 后,CodeLens 消失 4. 在设置中将某语言从 `codelens.languages` 中移除后,该语言文件不显示 CodeLens 5. 未配置 API Key 时,提示需要 API Key(不崩溃) 6. AI 返回非 JSON 时,降级提示解析失败(不崩溃) 7. 文档关闭后重新打开,CodeLens 恢复为初始状态 `🔍 Code Purifier: 审查此方法`(缓存已清理) ### 架构验收 1. `src/orchestrator/orchestrator.ts` 未被修改 2. `src/rules/rule-filter.ts` 未被修改 3. 现有 `codeReviewer.review` 命令行为不变(文档级三阶段审查正常工作) 4. 现有 `codeReviewer.reviewSelection` 命令行为不变 5. TypeScript 编译无错误(`npm run compile`) 6. ESLint 检查无错误(`npm run lint`) --- ## 注意事项 - 所有用户可见文案必须通过 `t()` 函数获取,支持中英文切换 - `runMethodReview` 内复用现有的 `getApiKey`、`createProvider`、`parseJsonResponse`、`addLineNumbers` 等工具函数,不重复实现 - `MethodFinding` 的字段与 `AIFinding` 高度重叠,`mergeResults` 可直接接收 `MethodFinding[]` 作为 `aiFindings` 参数(结构兼容) - CodeLens 的 `refresh()` 通过 `_onDidChangeCodeLenses.fire()` 触发,VS Code 会重新调用 `provideCodeLenses` - `package.json` 中的 `engines.vscode` 字段为 `^1.120.0`,CodeLens API 和 DocumentSymbol API 在该版本完全支持 - 自定义规则通过 `loadActiveRules` + `filterForDocument` 加载,与现有 `codeReviewer.review` 命令使用同一加载路径,保证规则一致性 - AI 对自定义规则做语义匹配而非文本匹配——prompt 中明确要求"理解语义,而非文本匹配",避免简单关键词命中导致的误报 - 当 `customRules` 为空数组时,system prompt 中的 Task 1 规则匹配段和 `customRuleResults` 输出段自动省略,AI 仅执行 6 维度深度审查