Skip to main content
Glama
SACLProcessor.ts13.6 kB
import { CodeRepresentation, SACLConfig, ProcessingStats, RetrievalResult, EnhancedRetrievalResult } from '../types/index.js'; import { TextualBiasDetector } from './BiasDetector.js'; import { SemanticAugmenter } from './SemanticAugmenter.js'; import { SACLReranker } from './SACLReranker.js'; import { GraphitiClient, GraphitiConfig } from '../graphiti/GraphitiClient.js'; import { CodeAnalyzer } from '../utils/CodeAnalyzer.js'; /** * SACLProcessor orchestrates the complete SACL pipeline * Implements the full Semantic-Augmented Reranking and Localization framework */ export class SACLProcessor { private config: SACLConfig; private biasDetector: TextualBiasDetector; private semanticAugmenter: SemanticAugmenter; private reranker: SACLReranker; private graphitiClient: GraphitiClient; private codeAnalyzer: CodeAnalyzer; private cache: Map<string, CodeRepresentation>; constructor(config: SACLConfig, graphitiConfig: GraphitiConfig) { this.config = config; this.cache = new Map(); // Initialize SACL components this.biasDetector = new TextualBiasDetector(); this.semanticAugmenter = new SemanticAugmenter(graphitiConfig.openaiApiKey); this.graphitiClient = new GraphitiClient(graphitiConfig); this.reranker = new SACLReranker(this.graphitiClient); // Pass GraphitiClient for enhanced results this.codeAnalyzer = new CodeAnalyzer(); } /** * Initialize the SACL system * No longer performs initial scan - repositories are processed on-demand */ async initialize(): Promise<void> { console.log('Initializing SACL processor...'); try { // Initialize components only - repositories processed on-demand console.log('SACL processor initialized successfully'); console.log('Ready to process repositories on-demand via MCP tools'); } catch (error) { console.error('SACL initialization failed:', error); throw error; } } /** * Process entire repository with specified path and namespace */ async processRepository(repositoryPath: string, namespace: string): Promise<ProcessingStats> { const startTime = Date.now(); const stats: ProcessingStats = { filesProcessed: 0, totalFiles: 0, biasDetected: 0, averageBiasScore: 0, processingTime: 0 }; try { // Get all code files in repository const codeFiles = await this.codeAnalyzer.findCodeFiles(repositoryPath); stats.totalFiles = codeFiles.length; console.log(`Found ${codeFiles.length} code files to process`); let totalBiasScore = 0; // Process each file through SACL pipeline for (const filePath of codeFiles) { try { const processed = await this.processFile(filePath); if (processed) { stats.filesProcessed++; totalBiasScore += processed.biasScore; if (processed.biasScore > this.config.biasThreshold) { stats.biasDetected++; } // Store in knowledge graph with namespace await this.graphitiClient.storeCodeRepresentation(processed, namespace); // Cache if enabled if (this.config.cacheEnabled) { this.cache.set(filePath, processed); } } } catch (error) { console.error(`Failed to process file ${filePath}:`, error); } } stats.averageBiasScore = stats.filesProcessed > 0 ? totalBiasScore / stats.filesProcessed : 0; stats.processingTime = Date.now() - startTime; console.log(`Processing completed: ${stats.filesProcessed}/${stats.totalFiles} files`); console.log(`Average bias score: ${stats.averageBiasScore.toFixed(3)}`); console.log(`High bias files: ${stats.biasDetected}`); return stats; } catch (error) { console.error('Repository processing failed:', error); throw error; } } /** * Process single file through SACL pipeline */ async processFile(filePath: string): Promise<CodeRepresentation | null> { try { console.log(`Processing file: ${filePath}`); // 1. Analyze code structure and extract features const codeRep = await this.codeAnalyzer.analyzeFile(filePath); if (!codeRep) { return null; } // 2. Detect textual bias (Stage 0) const biasScore = await this.biasDetector.detectBias(codeRep); codeRep.biasScore = biasScore; // 3. Semantic augmentation (Stage 1) const augmented = await this.semanticAugmenter.augmentRepresentation(codeRep); // 4. Store relationships in knowledge graph (NEW) if (augmented.relationships) { await this.graphitiClient.storeFileRelationships(filePath, augmented.relationships); console.log(` Relationships stored: ${augmented.relationships.imports.length} imports, ${augmented.relationships.functionCalls.length} calls`); } console.log(` Bias score: ${biasScore.toFixed(3)}`); console.log(` Semantic features: ${augmented.semanticFeatures.behaviorPattern}`); return augmented; } catch (error) { console.error(`Error processing file ${filePath}:`, error); return null; } } /** * Enhanced query with relationship context (NEW - Phase 5) */ async queryCodeWithContext(query: string, maxResults: number, repositoryPath: string, namespace: string): Promise<EnhancedRetrievalResult[]> { const limit = maxResults || this.config.maxResults; try { console.log(`SACL Enhanced Query: "${query}" in namespace: ${namespace}`); // 1. Initial retrieval from knowledge graph const initialResults = await this.graphitiClient.searchCode(query, limit * 2, namespace); if (initialResults.length === 0) { console.log('No initial results found'); return []; } // 2. Enhanced SACL reranking with relationship context (Stage 2+) const enhancedResults = await this.reranker.rerankWithContext(initialResults, query, limit); console.log(`Enhanced reranked ${initialResults.length} → ${enhancedResults.length} results with context`); return enhancedResults; } catch (error) { console.error('Enhanced query failed:', error); throw error; } } /** * Query code using SACL-enhanced retrieval (basic version) */ async queryCode(query: string, maxResults: number, repositoryPath: string, namespace: string): Promise<RetrievalResult[]> { const limit = maxResults || this.config.maxResults; try { console.log(`SACL Query: "${query}" in namespace: ${namespace}`); // 1. Initial retrieval from knowledge graph const initialResults = await this.graphitiClient.searchCode(query, limit * 2, namespace); if (initialResults.length === 0) { console.log('No initial results found'); return []; } // 2. SACL reranking and localization (Stage 2) const rerankedResults = await this.reranker.rerank(initialResults, query, limit); console.log(`Reranked ${initialResults.length} → ${rerankedResults.length} results`); return rerankedResults; } catch (error) { console.error('Query failed:', error); throw error; } } /** * Update single file (explicit agent-controlled update) */ async updateFile(filePath: string, changeType: 'created' | 'modified' | 'deleted'): Promise<{success: boolean, message: string}> { try { console.log(`Explicit update - File ${changeType}: ${filePath}`); // Validate file path is within repository if (!this.validateFilePath(filePath)) { return { success: false, message: `File path ${filePath} is outside repository` }; } switch (changeType) { case 'created': case 'modified': const processed = await this.processFile(filePath); if (processed) { await this.graphitiClient.storeCodeRepresentation(processed); if (this.config.cacheEnabled) { this.cache.set(filePath, processed); } return { success: true, message: `File ${filePath} processed successfully. Bias score: ${processed.biasScore.toFixed(3)}` }; } else { return { success: false, message: `Failed to process file ${filePath}` }; } case 'deleted': // Remove from cache and knowledge graph this.cache.delete(filePath); await this.graphitiClient.deleteFileRelationships(filePath); return { success: true, message: `File ${filePath} removed from cache and knowledge graph` }; default: return { success: false, message: `Unknown change type: ${changeType}` }; } } catch (error) { console.error(`Failed to update file ${filePath}:`, error); return { success: false, message: `Error updating file: ${error}` }; } } /** * Update multiple files in batch (explicit agent-controlled update) */ async updateFiles(files: Array<{filePath: string, changeType: 'created' | 'modified' | 'deleted'}>): Promise<{ totalFiles: number, successfulUpdates: number, failedUpdates: number, results: Array<{filePath: string, success: boolean, message: string}> }> { const results = []; let successfulUpdates = 0; let failedUpdates = 0; console.log(`Batch update: Processing ${files.length} files`); for (const file of files) { const result = await this.updateFile(file.filePath, file.changeType); results.push({ filePath: file.filePath, success: result.success, message: result.message }); if (result.success) { successfulUpdates++; } else { failedUpdates++; } } console.log(`Batch update completed: ${successfulUpdates} successful, ${failedUpdates} failed`); return { totalFiles: files.length, successfulUpdates, failedUpdates, results }; } /** * Validate file path is within repository bounds */ private validateFilePath(filePath: string): boolean { // Basic validation - ensure file is within repository path const normalizedRepoPath = this.config.repositoryPath.replace(/\\/g, '/'); const normalizedFilePath = filePath.replace(/\\/g, '/'); return normalizedFilePath.startsWith(normalizedRepoPath) || normalizedFilePath.startsWith('./') || !normalizedFilePath.startsWith('/'); } /** * Get detailed bias analysis */ async getBiasAnalysis(filePath?: string): Promise<any> { try { const biasMetrics = await this.graphitiClient.getBiasMetrics(filePath); if (filePath && this.cache.has(filePath)) { const code = this.cache.get(filePath)!; const indicators = await this.biasDetector.getBiasIndicators(code); return { ...biasMetrics, fileSpecific: { path: filePath, biasScore: code.biasScore, indicators, semanticFeatures: code.semanticFeatures } }; } return biasMetrics; } catch (error) { console.error('Failed to get bias analysis:', error); throw error; } } /** * Get related components for a file (NEW - Phase 4) */ async getRelatedComponents(filePath: string, maxDepth: number = 3): Promise<any> { try { const relatedComponents = await this.graphitiClient.getRelatedComponents(filePath, { maxDepth }); return { primaryFile: filePath, relatedComponents, traversalDepth: maxDepth, totalRelated: relatedComponents.length }; } catch (error) { console.error(`Failed to get related components for ${filePath}:`, error); throw error; } } /** * Get relationship graph for a file (NEW - Phase 4) */ async getRelationshipGraph(filePath: string, relationshipTypes?: string[]): Promise<any> { try { const traversalResult = await this.graphitiClient.traverseRelationships( filePath, relationshipTypes || ['imports', 'exports', 'calls', 'extends', 'implements'], 3 ); return traversalResult; } catch (error) { console.error(`Failed to get relationship graph for ${filePath}:`, error); throw error; } } /** * Get system statistics */ async getSystemStats(): Promise<any> { try { const graphitiStats = await this.graphitiClient.getStats(); return { ...graphitiStats, config: { namespace: this.config.namespace, biasThreshold: this.config.biasThreshold, cacheEnabled: this.config.cacheEnabled, maxResults: this.config.maxResults }, cache: { size: this.cache.size, enabled: this.config.cacheEnabled } }; } catch (error) { console.error('Failed to get system stats:', error); throw error; } } /** * Cleanup resources */ async cleanup(): Promise<void> { try { // Clear cache this.cache.clear(); console.log('SACL processor cleanup completed'); } catch (error) { console.error('Cleanup failed:', error); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ulasbilgen/sacl'

If you have feedback or need assistance with the MCP directory API, please join our Discord server