@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
995 lines • 40.3 kB
JavaScript
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