const fs = require('fs'); const path = require('path'); const https = require('https'); const http = require('http'); const crypto = require('crypto'); /** * AI分析服务 * 用于对代码检查结果进行智能分析、去重、风险评估和生成修改建议 * 支持OpenAI和科大讯飞Spark API */ class AIAnalyzer { constructor() { // OpenAI配置 this.openaiApiKey = process.env.OPENAI_API_KEY || ''; this.openaiApiBase = process.env.OPENAI_API_BASE || 'https://api.openai.com/v1'; this.openaiModel = process.env.OPENAI_MODEL || 'gpt-3.5-turbo'; // 科大讯飞配置(HTTP API) // 注意:HTTP API使用APIpassword(Bearer Token),不是apikey和apisecret // 获取地址:https://console.xfyun.cn/services/bmx1 this.xfApiPassword = process.env.XF_API_PASSWORD || process.env.XF_API_KEY || ''; // HTTP协议的APIpassword this.xfApiKey = process.env.XF_API_KEY || ''; // 兼容旧配置 this.xfApiSecret = process.env.XF_API_SECRET || ''; // 兼容旧配置(WebSocket使用) this.xfModel = process.env.XF_MODEL || 'spark-x'; // Spark X1.5 模型,使用spark-x this.xfApiUrl = process.env.XF_API_URL || 'https://spark-api-open.xf-yun.com/v2/chat/completions'; // 选择使用的API提供商 this.provider = process.env.AI_PROVIDER || 'xf'; // 'openai' 或 'xf' this.enabled = process.env.AI_ANALYZER_ENABLED !== 'false'; console.log(`[AI分析] 使用提供商: ${this.provider}`); if (this.provider === 'xf') { if (this.xfApiPassword) { console.log(`[AI分析] 科大讯飞 HTTP API Password: ${this.xfApiPassword.substring(0, 10)}... (已配置)`); console.log(`[AI分析] 使用模型: ${this.xfModel}`); } else { console.log(`[AI分析] 科大讯飞 HTTP API Password: 未配置`); console.log(`[AI分析] 请在控制台获取APIpassword: https://console.xfyun.cn/services/bmx1`); } } else if (this.provider === 'openai') { console.log(`[AI分析] OpenAI API Key: ${this.openaiApiKey ? '已配置' : '未配置'}`); } } /** * 分析检查结果 * @param {Array} issues - 检查结果列表 * @param {string} projectPath - 项目路径 * @returns {Promise} 分析结果 */ async analyzeIssues(issues, projectPath = null) { if (!this.enabled) { console.log('[AI分析] AI分析功能已禁用,使用基础去重'); return this.basicDeduplication(issues); } try { // 先进行基础去重 const deduplicated = this.basicDeduplication(issues); // 检查是否有可用的API配置 const hasApiConfig = (this.provider === 'openai' && this.openaiApiKey) || (this.provider === 'xf' && this.xfApiPassword); if (hasApiConfig) { return await this.aiAnalysis(deduplicated.issues, projectPath); } else { // 否则使用规则基础分析 console.log('[AI分析] 未配置API密钥,使用规则基础分析'); return this.ruleBasedAnalysis(deduplicated.issues); } } catch (error) { console.error('[AI分析] 分析失败,回退到基础分析:', error); return this.basicDeduplication(issues); } } /** * 基础去重 - 基于文件、行号、规则和消息相似度 */ basicDeduplication(issues) { const seen = new Map(); const uniqueIssues = []; const duplicates = []; for (const issue of issues) { // 生成唯一键:文件路径 + 行号 + 规则ID const key = `${issue.relative_path || issue.file}:${issue.line}:${issue.rule || 'unknown'}`; if (seen.has(key)) { // 检查消息相似度 const existing = seen.get(key); const similarity = this.calculateSimilarity( issue.message || '', existing.message || '' ); if (similarity < 0.7) { // 消息差异较大,可能是不同的问题 uniqueIssues.push(issue); } else { // 相似度高,合并工具来源 if (!existing.detected_by) { existing.detected_by = [existing.tool || 'unknown']; } if (issue.tool && !existing.detected_by.includes(issue.tool)) { existing.detected_by.push(issue.tool); } duplicates.push(issue); } } else { issue.detected_by = issue.tool ? [issue.tool] : ['unknown']; seen.set(key, issue); uniqueIssues.push(issue); } } return { issues: uniqueIssues, total_issues: uniqueIssues.length, duplicates_removed: duplicates.length, deduplication_rate: issues.length > 0 ? ((duplicates.length / issues.length) * 100).toFixed(2) + '%' : '0%' }; } /** * 计算字符串相似度(简单的Jaccard相似度) */ calculateSimilarity(str1, str2) { if (!str1 || !str2) return 0; const words1 = new Set(str1.toLowerCase().split(/\s+/)); const words2 = new Set(str2.toLowerCase().split(/\s+/)); const intersection = new Set([...words1].filter(x => words2.has(x))); const union = new Set([...words1, ...words2]); return intersection.size / union.size; } /** * 基于规则的分析(不使用AI) */ ruleBasedAnalysis(issues) { const analyzed = issues.map(issue => { const risk = this.assessRisk(issue); const suggestion = this.generateSuggestion(issue); return { ...issue, risk_level: risk.level, risk_score: risk.score, suggestion: suggestion, ai_analyzed: false }; }); // 按风险评分排序 analyzed.sort((a, b) => b.risk_score - a.risk_score); return { issues: analyzed, total_issues: analyzed.length, high_risk_count: analyzed.filter(i => i.risk_level === 'high').length, medium_risk_count: analyzed.filter(i => i.risk_level === 'medium').length, low_risk_count: analyzed.filter(i => i.risk_level === 'low').length, analysis_method: 'rule-based' }; } /** * 使用AI进行分析 */ async aiAnalysis(issues, projectPath) { try { // 一次性发送所有问题,不再分批处理 console.log(`[AI分析] 开始AI分析,共 ${issues.length} 个问题,一次性处理`); const analyzedIssues = this.provider === 'xf' ? await this.analyzeBatchXf(issues, projectPath) : await this.analyzeBatch(issues, projectPath); // 按风险评分排序 analyzedIssues.sort((a, b) => b.risk_score - a.risk_score); return { issues: analyzedIssues, total_issues: analyzedIssues.length, high_risk_count: analyzedIssues.filter(i => i.risk_level === 'high').length, medium_risk_count: analyzedIssues.filter(i => i.risk_level === 'medium').length, low_risk_count: analyzedIssues.filter(i => i.risk_level === 'low').length, analysis_method: 'ai-powered' }; } catch (error) { console.error('[AI分析] AI分析失败:', error); // 回退到规则基础分析 return this.ruleBasedAnalysis(issues); } } /** * 批量分析问题 */ async analyzeBatch(issues, projectPath) { const prompt = await this.buildAnalysisPrompt(issues, projectPath); try { const requestData = JSON.stringify({ model: this.openaiModel, messages: [ { role: 'system', content: '你是一个专业的Python代码质量分析专家,擅长分析代码问题,评估风险并提供详细、具体的修改建议。你的建议必须包含问题分析、修复步骤、代码示例和最佳实践。' }, { role: 'user', content: prompt } ], temperature: 0.3, max_tokens: 8000 // 增加token数量以支持一次性处理所有问题和完整代码 }); const url = new URL(`${this.openaiApiBase}/chat/completions`); const options = { hostname: url.hostname, port: url.port || (url.protocol === 'https:' ? 443 : 80), path: url.pathname, method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.openaiApiKey}`, 'Content-Length': Buffer.byteLength(requestData) } }; const data = await new Promise((resolve, reject) => { const client = url.protocol === 'https:' ? https : http; const req = client.request(options, (res) => { let responseData = ''; res.on('data', (chunk) => { responseData += chunk; }); res.on('end', () => { if (res.statusCode !== 200) { reject(new Error(`AI API错误: ${res.statusCode} ${res.statusMessage}`)); return; } try { resolve(JSON.parse(responseData)); } catch (e) { reject(new Error(`解析AI响应失败: ${e.message}`)); } }); }); req.on('error', reject); req.write(requestData); req.end(); }); // 提取AI返回的内容 let aiContent = ''; if (data.choices && data.choices.length > 0) { aiContent = data.choices[0].message.content || ''; } else if (data.content) { aiContent = data.content; } else { throw new Error('AI响应中未找到内容'); } console.log(`[AI分析-OpenAI] 收到响应,长度: ${aiContent.length} 字符`); console.log(`[AI分析-OpenAI] 响应内容预览: ${aiContent.substring(0, 300)}`); // 解析AI响应中的JSON let analysisResult; try { // 尝试直接解析 analysisResult = JSON.parse(aiContent); } catch (e) { // 如果不是纯JSON,尝试提取JSON部分 console.log('[AI分析-OpenAI] 直接解析失败,尝试提取JSON部分'); const jsonMatch = aiContent.match(/\{[\s\S]*\}/); if (jsonMatch) { try { analysisResult = JSON.parse(jsonMatch[0]); console.log('[AI分析-OpenAI] 成功提取并解析JSON'); } catch (e2) { console.error('[AI分析-OpenAI] 提取的JSON解析失败:', e2); console.error('[AI分析-OpenAI] 响应内容:', aiContent.substring(0, 1000)); throw new Error('无法解析AI响应为JSON: ' + e2.message); } } else { console.error('[AI分析-OpenAI] 响应中未找到JSON格式'); console.error('[AI分析-OpenAI] 响应内容:', aiContent.substring(0, 1000)); throw new Error('AI响应中未找到有效的JSON格式'); } } // 验证分析结果格式 if (!analysisResult.issues || !Array.isArray(analysisResult.issues)) { console.warn('[AI分析-OpenAI] AI返回的格式不正确,使用规则基础分析'); console.warn('[AI分析-OpenAI] 返回的数据结构:', JSON.stringify(analysisResult).substring(0, 500)); throw new Error('AI返回格式不正确'); } console.log(`[AI分析-OpenAI] 成功解析,包含 ${analysisResult.issues.length} 个问题的分析结果`); // 合并分析结果到原始问题 return issues.map((issue, index) => { const analysis = analysisResult.issues && analysisResult.issues[index] ? analysisResult.issues[index] : {}; const aiSuggestion = analysis.suggestion || ''; // 验证AI返回的建议是否有效 if (!aiSuggestion || aiSuggestion.trim().length < 20) { console.warn(`[AI分析-OpenAI] 问题 ${index + 1} 的AI建议过短或为空,使用规则基础建议`); console.warn(`[AI分析-OpenAI] 原始建议: ${aiSuggestion}`); } else { console.log(`[AI分析-OpenAI] 问题 ${index + 1} 获得AI建议,长度: ${aiSuggestion.length} 字符`); } return { ...issue, risk_level: analysis.risk_level || this.assessRisk(issue).level, risk_score: analysis.risk_score !== undefined ? analysis.risk_score : this.assessRisk(issue).score, suggestion: (aiSuggestion && aiSuggestion.trim().length >= 20) ? aiSuggestion : this.generateSuggestion(issue), ai_analyzed: (aiSuggestion && aiSuggestion.trim().length >= 20) }; }); } catch (error) { console.error('[AI分析] 批量分析失败:', error); // 回退到规则基础分析 return issues.map(issue => { const risk = this.assessRisk(issue); const suggestion = this.generateSuggestion(issue); return { ...issue, risk_level: risk.level, risk_score: risk.score, suggestion: suggestion, ai_analyzed: false }; }); } } /** * 使用科大讯飞API批量分析问题(HTTP方式) */ async analyzeBatchXf(issues, projectPath) { const prompt = await this.buildAnalysisPrompt(issues, projectPath); try { // 构建请求数据 - 使用类似OpenAI的格式 const requestData = JSON.stringify({ model: this.xfModel, messages: [ { role: 'system', content: '你是一个专业的Python代码质量分析专家,擅长分析代码问题,评估风险并提供详细、具体的修改建议。你的建议必须包含问题分析、修复步骤、代码示例和最佳实践。' }, { role: 'user', content: prompt } ], temperature: 0.3, max_tokens: 8000 // 增加token数量以支持一次性处理所有问题和完整代码 }); // 解析API URL const url = new URL(this.xfApiUrl); // 根据官方文档,HTTP API使用Bearer Token认证(APIpassword) // 文档:https://www.xfyun.cn/doc/spark/X1http.html if (!this.xfApiPassword) { throw new Error('未配置XF_API_PASSWORD(HTTP协议的APIpassword),请在控制台获取:https://console.xfyun.cn/services/bmx1'); } const options = { hostname: url.hostname, port: url.port || 443, path: url.pathname, method: 'POST', headers: { 'Authorization': `Bearer ${this.xfApiPassword}`, // 使用Bearer Token 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(requestData) } }; console.log(`[AI分析-讯飞] 发送HTTP请求到: ${this.xfApiUrl}`); console.log(`[AI分析-讯飞] 使用模型: ${this.xfModel}`); console.log(`[AI分析-讯飞] 请求头部:`); console.log(` Authorization: Bearer ${this.xfApiPassword.substring(0, 10)}...`); console.log(` Content-Type: application/json`); console.log(` Content-Length: ${Buffer.byteLength(requestData)}`); const data = await new Promise((resolve, reject) => { const req = https.request(options, (res) => { let responseData = ''; res.on('data', (chunk) => { responseData += chunk; }); res.on('end', () => { if (res.statusCode !== 200) { console.error(`[AI分析-讯飞] HTTP错误: ${res.statusCode} ${res.statusMessage}`); console.error(`[AI分析-讯飞] 响应内容:`, responseData.substring(0, 500)); reject(new Error(`科大讯飞API错误: ${res.statusCode} ${res.statusMessage}`)); return; } try { resolve(JSON.parse(responseData)); } catch (e) { console.error('[AI分析-讯飞] 解析响应失败:', e); console.error('[AI分析-讯飞] 响应内容:', responseData.substring(0, 500)); reject(new Error(`解析AI响应失败: ${e.message}`)); } }); }); req.on('error', (error) => { console.error('[AI分析-讯飞] 请求错误:', error); reject(error); }); req.write(requestData); req.end(); }); // 提取AI返回的内容 let aiContent = ''; if (data.choices && data.choices.length > 0) { aiContent = data.choices[0].message.content || ''; } else if (data.content) { aiContent = data.content; } else { throw new Error('AI响应中未找到内容'); } console.log(`[AI分析-讯飞] 收到响应,长度: ${aiContent.length} 字符`); console.log(`[AI分析-讯飞] 响应内容预览: ${aiContent.substring(0, 500)}`); // 如果响应太长,也打印末尾部分 if (aiContent.length > 1000) { console.log(`[AI分析-讯飞] 响应内容末尾: ...${aiContent.substring(aiContent.length - 500)}`); } // 解析AI响应中的JSON let analysisResult; try { // 尝试直接解析 analysisResult = JSON.parse(aiContent); console.log('[AI分析-讯飞] 直接解析JSON成功'); } catch (e) { console.log('[AI分析-讯飞] 直接解析失败,尝试提取JSON部分'); console.log('[AI分析-讯飞] 错误信息:', e.message); // 方法1: 尝试提取markdown代码块中的JSON let jsonText = ''; const codeBlockMatch = aiContent.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/); if (codeBlockMatch) { jsonText = codeBlockMatch[1]; console.log('[AI分析-讯飞] 从markdown代码块中提取JSON'); } else { // 方法2: 尝试提取第一个完整的JSON对象 const jsonMatch = aiContent.match(/\{[\s\S]*\}/); if (jsonMatch) { jsonText = jsonMatch[0]; console.log('[AI分析-讯飞] 从响应中提取JSON对象'); } } if (jsonText) { try { // 尝试清理JSON文本(移除可能的控制字符) jsonText = jsonText.replace(/[\x00-\x1F\x7F]/g, ''); analysisResult = JSON.parse(jsonText); console.log('[AI分析-讯飞] 成功提取并解析JSON'); } catch (e2) { console.error('[AI分析-讯飞] 提取的JSON解析失败:', e2.message); console.error('[AI分析-讯飞] 错误位置:', e2.message.match(/position (\d+)/)?.[1] || '未知'); // 尝试修复常见的JSON错误 try { // 先尝试找到JSON的起始和结束位置 let startPos = jsonText.indexOf('{'); let endPos = jsonText.lastIndexOf('}'); if (startPos >= 0 && endPos > startPos) { jsonText = jsonText.substring(startPos, endPos + 1); } // 尝试修复未转义的引号(在字符串值中) // 使用更智能的方法:识别JSON结构,只修复字符串值中的引号 // 策略:找到 "key": "value" 模式,修复value中的引号 // 使用状态机方法修复JSON中的未转义引号 // 策略:遍历JSON,识别字符串值,转义其中的未转义引号 let fixedJson = ''; let inString = false; let escapeNext = false; for (let i = 0; i < jsonText.length; i++) { const char = jsonText[i]; if (escapeNext) { // 当前字符是转义序列的一部分 fixedJson += char; escapeNext = false; continue; } if (char === '\\') { // 遇到反斜杠,下一个字符是转义字符 fixedJson += char; escapeNext = true; continue; } if (char === '"') { if (!inString) { // 字符串开始 inString = true; fixedJson += char; } else { // 在字符串中遇到引号 // 检查后面的字符,判断这是字符串结束还是字符串值中的引号 let j = i + 1; // 跳过空白字符 while (j < jsonText.length && /\s/.test(jsonText[j])) { j++; } if (j >= jsonText.length) { // 到达末尾,这是字符串结束 inString = false; fixedJson += char; } else { const nextNonSpace = jsonText[j]; // 如果下一个非空白字符是:或,或}或],说明这是字符串结束 if (nextNonSpace === ':' || nextNonSpace === ',' || nextNonSpace === '}' || nextNonSpace === ']') { inString = false; fixedJson += char; } else { // 这是字符串值中的引号,需要转义 fixedJson += '\\"'; } } } } else { fixedJson += char; } } jsonText = fixedJson; // 尝试解析修复后的JSON analysisResult = JSON.parse(jsonText); console.log('[AI分析-讯飞] 修复后成功解析JSON'); } catch (e3) { console.error('[AI分析-讯飞] 修复后仍然失败:', e3.message); // 打印错误位置的上下文 const errorPos = parseInt(e3.message.match(/position (\d+)/)?.[1] || '0'); if (errorPos > 0) { const start = Math.max(0, errorPos - 100); const end = Math.min(jsonText.length, errorPos + 100); console.error('[AI分析-讯飞] 错误位置上下文:', jsonText.substring(start, end)); console.error('[AI分析-讯飞] 错误位置标记:', ' '.repeat(Math.min(100, errorPos - start)) + '^'); } console.error('[AI分析-讯飞] 尝试解析的JSON文本(前1000字符):', jsonText.substring(0, 1000)); if (jsonText.length > 1000) { console.error('[AI分析-讯飞] 尝试解析的JSON文本(后1000字符):', jsonText.substring(Math.max(0, jsonText.length - 1000))); } // 最后尝试:使用eval(不安全,但作为最后的尝试) // 实际上,我们应该避免使用eval,而是抛出错误让用户知道 throw new Error(`无法解析AI响应为JSON: ${e2.message}。错误位置: ${errorPos}。请检查AI返回的JSON格式是否正确。`); } } } else { console.error('[AI分析-讯飞] 响应中未找到JSON格式'); console.error('[AI分析-讯飞] 完整响应内容:', aiContent); throw new Error('AI响应中未找到有效的JSON格式'); } } // 验证分析结果格式 if (!analysisResult.issues || !Array.isArray(analysisResult.issues)) { console.warn('[AI分析-讯飞] AI返回的格式不正确,使用规则基础分析'); console.warn('[AI分析-讯飞] 返回的数据结构:', JSON.stringify(analysisResult).substring(0, 500)); throw new Error('AI返回格式不正确'); } console.log(`[AI分析-讯飞] 成功解析,包含 ${analysisResult.issues.length} 个问题的分析结果`); console.log(`[AI分析-讯飞] 原始问题数量: ${issues.length}`); // 检查返回的issues数组长度 if (analysisResult.issues.length < issues.length) { console.warn(`[AI分析-讯飞] AI返回的issues数组长度不足: ${analysisResult.issues.length} < ${issues.length}`); console.warn(`[AI分析-讯飞] 将使用规则基础分析补充缺失的问题`); // 打印AI返回的每个问题的建议长度,用于调试 analysisResult.issues.forEach((item, idx) => { const suggestion = item.suggestion || ''; console.log(`[AI分析-讯飞] 问题 ${idx + 1}: suggestion长度=${suggestion.length}, risk_level=${item.risk_level}, risk_score=${item.risk_score}`); }); } // 合并分析结果到原始问题 return issues.map((issue, index) => { const analysis = analysisResult.issues && analysisResult.issues[index] ? analysisResult.issues[index] : {}; const aiSuggestion = analysis.suggestion || ''; // 如果AI返回的数组长度不够,或者建议为空/过短,使用规则基础建议 if (index >= analysisResult.issues.length) { console.warn(`[AI分析-讯飞] 问题 ${index + 1} 超出AI返回范围,使用规则基础建议`); const risk = this.assessRisk(issue); const suggestion = this.generateSuggestion(issue); return { ...issue, risk_level: risk.level, risk_score: risk.score, suggestion: suggestion, ai_analyzed: false }; } // 验证AI返回的建议是否有效 if (!aiSuggestion || aiSuggestion.trim().length < 20) { console.warn(`[AI分析-讯飞] 问题 ${index + 1} 的AI建议过短或为空,使用规则基础建议`); console.warn(`[AI分析-讯飞] 原始建议: "${aiSuggestion}" (长度: ${aiSuggestion.length})`); const risk = this.assessRisk(issue); const suggestion = this.generateSuggestion(issue); return { ...issue, risk_level: analysis.risk_level || risk.level, risk_score: analysis.risk_score !== undefined ? analysis.risk_score : risk.score, suggestion: suggestion, ai_analyzed: false }; } else { console.log(`[AI分析-讯飞] 问题 ${index + 1} 获得AI建议,长度: ${aiSuggestion.length} 字符`); return { ...issue, risk_level: analysis.risk_level || this.assessRisk(issue).level, risk_score: analysis.risk_score !== undefined ? analysis.risk_score : this.assessRisk(issue).score, suggestion: aiSuggestion, ai_analyzed: true }; } }); } catch (error) { console.error('[AI分析-讯飞] 批量分析失败:', error); // 回退到规则基础分析 return issues.map(issue => { const risk = this.assessRisk(issue); const suggestion = this.generateSuggestion(issue); return { ...issue, risk_level: risk.level, risk_score: risk.score, suggestion: suggestion, ai_analyzed: false }; }); } } /** * 构建AI分析提示词 * 优化:一次性处理所有问题,按文件分组,读取完整文件内容 */ async buildAnalysisPrompt(issues, projectPath) { // 按文件分组问题 const issuesByFile = new Map(); for (const issue of issues) { const filePath = issue.relative_path || issue.file; if (!issuesByFile.has(filePath)) { issuesByFile.set(filePath, []); } issuesByFile.get(filePath).push(issue); } console.log(`[AI分析] 问题分布在 ${issuesByFile.size} 个文件中`); // 读取每个文件的完整内容 const filesData = new Map(); for (const [filePath, fileIssues] of issuesByFile.entries()) { try { if (filePath && projectPath) { const fullPath = path.join(projectPath, filePath); if (fs.existsSync(fullPath)) { const content = fs.readFileSync(fullPath, 'utf8'); filesData.set(filePath, { content: content, issues: fileIssues, lineCount: content.split('\n').length }); console.log(`[AI分析] 读取文件: ${filePath} (${content.split('\n').length} 行)`); } } } catch (error) { console.warn(`[AI分析] 无法读取文件 ${filePath}: ${error.message}`); } } // 构建问题列表(包含完整文件内容) let issuesText = ''; let issueIndex = 1; for (const [filePath, fileData] of filesData.entries()) { issuesText += `\n## 文件: ${filePath}\n`; issuesText += `完整代码(共 ${fileData.lineCount} 行):\n\`\`\`python\n${fileData.content}\n\`\`\`\n\n`; issuesText += `该文件中的问题:\n`; for (const issue of fileData.issues) { issuesText += `${issueIndex}. 行号: ${issue.line}, 列号: ${issue.column || 0}\n`; issuesText += ` 规则: ${issue.rule || '未知'}\n`; issuesText += ` 消息: ${issue.message || '无'}\n`; issuesText += ` 类型: ${issue.type || 'unknown'}\n`; issuesText += ` 严重程度: ${issue.severity || 'medium'}\n`; issuesText += ` 检测工具: ${(issue.detected_by || []).join(', ')}\n\n`; issueIndex++; } } // 对于没有文件内容的问题,单独列出 for (const issue of issues) { const filePath = issue.relative_path || issue.file; if (!filesData.has(filePath)) { issuesText += `${issueIndex}. 文件: ${filePath}\n`; issuesText += ` 行号: ${issue.line}\n`; issuesText += ` 规则: ${issue.rule || '未知'}\n`; issuesText += ` 消息: ${issue.message || '无'}\n\n`; issueIndex++; } } return `你是一个专业的Python代码质量分析专家。请分析以下代码检查问题,并提供统一的修复建议。 **重要任务:** 1. **合并重复问题**:首先识别并合并重复或相似的问题(不同工具可能检测到相同的问题) 2. **统一分析**:对所有问题(包括合并后的)进行统一分析 3. **提供详细建议**:为每个问题提供详细的修复建议 **分析要求:** 1. **风险等级**(high/medium/low):根据问题的严重程度和影响范围评估 2. **风险评分**(0-100):数值越高表示风险越大,需要优先处理 3. **修改建议**:必须提供详细且具体的修复建议,包括: - 问题原因分析 - 具体的修复步骤 - 修复前后的代码示例对比 - 最佳实践建议 - 避免类似问题的建议 **重要要求:** - **必须为所有问题提供建议**:返回的issues数组长度必须与原始问题数量完全一致(共 ${issues.length} 个问题) - **每个问题都必须有suggestion字段**:suggestion字段不能为空,必须包含至少50字的详细建议 - **按顺序对应**:issues数组中的第1个元素对应第1个问题,第2个元素对应第2个问题,以此类推 - **如果问题已合并**:在对应位置返回合并后的分析结果,但必须确保数组长度不变 **注意:** - 请先合并重复问题,然后对所有问题(包括合并后的)进行分析 - 根据完整代码上下文给出针对性建议 - 建议要具体、可操作,避免空泛的描述 - **绝对不能省略任何问题**:即使问题相似或重复,也必须为每个问题提供独立的分析结果 问题列表(包含完整代码): ${issuesText} **请严格按照以下JSON格式返回分析结果,必须确保:** 1. issues数组长度 = ${issues.length}(与原始问题数量完全一致) 2. 每个issue对象都包含完整的suggestion字段(至少50字) 3. 所有字段都必须有值,不能为空 4. **只返回纯JSON,不要包含任何markdown代码块标记(不要使用\`\`\`json\`\`\`包裹)** 5. **JSON中的字符串值必须正确转义**:换行符使用\\n,引号使用\\",反斜杠使用\\\\ **返回格式(纯JSON,不要任何其他文本):** { "issues": [ { "risk_level": "high|medium|low", "risk_score": 0-100, "suggestion": "详细的问题分析、修复步骤、代码示例和最佳实践建议(至少50字,不能为空,字符串中的特殊字符必须正确转义)" } ] } **重要提示:** - 只返回JSON对象,不要包含任何解释文字 - 不要使用markdown代码块包裹JSON - JSON字符串中的换行符、引号等特殊字符必须正确转义 - 必须返回 ${issues.length} 个issues,每个都必须有完整的suggestion!**`; } /** * 评估风险(规则基础) */ assessRisk(issue) { let score = 0; let level = 'low'; // 基于类型 if (issue.type === 'error') score += 50; else if (issue.type === 'warning') score += 30; else score += 10; // 基于严重程度 if (issue.severity === 'high') score += 40; else if (issue.severity === 'medium') score += 20; else score += 10; // 基于规则类型(安全相关规则风险更高) const securityKeywords = ['security', 'injection', 'xss', 'csrf', 'sql', 'password', 'secret', 'key', 'token']; const ruleLower = (issue.rule || '').toLowerCase(); const messageLower = (issue.message || '').toLowerCase(); if (securityKeywords.some(keyword => ruleLower.includes(keyword) || messageLower.includes(keyword) )) { score += 30; } // 确定风险等级 if (score >= 70) level = 'high'; else if (score >= 40) level = 'medium'; else level = 'low'; return { level, score }; } /** * 生成修改建议(规则基础) */ generateSuggestion(issue) { const rule = issue.rule || ''; const message = issue.message || ''; const ruleLower = rule.toLowerCase(); const messageLower = message.toLowerCase(); // 基于规则类型生成建议 if (ruleLower.includes('import') || messageLower.includes('import')) { return '检查导入语句,确保导入的模块存在且路径正确。考虑使用相对导入或检查PYTHONPATH设置。'; } if (ruleLower.includes('unused') || messageLower.includes('unused')) { return '删除未使用的变量、函数或导入。如果确实需要保留,可以在变量名前加下划线(如 _unused_var)。'; } if (ruleLower.includes('naming') || messageLower.includes('naming')) { return '遵循PEP 8命名规范:类名使用驼峰命名(CamelCase),函数和变量使用下划线命名(snake_case)。'; } if (ruleLower.includes('security') || messageLower.includes('security') || ruleLower.includes('bandit')) { return '这是一个安全问题。请仔细检查代码,确保没有安全漏洞。考虑使用安全的替代方案,如使用参数化查询而不是字符串拼接。'; } if (ruleLower.includes('complexity') || messageLower.includes('complexity')) { return '代码复杂度过高。考虑将函数拆分为更小的函数,提高代码可读性和可维护性。'; } if (messageLower.includes('line too long')) { return '行长度超过限制。将长行拆分为多行,或使用括号、反斜杠进行换行。'; } if (messageLower.includes('missing docstring')) { return '添加函数或类的文档字符串(docstring),说明其功能、参数和返回值。'; } // 默认建议 return `根据规则"${rule}"的建议:${message}。请参考相关代码规范文档进行修改。`; } } module.exports = new AIAnalyzer();