UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

995 lines 40.3 kB
import { randomUUID } from 'crypto'; import { createTool, createSuccessResult, createErrorResult } from '../../core/tool-framework.js'; import { promises as fs } from 'fs'; import path from 'path'; /** * Index codebase for semantic search and dependency analysis */ const indexCodebaseTool = createTool({ name: 'index_codebase', description: 'Index codebase for semantic search and dependency analysis with SQLite storage', category: 'local-ai', inputSchema: { type: 'object', properties: { directory: { type: 'string', default: '.', description: 'Directory to index (relative or absolute path)', maxLength: 500 }, extensions: { type: 'array', items: { type: 'string', maxLength: 10 }, default: ['.ts', '.js', '.tsx', '.jsx', '.py', '.rs', '.go'], description: 'File extensions to include', maxItems: 20 }, recursive: { type: 'boolean', default: true, description: 'Recursively index subdirectories' }, includePatterns: { type: 'array', items: { type: 'string', maxLength: 100 }, description: 'Glob patterns to include', maxItems: 10 }, excludePatterns: { type: 'array', items: { type: 'string', maxLength: 100 }, default: ['node_modules/**', '.git/**', 'dist/**', 'build/**'], description: 'Glob patterns to exclude', maxItems: 20 } }, additionalProperties: false }, async execute(input, context) { try { const indexId = randomUUID(); const now = Date.now(); // Resolve directory path const dir = path.isAbsolute(input.directory || '.') ? input.directory || '.' : path.join(process.cwd(), input.directory || '.'); // Find code files const files = await findCodeFiles(dir, { extensions: input.extensions || ['.ts', '.js', '.tsx', '.jsx', '.py', '.rs', '.go'], recursive: input.recursive !== false, includePatterns: input.includePatterns || [], excludePatterns: input.excludePatterns || ['node_modules/**', '.git/**', 'dist/**', 'build/**'] }); let indexed = 0; let failed = 0; const errors = []; // Process files and store embeddings for (const file of files) { try { const content = await fs.readFile(file, 'utf-8'); const language = detectLanguage(file); const relativePath = path.relative(dir, file); // Extract code features const features = extractCodeFeatures(content, language); const functions = extractFunctions(content, language); const imports = extractImports(content, language); // Generate embedding data const embeddingData = generateEmbeddingData(content, language, features); // Store in database const result = await context.db.run(`INSERT INTO ai_embeddings (id, project_id, file_path, content, language, features, functions, imports, embedding_data, file_size, last_modified, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), context.projectId || 'default', relativePath, content, language, JSON.stringify(features), JSON.stringify(functions), JSON.stringify(imports), JSON.stringify(embeddingData), content.length, now, now, now ]); if (result.success) { indexed++; } else { failed++; errors.push(`Failed to store ${relativePath}: ${result.error}`); } } catch (error) { failed++; errors.push(`Error processing ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Store indexing session await context.db.run(`INSERT INTO ai_indexing_sessions (id, project_id, directory, files_found, files_indexed, files_failed, extensions, errors, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ indexId, context.projectId || 'default', dir, files.length, indexed, failed, JSON.stringify(input.extensions), JSON.stringify(errors.slice(0, 100)), // Limit error storage now ]); return createSuccessResult({ indexingSession: { id: indexId, directory: dir, filesFound: files.length, filesIndexed: indexed, filesFailed: failed, embeddingCount: indexed, errors: errors.slice(0, 10) // Limit errors in response }, message: `Successfully indexed ${indexed}/${files.length} files for semantic search`, summary: { total: files.length, indexed, failed, successRate: files.length > 0 ? (indexed / files.length) * 100 : 0 } }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to index codebase: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Search codebase using semantic search */ const semanticSearchTool = createTool({ name: 'semantic_search', description: 'Search codebase using natural language queries with semantic similarity', category: 'local-ai', readOnly: true, inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Natural language search query', minLength: 1, maxLength: 500 }, topK: { type: 'number', default: 5, description: 'Number of results to return', minimum: 1, maximum: 50 }, threshold: { type: 'number', default: 0.3, description: 'Minimum similarity threshold (0-1)', minimum: 0, maximum: 1 }, filters: { type: 'object', description: 'Additional filters (language, file patterns, etc.)', additionalProperties: true } }, required: ['query'], additionalProperties: false }, async execute(input, context) { try { const searchId = randomUUID(); const now = Date.now(); // Generate query embedding const queryEmbedding = generateQueryEmbedding(input.query); // Build search query with filters let sqlQuery = ` SELECT id, file_path, content, language, features, functions, imports, embedding_data, file_size, created_at FROM ai_embeddings WHERE project_id = ? `; const params = [context.projectId || 'default']; // Apply filters if (input.filters?.language) { sqlQuery += ' AND language = ?'; params.push(input.filters.language); } if (input.filters?.filePattern) { sqlQuery += ' AND file_path LIKE ?'; params.push(`%${input.filters.filePattern}%`); } sqlQuery += ' ORDER BY created_at DESC LIMIT ?'; params.push(input.topK || 5); const embeddings = await context.db.all(sqlQuery, params); if (!embeddings.success || !embeddings.data) { return createSuccessResult({ query: input.query, results: [], message: 'No embeddings found. Try indexing the codebase first with index_codebase' }); } // Calculate similarity scores const results = embeddings.data .map((row) => { const embeddingData = JSON.parse(row.embedding_data); const similarity = calculateSimilarity(queryEmbedding, embeddingData); return { id: row.id, filePath: row.file_path, language: row.language, similarity, features: JSON.parse(row.features || '[]'), functions: JSON.parse(row.functions || '[]'), imports: JSON.parse(row.imports || '[]'), fileSize: row.file_size, preview: generatePreview(row.content, input.query), metadata: { createdAt: row.created_at, language: row.language } }; }) .filter((result) => result.similarity >= (input.threshold || 0.3)) .sort((a, b) => b.similarity - a.similarity) .slice(0, input.topK || 5); // Store search session await context.db.run(`INSERT INTO ai_search_sessions (id, project_id, query, results_found, top_similarity, filters, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, [ searchId, context.projectId || 'default', input.query, results.length, results.length > 0 ? results[0].similarity : 0, JSON.stringify(input.filters || {}), now ]); return createSuccessResult({ searchSession: { id: searchId, query: input.query, resultsFound: results.length, threshold: input.threshold || 0.3 }, results, message: results.length > 0 ? `Found ${results.length} relevant code files` : 'No matching code found for your query', summary: { totalResults: results.length, avgSimilarity: results.length > 0 ? results.reduce((sum, r) => sum + r.similarity, 0) / results.length : 0, languages: [...new Set(results.map((r) => r.language))] } }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to perform semantic search: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Find files with similar code patterns */ const findSimilarCodeTool = createTool({ name: 'find_similar_code', description: 'Find files with similar code patterns to a given file', category: 'local-ai', readOnly: true, inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'Path to file to find similar code for', minLength: 1, maxLength: 500 }, topK: { type: 'number', default: 5, description: 'Number of similar files to return', minimum: 1, maximum: 20 }, threshold: { type: 'number', default: 0.5, description: 'Minimum similarity threshold (0-1)', minimum: 0, maximum: 1 } }, required: ['filePath'], additionalProperties: false }, async execute(input, context) { try { // Get the target file's embedding const targetFile = await context.db.get('SELECT * FROM ai_embeddings WHERE project_id = ? AND file_path = ?', [context.projectId || 'default', input.filePath]); if (!targetFile.success || !targetFile.data) { return createErrorResult({ code: 'NOT_FOUND', message: `File ${input.filePath} not found in embeddings. Try indexing it first.`, category: 'validation' }); } const targetEmbedding = JSON.parse(targetFile.data.embedding_data); // Get all other embeddings const allEmbeddings = await context.db.all('SELECT * FROM ai_embeddings WHERE project_id = ? AND file_path != ?', [context.projectId || 'default', input.filePath]); if (!allEmbeddings.success || !allEmbeddings.data) { return createSuccessResult({ sourceFile: input.filePath, similarFiles: [], message: 'No other files found in the index' }); } // Calculate similarities const similarities = allEmbeddings.data .map((row) => { const embedding = JSON.parse(row.embedding_data); const similarity = calculateSimilarity(targetEmbedding, embedding); return { filePath: row.file_path, language: row.language, similarity, features: JSON.parse(row.features || '[]'), functions: JSON.parse(row.functions || '[]'), sharedFeatures: findSharedFeatures(JSON.parse(targetFile.data.features || '[]'), JSON.parse(row.features || '[]')), fileSize: row.file_size }; }) .filter((result) => result.similarity >= (input.threshold || 0.5)) .sort((a, b) => b.similarity - a.similarity) .slice(0, input.topK || 5); return createSuccessResult({ sourceFile: input.filePath, sourceLanguage: targetFile.data.language, similarFiles: similarities, message: similarities.length > 0 ? `Found ${similarities.length} similar files` : 'No similar files found', analysis: { totalCandidates: allEmbeddings.data.length, belowThreshold: allEmbeddings.data.length - similarities.length, averageSimilarity: similarities.length > 0 ? similarities.reduce((sum, s) => sum + s.similarity, 0) / similarities.length : 0 } }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to find similar code: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Analyze code dependencies and relationships */ const analyzeDependenciesTool = createTool({ name: 'analyze_dependencies', description: 'Analyze code dependencies and relationships', category: 'local-ai', readOnly: true, inputSchema: { type: 'object', properties: { file: { type: 'string', description: 'File to analyze dependencies for', minLength: 1, maxLength: 500 }, depth: { type: 'number', default: 2, description: 'Depth of dependency analysis', minimum: 1, maximum: 5 }, includeTransitive: { type: 'boolean', default: true, description: 'Include transitive dependencies' } }, required: ['file'], additionalProperties: false }, async execute(input, context) { try { const analysisId = randomUUID(); const now = Date.now(); // Get file's dependencies const fileData = await context.db.get('SELECT * FROM ai_embeddings WHERE project_id = ? AND file_path = ?', [context.projectId || 'default', input.file]); if (!fileData.success || !fileData.data) { return createErrorResult({ code: 'NOT_FOUND', message: `File ${input.file} not found in embeddings`, category: 'validation' }); } const imports = JSON.parse(fileData.data.imports || '[]'); const functions = JSON.parse(fileData.data.functions || '[]'); // Analyze dependency relationships const dependencyAnalysis = await analyzeDependencyRelationships(context, input.file, imports, input.depth || 2, input.includeTransitive !== false); // Store analysis await context.db.run(`INSERT INTO ai_dependency_analyses (id, project_id, target_file, depth, dependencies, circular_deps, hotspots, analysis_data, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ analysisId, context.projectId || 'default', input.file, input.depth || 2, JSON.stringify(dependencyAnalysis.dependencies), JSON.stringify(dependencyAnalysis.circularDependencies), JSON.stringify(dependencyAnalysis.hotspots), JSON.stringify(dependencyAnalysis), now ]); return createSuccessResult({ analysis: { id: analysisId, targetFile: input.file, dependencies: dependencyAnalysis.dependencies, circularDependencies: dependencyAnalysis.circularDependencies, hotspots: dependencyAnalysis.hotspots, metrics: dependencyAnalysis.metrics }, message: `Analyzed dependencies for ${input.file}`, insights: { totalDependencies: dependencyAnalysis.dependencies.length, circularCount: dependencyAnalysis.circularDependencies.length, complexityScore: dependencyAnalysis.metrics.complexityScore, recommendations: dependencyAnalysis.recommendations } }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to analyze dependencies: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Track code changes in the semantic index */ const trackCodeChangeTool = createTool({ name: 'track_code_change', description: 'Track code changes in memory for pattern analysis and semantic search', category: 'local-ai', inputSchema: { type: 'object', properties: { file: { type: 'string', description: 'File that changed', minLength: 1, maxLength: 500 }, changeType: { type: 'string', enum: ['create', 'modify', 'delete'], description: 'Type of change' }, content: { type: 'string', description: 'New content (required for create/modify)', maxLength: 1000000 }, metadata: { type: 'object', description: 'Additional metadata about the change', additionalProperties: true } }, required: ['file', 'changeType'], additionalProperties: false }, async execute(input, context) { try { const changeId = randomUUID(); const now = Date.now(); if (input.changeType === 'delete') { // Remove from embeddings await context.db.run('DELETE FROM ai_embeddings WHERE project_id = ? AND file_path = ?', [context.projectId || 'default', input.file]); // Record the deletion await context.db.run(`INSERT INTO ai_code_changes (id, project_id, file_path, change_type, metadata, created_at) VALUES (?, ?, ?, ?, ?, ?)`, [ changeId, context.projectId || 'default', input.file, input.changeType, JSON.stringify(input.metadata || {}), now ]); return createSuccessResult({ changeId, action: 'deleted', file: input.file, message: `Removed ${input.file} from semantic index` }); } if (!input.content) { return createErrorResult({ code: 'VALIDATION_ERROR', message: 'Content is required for create/modify operations', category: 'validation' }); } const language = detectLanguage(input.file); const features = extractCodeFeatures(input.content, language); const functions = extractFunctions(input.content, language); const imports = extractImports(input.content, language); const embeddingData = generateEmbeddingData(input.content, language, features); // Update or insert embedding const existingResult = await context.db.get('SELECT id FROM ai_embeddings WHERE project_id = ? AND file_path = ?', [context.projectId || 'default', input.file]); if (existingResult.success && existingResult.data) { // Update existing await context.db.run(`UPDATE ai_embeddings SET content = ?, language = ?, features = ?, functions = ?, imports = ?, embedding_data = ?, file_size = ?, last_modified = ?, updated_at = ? WHERE project_id = ? AND file_path = ?`, [ input.content, language, JSON.stringify(features), JSON.stringify(functions), JSON.stringify(imports), JSON.stringify(embeddingData), input.content.length, now, now, context.projectId || 'default', input.file ]); } else { // Insert new await context.db.run(`INSERT INTO ai_embeddings (id, project_id, file_path, content, language, features, functions, imports, embedding_data, file_size, last_modified, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), context.projectId || 'default', input.file, input.content, language, JSON.stringify(features), JSON.stringify(functions), JSON.stringify(imports), JSON.stringify(embeddingData), input.content.length, now, now, now ]); } // Record the change await context.db.run(`INSERT INTO ai_code_changes (id, project_id, file_path, change_type, language, features_added, functions_added, metadata, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ changeId, context.projectId || 'default', input.file, input.changeType, language, JSON.stringify(features), JSON.stringify(functions), JSON.stringify(input.metadata || {}), now ]); return createSuccessResult({ changeId, action: input.changeType === 'create' ? 'indexed' : 'updated', file: input.file, language, metrics: { featuresExtracted: features.length, functionsFound: functions.length, importsFound: imports.length, fileSize: input.content.length }, message: `Successfully ${input.changeType === 'create' ? 'indexed' : 'updated'} ${input.file} in semantic search` }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to track code change: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Get embedding statistics and health metrics */ const getEmbeddingStatsTool = createTool({ name: 'get_embedding_stats', description: 'Get statistics about the current embedding index', category: 'local-ai', readOnly: true, inputSchema: { type: 'object', properties: { includeDetails: { type: 'boolean', default: false, description: 'Include detailed breakdown by language and features' } }, additionalProperties: false }, async execute(input, context) { try { // Get basic statistics const totalCount = await context.db.get('SELECT COUNT(*) as count FROM ai_embeddings WHERE project_id = ?', [context.projectId || 'default']); const languageStats = await context.db.all('SELECT language, COUNT(*) as count FROM ai_embeddings WHERE project_id = ? GROUP BY language', [context.projectId || 'default']); const indexingStats = await context.db.get(`SELECT COUNT(*) as sessions, SUM(files_indexed) as total_indexed, AVG(files_indexed) as avg_per_session FROM ai_indexing_sessions WHERE project_id = ?`, [context.projectId || 'default']); const searchStats = await context.db.get(`SELECT COUNT(*) as searches, AVG(results_found) as avg_results, AVG(top_similarity) as avg_similarity FROM ai_search_sessions WHERE project_id = ?`, [context.projectId || 'default']); const recentActivity = await context.db.all(`SELECT change_type, COUNT(*) as count FROM ai_code_changes WHERE project_id = ? AND created_at > ? GROUP BY change_type`, [context.projectId || 'default', Date.now() - (7 * 24 * 60 * 60 * 1000)] // Last 7 days ); const stats = { totalEmbeddings: totalCount.data?.count || 0, languageBreakdown: languageStats.data || [], indexingSessions: indexingStats.data?.sessions || 0, totalFilesIndexed: indexingStats.data?.total_indexed || 0, averageFilesPerSession: indexingStats.data?.avg_per_session || 0, searchSessions: searchStats.data?.searches || 0, averageResultsPerSearch: searchStats.data?.avg_results || 0, averageSearchSimilarity: searchStats.data?.avg_similarity || 0, recentActivity: recentActivity.data || [] }; let details = {}; if (input.includeDetails) { // Get additional detailed statistics const featureStats = await context.db.all(`SELECT language, AVG(JSON_ARRAY_LENGTH(features)) as avg_features, AVG(JSON_ARRAY_LENGTH(functions)) as avg_functions, AVG(file_size) as avg_file_size FROM ai_embeddings WHERE project_id = ? GROUP BY language`, [context.projectId || 'default']); const topFiles = await context.db.all(`SELECT file_path, language, file_size, JSON_ARRAY_LENGTH(features) as feature_count, JSON_ARRAY_LENGTH(functions) as function_count FROM ai_embeddings WHERE project_id = ? ORDER BY file_size DESC LIMIT 10`, [context.projectId || 'default']); details = { featureStatsByLanguage: featureStats.data || [], largestFiles: topFiles.data || [] }; } return createSuccessResult({ statistics: stats, details: input.includeDetails ? details : undefined, healthMetrics: { indexingHealth: stats.totalEmbeddings > 0 ? 'good' : 'needs_indexing', searchUsage: stats.searchSessions > 0 ? 'active' : 'unused', diversityScore: stats.languageBreakdown.length, lastActivity: recentActivity.data && recentActivity.data.length > 0 ? 'recent' : 'stale' }, message: `Index contains ${stats.totalEmbeddings} embeddings across ${stats.languageBreakdown.length} languages` }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to get embedding stats: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); // Helper functions async function findCodeFiles(dir, options) { const files = []; async function walk(currentDir) { try { const entries = await fs.readdir(currentDir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(currentDir, entry.name); // Check exclude patterns const relativePath = path.relative(dir, fullPath); if (options.excludePatterns.some(pattern => relativePath.includes(pattern.replace('/**', '')) || entry.name.startsWith('.'))) { continue; } if (entry.isDirectory() && options.recursive) { await walk(fullPath); } else if (entry.isFile() && options.extensions.some(ext => entry.name.endsWith(ext))) { files.push(fullPath); } } } catch (error) { // Skip directories we can't read } } await walk(dir); return files; } function detectLanguage(filePath) { const ext = path.extname(filePath).toLowerCase(); const languageMap = { '.ts': 'typescript', '.tsx': 'typescript', '.js': 'javascript', '.jsx': 'javascript', '.py': 'python', '.rs': 'rust', '.go': 'go', '.java': 'java', '.cpp': 'cpp', '.c': 'c', '.h': 'c', '.hpp': 'cpp', '.cs': 'csharp', '.php': 'php', '.rb': 'ruby', '.swift': 'swift', '.kt': 'kotlin', '.scala': 'scala', '.clj': 'clojure' }; return languageMap[ext] || 'unknown'; } function extractCodeFeatures(content, language) { const features = []; // Extract based on language if (language === 'typescript' || language === 'javascript') { // Classes const classMatches = content.match(/(?:class|interface)\s+(\w+)/g); if (classMatches) { classMatches.forEach(match => { const name = match.split(/\s+/)[1]; features.push(`class:${name}`); }); } // Types and interfaces const typeMatches = content.match(/(?:type|interface)\s+(\w+)/g); if (typeMatches) { typeMatches.forEach(match => { const name = match.split(/\s+/)[1]; features.push(`type:${name}`); }); } // Async functions if (content.includes('async ')) { features.push('pattern:async'); } // React patterns if (content.includes('useState') || content.includes('useEffect')) { features.push('pattern:react-hooks'); } // Error handling if (content.includes('try') && content.includes('catch')) { features.push('pattern:error-handling'); } } // Common patterns across languages if (content.includes('test') || content.includes('spec')) { features.push('pattern:testing'); } if (content.includes('API') || content.includes('endpoint')) { features.push('pattern:api'); } if (content.includes('database') || content.includes('SQL') || content.includes('query')) { features.push('pattern:database'); } return features; } function extractFunctions(content, language) { const functions = []; if (language === 'typescript' || language === 'javascript') { // Function declarations and expressions const functionRegex = /(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:function|\([^)]*\)\s*=>))/g; let match; while ((match = functionRegex.exec(content)) !== null) { const name = match[1] || match[2]; if (name) functions.push(name); } // Method definitions const methodRegex = /(\w+)\s*\([^)]*\)\s*{/g; while ((match = methodRegex.exec(content)) !== null) { if (!functions.includes(match[1])) { functions.push(match[1]); } } } else if (language === 'python') { const funcRegex = /def\s+(\w+)\s*\(/g; let match; while ((match = funcRegex.exec(content)) !== null) { functions.push(match[1]); } } return functions; } function extractImports(content, language) { const imports = []; if (language === 'typescript' || language === 'javascript') { const importRegex = /import\s+.*\s+from\s+['"]([^'"]+)['"]/g; let match; while ((match = importRegex.exec(content)) !== null) { imports.push(match[1]); } const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g; while ((match = requireRegex.exec(content)) !== null) { imports.push(match[1]); } } else if (language === 'python') { const importRegex = /(?:import\s+(\w+)|from\s+(\w+)\s+import)/g; let match; while ((match = importRegex.exec(content)) !== null) { imports.push(match[1] || match[2]); } } return imports; } function generateEmbeddingData(content, language, features) { // Simple hash-based embedding for local use // In production, this would use a proper embedding model const text = `${language} ${features.join(' ')} ${content}`; const hash = require('crypto').createHash('sha256').update(text).digest('hex'); // Generate a simple vector representation const vector = []; for (let i = 0; i < 128; i++) { const byte = parseInt(hash.substr(i % hash.length, 2), 16); vector.push((byte / 255.0) * 2 - 1); // Normalize to [-1, 1] } return { vector, textHash: hash, dimensions: 128, model: 'local-hash-embedding' }; } function generateQueryEmbedding(query) { return generateEmbeddingData(query, 'query', []); } function calculateSimilarity(embedding1, embedding2) { if (!embedding1.vector || !embedding2.vector) return 0; const v1 = embedding1.vector; const v2 = embedding2.vector; if (v1.length !== v2.length) return 0; // Cosine similarity let dotProduct = 0; let norm1 = 0; let norm2 = 0; for (let i = 0; i < v1.length; i++) { dotProduct += v1[i] * v2[i]; norm1 += v1[i] * v1[i]; norm2 += v2[i] * v2[i]; } if (norm1 === 0 || norm2 === 0) return 0; return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)); } function generatePreview(content, query) { const lines = content.split('\n'); const queryWords = query.toLowerCase().split(/\s+/); // Find lines that contain query words const relevantLines = []; lines.forEach((line, index) => { const lowerLine = line.toLowerCase(); const score = queryWords.reduce((acc, word) => { return acc + (lowerLine.includes(word) ? 1 : 0); }, 0); if (score > 0) { relevantLines.push({ line: line.trim(), index, score }); } }); // Sort by score and take top 3 lines const topLines = relevantLines .sort((a, b) => b.score - a.score) .slice(0, 3) .map(item => `Line ${item.index + 1}: ${item.line}`) .join('\n'); return topLines || lines.slice(0, 3).join('\n'); } function findSharedFeatures(features1, features2) { return features1.filter(f => features2.includes(f)); } async function analyzeDependencyRelationships(context, targetFile, imports, depth, includeTransitive) { // This is a simplified implementation // In practice, this would do deeper analysis of the dependency graph const dependencies = []; const circularDependencies = []; const hotspots = []; // Analyze direct dependencies for (const imp of imports) { // Find files that might correspond to this import const possibleFiles = await context.db.all(`SELECT file_path FROM ai_embeddings WHERE project_id = ? AND file_path LIKE ?`, [context.projectId || 'default', `%${imp}%`]); if (possibleFiles.success && possibleFiles.data) { dependencies.push(...possibleFiles.data.map((f) => f.file_path)); } } // Simple metrics calculation const metrics = { directDependencies: dependencies.length, totalDependencies: dependencies.length, // Would include transitive in full implementation complexityScore: Math.min(dependencies.length * 10, 100), fanOut: dependencies.length, fanIn: 0 // Would calculate reverse dependencies }; const recommendations = []; if (metrics.complexityScore > 70) { recommendations.push('Consider breaking down this file to reduce complexity'); } if (dependencies.length > 10) { recommendations.push('High number of dependencies - consider dependency injection'); } return { dependencies, circularDependencies, hotspots, metrics, recommendations }; } /** * Setup local AI tools */ export async function setupLocalAITools() { return { module: 'local-ai', tools: [ indexCodebaseTool, semanticSearchTool, findSimilarCodeTool, analyzeDependenciesTool, trackCodeChangeTool, getEmbeddingStatsTool ] }; } //# sourceMappingURL=tools.js.map