UNPKG

ripple-ai-detector

Version:

🌊 Ripple AI Bug Detector - Built by an AI that knows its flaws. Catch AI-generated bugs before you commit.

298 lines • 12.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AIDetector = void 0; const file_utils_1 = require("../utils/file-utils"); const simple_parser_1 = require("../analysis/simple-parser"); class AIDetector { parser; constructor() { this.parser = new simple_parser_1.SimpleParser(); } // Main AI detection method async detect(files) { const patterns = []; const reasoning = []; // Pattern 1: Multiple files changed simultaneously if (files.length > 3) { patterns.push({ type: 'multiple-file-changes', confidence: Math.min(0.3 + (files.length * 0.1), 0.8), evidence: `${files.length} files changed together`, weight: 0.3 }); reasoning.push(`${files.length} files modified simultaneously (AI often changes multiple files)`); } // Pattern 2: Function signature changes const functionPatterns = await this.detectFunctionPatterns(files); patterns.push(...functionPatterns.patterns); reasoning.push(...functionPatterns.reasoning); // Pattern 3: Import/export reorganization const importPatterns = await this.detectImportPatterns(files); patterns.push(...importPatterns.patterns); reasoning.push(...importPatterns.reasoning); // Pattern 4: Code style patterns const stylePatterns = await this.detectStylePatterns(files); patterns.push(...stylePatterns.patterns); reasoning.push(...stylePatterns.reasoning); // Pattern 5: TypeScript patterns const typePatterns = await this.detectTypeScriptPatterns(files); patterns.push(...typePatterns.patterns); reasoning.push(...typePatterns.reasoning); // Calculate overall confidence const overallConfidence = this.calculateConfidence(patterns); const isAIGenerated = overallConfidence > 0.6; return { isAIGenerated, confidence: overallConfidence, patterns, reasoning }; } // Detect function-related AI patterns async detectFunctionPatterns(files) { const patterns = []; const reasoning = []; let functionsWithOptions = 0; let functionsWithTypeAnnotations = 0; let totalFunctions = 0; for (const file of files) { try { const fileInfo = await file_utils_1.FileUtils.getFileInfo(file); if (!fileInfo.isJavaScript) continue; const functions = this.parser.extractFunctions(fileInfo.content, file); totalFunctions += functions.length; for (const func of functions) { // AI loves adding "options" parameters if (func.parameters.some(p => p.name.toLowerCase().includes('option') || p.name.toLowerCase().includes('config') || p.name.toLowerCase().includes('setting'))) { functionsWithOptions++; } // AI often adds type annotations if (func.parameters.some(p => p.type)) { functionsWithTypeAnnotations++; } } } catch (error) { // Skip files that can't be parsed } } if (totalFunctions > 0) { const optionsRatio = functionsWithOptions / totalFunctions; const typeRatio = functionsWithTypeAnnotations / totalFunctions; if (optionsRatio > 0.3) { patterns.push({ type: 'options-parameter-pattern', confidence: Math.min(optionsRatio, 0.9), evidence: `${functionsWithOptions}/${totalFunctions} functions have options parameters`, weight: 0.4 }); reasoning.push('High usage of options/config parameters (common AI pattern)'); } if (typeRatio > 0.5) { patterns.push({ type: 'type-annotation-pattern', confidence: Math.min(typeRatio, 0.8), evidence: `${functionsWithTypeAnnotations}/${totalFunctions} functions have type annotations`, weight: 0.3 }); reasoning.push('Extensive type annotations added (AI often adds types)'); } } return { patterns, reasoning }; } // Detect import/export AI patterns async detectImportPatterns(files) { const patterns = []; const reasoning = []; let totalImports = 0; let namedImports = 0; let reorganizedImports = 0; for (const file of files) { try { const fileInfo = await file_utils_1.FileUtils.getFileInfo(file); if (!fileInfo.isJavaScript) continue; // For MVP, we'll skip import analysis to keep it simple const imports = []; totalImports += imports.length; namedImports += imports.filter(i => !i.isDefault && !i.isNamespace).length; // Check for import reorganization patterns const content = fileInfo.content; const importLines = content.split('\n').filter(line => line.trim().startsWith('import')); // AI often groups and sorts imports if (importLines.length > 3) { const sortedLines = [...importLines].sort(); const isSorted = JSON.stringify(importLines) === JSON.stringify(sortedLines); if (isSorted) { reorganizedImports++; } } } catch (error) { // Skip files that can't be parsed } } if (totalImports > 0) { const namedImportRatio = namedImports / totalImports; if (namedImportRatio > 0.7) { patterns.push({ type: 'named-import-preference', confidence: Math.min(namedImportRatio, 0.7), evidence: `${namedImports}/${totalImports} imports are named imports`, weight: 0.2 }); reasoning.push('High ratio of named imports (AI prefers explicit imports)'); } } if (reorganizedImports > 0) { patterns.push({ type: 'import-organization', confidence: 0.6, evidence: `${reorganizedImports} files have organized imports`, weight: 0.3 }); reasoning.push('Imports appear to be automatically organized'); } return { patterns, reasoning }; } // Detect code style AI patterns async detectStylePatterns(files) { const patterns = []; const reasoning = []; let consistentFormatting = 0; let verboseComments = 0; let totalFiles = files.length; for (const file of files) { try { const fileInfo = await file_utils_1.FileUtils.getFileInfo(file); if (!fileInfo.isJavaScript) continue; const content = fileInfo.content; const lines = content.split('\n'); // Check for consistent formatting (AI is very consistent) const indentationPattern = this.detectIndentationPattern(lines); if (indentationPattern.consistent) { consistentFormatting++; } // Check for verbose comments (AI often adds explanatory comments) const commentLines = lines.filter(line => line.trim().startsWith('//') || line.trim().startsWith('/*') || line.trim().startsWith('*')); if (commentLines.length > lines.length * 0.1) { verboseComments++; } } catch (error) { // Skip files that can't be parsed } } if (totalFiles > 0) { const formattingRatio = consistentFormatting / totalFiles; const commentRatio = verboseComments / totalFiles; if (formattingRatio > 0.8) { patterns.push({ type: 'consistent-formatting', confidence: Math.min(formattingRatio, 0.6), evidence: `${consistentFormatting}/${totalFiles} files have consistent formatting`, weight: 0.2 }); reasoning.push('Extremely consistent code formatting'); } if (commentRatio > 0.5) { patterns.push({ type: 'verbose-comments', confidence: Math.min(commentRatio, 0.7), evidence: `${verboseComments}/${totalFiles} files have extensive comments`, weight: 0.3 }); reasoning.push('High density of explanatory comments'); } } return { patterns, reasoning }; } // Detect TypeScript-specific AI patterns async detectTypeScriptPatterns(files) { const patterns = []; const reasoning = []; let interfaceDefinitions = 0; let explicitTypes = 0; let tsFiles = 0; for (const file of files) { try { const fileInfo = await file_utils_1.FileUtils.getFileInfo(file); if (!fileInfo.isTypeScript) continue; tsFiles++; const content = fileInfo.content; // Count interface definitions (AI loves creating interfaces) const interfaceMatches = content.match(/interface\s+\w+/g); if (interfaceMatches) { interfaceDefinitions += interfaceMatches.length; } // Count explicit type annotations const typeMatches = content.match(/:\s*\w+/g); if (typeMatches) { explicitTypes += typeMatches.length; } } catch (error) { // Skip files that can't be parsed } } if (tsFiles > 0) { const interfaceRatio = interfaceDefinitions / tsFiles; const typeRatio = explicitTypes / (tsFiles * 10); // Normalize by expected types per file if (interfaceRatio > 2) { patterns.push({ type: 'interface-heavy', confidence: Math.min(interfaceRatio / 5, 0.8), evidence: `${interfaceDefinitions} interfaces across ${tsFiles} TypeScript files`, weight: 0.4 }); reasoning.push('Heavy use of TypeScript interfaces (AI loves type safety)'); } if (typeRatio > 0.8) { patterns.push({ type: 'explicit-typing', confidence: Math.min(typeRatio, 0.7), evidence: `Extensive explicit type annotations`, weight: 0.3 }); reasoning.push('Very explicit type annotations throughout'); } } return { patterns, reasoning }; } // Detect indentation pattern consistency detectIndentationPattern(lines) { const indentations = lines .filter(line => line.trim().length > 0) .map(line => line.match(/^(\s*)/)?.[1] || '') .filter(indent => indent.length > 0); if (indentations.length === 0) { return { consistent: true, pattern: 'none' }; } // Check if all indentations use the same pattern (spaces vs tabs) const usesSpaces = indentations.every(indent => !indent.includes('\t')); const usesTabs = indentations.every(indent => indent.includes('\t')); return { consistent: usesSpaces || usesTabs, pattern: usesSpaces ? 'spaces' : usesTabs ? 'tabs' : 'mixed' }; } // Calculate overall confidence from patterns calculateConfidence(patterns) { if (patterns.length === 0) { return 0; } // Weighted average of pattern confidences const totalWeight = patterns.reduce((sum, p) => sum + p.weight, 0); const weightedSum = patterns.reduce((sum, p) => sum + (p.confidence * p.weight), 0); return totalWeight > 0 ? Math.min(weightedSum / totalWeight, 0.99) : 0; } } exports.AIDetector = AIDetector; //# sourceMappingURL=ai-detector.js.map