UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

1,123 lines (1,117 loc) 45.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ContextualSummariesAnalyzer = void 0; const path = __importStar(require("path")); const BusinessLogicExtractor_1 = require("./extractors/BusinessLogicExtractor"); const ComponentExtractor_1 = require("./extractors/ComponentExtractor"); const FunctionExtractor_1 = require("./extractors/FunctionExtractor"); const ImportExtractor_1 = require("./extractors/ImportExtractor"); const TypeExtractor_1 = require("./extractors/TypeExtractor"); const summaryBuilder_1 = require("./utils/summaryBuilder"); const contextUtils_1 = require("./utils/contextUtils"); const semanticAnalysis_1 = require("./utils/semanticAnalysis"); class ContextualSummariesAnalyzer { constructor(scanResult, srcDir, typeChecker) { this.scanResult = scanResult; this.srcDir = srcDir; this.typeChecker = typeChecker; this.options = this.getDefaultOptions(); this.cache = this.initializeCache(); this.stats = this.initializeStats(); this.summaryBuilder = new summaryBuilder_1.SummaryBuilder(this.options.config); } /** * Main analysis method that orchestrates the entire contextual summaries generation process */ async analyze(options) { // Merge options this.options = { ...this.options, ...options }; this.summaryBuilder = new summaryBuilder_1.SummaryBuilder(this.options.config); const startTime = Date.now(); this.stats.filesAnalyzed = 0; this.stats.errorsEncountered = 0; try { // Initialize analysis this.reportProgress("initialization", "", 0, 0); // Get relevant files for analysis const relevantFiles = this.filterRelevantFiles(); const totalFiles = relevantFiles.length; if (totalFiles === 0) { return this.createEmptyResult(); } // Analyze files const summaries = await this.analyzeFiles(relevantFiles, totalFiles); // Generate project context this.reportProgress("relationship-analysis", "Analyzing project relationships", totalFiles, totalFiles); const projectContext = this.generateProjectContext(summaries); // Generate file relationships const relationships = this.options.generateRelationships ? await this.generateFileRelationships(summaries) : []; // Calculate final statistics const statistics = this.calculateFinalStatistics(summaries); // Generate prompt templates const promptTemplates = this.generatePromptTemplates(summaries); // Finalize this.reportProgress("finalization", "Finalizing analysis", totalFiles, totalFiles); const processingTime = Date.now() - startTime; this.stats.averageProcessingTime = processingTime / Math.max(totalFiles, 1); return { summaries, projectContext, relationships, statistics, promptTemplates, }; } catch (error) { this.handleCriticalError(error); throw error; } } /** * Analyzes files in parallel or sequential based on configuration */ async analyzeFiles(filePaths, totalFiles) { const summaries = []; if (this.options.parallelProcessing) { const batches = this.createBatches(filePaths, this.options.maxConcurrency); for (const batch of batches) { const batchPromises = batch.map((filePath) => this.analyzeFile(filePath, summaries.length, totalFiles)); const batchResults = await Promise.allSettled(batchPromises); batchResults.forEach((result, index) => { if (result.status === "fulfilled" && result.value) { summaries.push(result.value); } else if (result.status === "rejected") { this.handleFileError(batch[index], "semantic-analysis", result.reason); } }); } } else { // Sequential processing for (let i = 0; i < filePaths.length; i++) { try { const summary = await this.analyzeFile(filePaths[i], i, totalFiles); if (summary) { summaries.push(summary); } } catch (error) { this.handleFileError(filePaths[i], "semantic-analysis", error); } } } return summaries; } /** * Analyzes a single file and generates its contextual summary */ async analyzeFile(filePath, currentIndex, totalFiles) { const fileName = path.basename(filePath); try { // Check cache first if (this.isCachedAndValid(filePath)) { const cached = this.cache.cachedSummaries.get(filePath); if (cached) { this.stats.filesAnalyzed++; return cached; } } // Get source file and content const sourceFile = this.scanResult.sourceFiles.get(filePath); const content = this.scanResult.fileContents.get(filePath); if (!sourceFile || !content) { this.handleFileError(filePath, "file-scanning", new Error("Source file or content not found")); return null; } // Determine file type const fileType = (0, contextUtils_1.determineFileContextType)(filePath, content, sourceFile); // Skip non-relevant files if (!this.isRelevantFileType(fileType)) { return null; } // Report progress this.reportProgress("dependency-extraction", fileName, currentIndex, totalFiles); // Extract dependencies const importExtractor = new ImportExtractor_1.ImportExtractor(this.srcDir); const dependencyContext = importExtractor.extractDependencies(sourceFile, filePath); // Report progress this.reportProgress("function-analysis", fileName, currentIndex, totalFiles); // Extract functions const functionExtractor = new FunctionExtractor_1.FunctionExtractor(sourceFile, content); const functionContext = functionExtractor.extractFunctionContext(); // Report progress this.reportProgress("type-analysis", fileName, currentIndex, totalFiles); // Extract types const typeExtractor = new TypeExtractor_1.TypeExtractor(this.typeChecker); const typeContext = typeExtractor.extractTypeContext(sourceFile); // Report progress this.reportProgress("component-analysis", fileName, currentIndex, totalFiles); // Extract components (if applicable) const componentExtractor = new ComponentExtractor_1.ComponentExtractor(sourceFile, content, filePath, this.srcDir, this.typeChecker); const componentContext = componentExtractor.extractComponentContext(); // Report progress this.reportProgress("business-logic-analysis", fileName, currentIndex, totalFiles); // Extract business logic const businessLogicExtractor = new BusinessLogicExtractor_1.BusinessLogicExtractor(sourceFile, content, filePath, this.srcDir, this.typeChecker); const businessLogic = businessLogicExtractor.extractBusinessLogicDefinition(); // Report progress this.reportProgress("semantic-analysis", fileName, currentIndex, totalFiles); // Perform semantic analysis const semanticAnalysis = semanticAnalysis_1.SemanticAnalyzer.analyzeSemantics(fileType, functionContext, componentContext, typeContext, businessLogic, dependencyContext, filePath, content); // Report progress this.reportProgress("summary-generation", fileName, currentIndex, totalFiles); // Generate contextual summary const summary = this.summaryBuilder.buildContextualSummary(filePath, fileType, functionContext, componentContext, typeContext, businessLogic, dependencyContext, semanticAnalysis, this.options.includeMetadata ? this.generateMetadata(filePath) : undefined); // Cache the result this.cacheResult(filePath, summary); // Update statistics this.updateProcessingStats(summary); this.stats.filesAnalyzed++; return summary; } catch (error) { this.handleFileError(filePath, "semantic-analysis", error); return null; } } /** * Generates project-level context from all summaries */ generateProjectContext(summaries) { const architectureTypes = summaries.map((s) => this.inferArchitectureType(s)); const mainArchitecture = this.getMostCommonValue(architectureTypes); const allPatterns = summaries.flatMap((s) => s.usagePatterns.map((p) => p.pattern)); const mainPatterns = this.getTopValues(allPatterns, 10); const techStack = summaries.flatMap((s) => s.dependencies.external.map((dep) => dep.name)); const technicalStack = this.getTopValues(techStack, 15); const complexities = summaries.map((s) => s.complexity); const avgComplexity = this.calculateAverageComplexity(complexities); const modules = this.identifyModules(summaries); return { architecture: { type: this.inferProjectType(summaries), structure: this.inferProjectStructure(summaries), modules, }, mainPatterns, technicalStack, complexity: avgComplexity, }; } /** * Generates relationships between files */ async generateFileRelationships(summaries) { const relationships = []; for (const summary of summaries) { // Import relationships summary.dependencies.internal.forEach((internal) => { const targetSummary = summaries.find((s) => s.filePath.includes(internal.path) || internal.path.includes(s.fileName)); if (targetSummary) { relationships.push({ source: summary.filePath, target: targetSummary.filePath, relationship: "imports", strength: this.calculateRelationshipStrength(internal.relationship), context: `${summary.fileName} imports from ${targetSummary.fileName}`, // Add context }); } }); // Export relationships (reverse lookup) const exportingSummaries = summaries.filter((s) => s.dependencies.internal.some((dep) => dep.path.includes(summary.fileName) || summary.filePath.includes(dep.path))); exportingSummaries.forEach((exportingSummary) => { if (exportingSummary.filePath !== summary.filePath) { relationships.push({ source: summary.filePath, target: exportingSummary.filePath, relationship: "extends", strength: "medium", context: `${summary.fileName} extends functionality from ${exportingSummary.fileName}`, // Add context }); } }); // Similar purpose relationships const similarSummaries = summaries.filter((s) => s.filePath !== summary.filePath && s.purpose .toLowerCase() .includes(summary.purpose.toLowerCase().split(" ")[0])); similarSummaries.slice(0, 3).forEach((similarSummary) => { relationships.push({ source: summary.filePath, target: similarSummary.filePath, relationship: "uses", strength: "weak", context: `${summary.fileName} has similar purpose to ${similarSummary.fileName}`, // Add context }); }); } return this.deduplicateRelationships(relationships); } /** * Calculates final analysis statistics */ calculateFinalStatistics(summaries) { const totalFiles = summaries.length; const complexities = summaries.map((s) => s.complexity); const averageComplexity = this.calculateAverageComplexity(complexities); const tokenStats = summaries.reduce((acc, summary) => ({ original: acc.original + summary.prompt.tokens.originalSize, reduced: acc.reduced + summary.prompt.tokens.approximate, }), { original: 0, reduced: 0 }); const reductionPercentage = tokenStats.original > 0 ? ((tokenStats.original - tokenStats.reduced) / tokenStats.original) * 100 : 0; const averageCompressionRatio = summaries.length > 0 ? summaries.reduce((sum, s) => sum + s.prompt.tokens.compressionRatio, 0) / summaries.length : 1; // Pattern distribution analysis const allPatterns = summaries.flatMap((s) => s.usagePatterns.map((p) => p.pattern)); const patternCounts = allPatterns.reduce((acc, pattern) => { acc[pattern] = (acc[pattern] || 0) + 1; return acc; }, {}); const patternDistribution = Object.entries(patternCounts).reduce((acc, [pattern, count]) => { acc[pattern] = { count, percentage: (count / totalFiles) * 100, }; return acc; }, {}); return { totalFiles, averageComplexity, tokenReduction: { originalTokens: tokenStats.original, reducedTokens: tokenStats.reduced, reductionPercentage, averageCompressionRatio, }, patternDistribution, }; } /** * Generates reusable prompt templates from analysis */ generatePromptTemplates(summaries) { const templates = []; // Group summaries by file type and complexity const groups = this.groupSummariesForTemplates(summaries); Object.entries(groups).forEach(([key, groupSummaries]) => { const [fileType, complexity] = key.split("-"); if (groupSummaries.length >= 3) { // Only create templates with sufficient examples const template = this.createPromptTemplate(fileType, complexity, groupSummaries); templates.push(template); } }); return templates; } /** * Creates a prompt template from a group of similar summaries */ createPromptTemplate(fileType, complexity, summaries) { const commonStructure = this.extractCommonStructure(summaries); const variableFields = this.identifyVariableFields(summaries); return { name: `${fileType}-${complexity}`, purpose: `Template for ${fileType} files with ${complexity} complexity`, template: this.buildTemplateString(commonStructure, variableFields), variables: variableFields, applicableFileTypes: [fileType], }; } // Helper methods for analysis filterRelevantFiles() { return Array.from(this.scanResult.sourceFiles.keys()).filter((filePath) => { const metadata = this.scanResult.fileMetadata.get(filePath); if (!metadata) return false; // Skip test files if not configured to include them if (metadata.isTest && !this.shouldIncludeTestFiles()) { return false; } // Include files with meaningful content return (metadata.hasReactImport || metadata.hasJSX || metadata.hasTranslations || metadata.hasTypeDefinitions || this.hasSignificantContent(filePath)); }); } hasSignificantContent(filePath) { const content = this.scanResult.fileContents.get(filePath); if (!content) return false; // File must have meaningful content (not just imports/exports) const meaningfulLines = content.split("\n").filter((line) => { const trimmed = line.trim(); return (trimmed.length > 0 && !trimmed.startsWith("import ") && !trimmed.startsWith("export ") && !trimmed.startsWith("//") && !trimmed.startsWith("/*") && trimmed !== "{" && trimmed !== "}"); }); return meaningfulLines.length > 5; // At least 5 meaningful lines } shouldIncludeTestFiles() { return (this.options.config.customPatterns?.some((pattern) => pattern.name.toLowerCase().includes("test")) || false); } isRelevantFileType(fileType) { const relevantTypes = [ "react-component", "react-hook", "utility", "type-definition", "api-route", "service", "business-logic", ]; return relevantTypes.includes(fileType); } createBatches(items, batchSize) { const batches = []; for (let i = 0; i < items.length; i += batchSize) { batches.push(items.slice(i, i + batchSize)); } return batches; } reportProgress(stage, currentFile, filesProcessed, totalFiles) { if (this.options.progressCallback) { const progress = { currentFile, filesProcessed, totalFiles, stage, startTime: Date.now(), estimatedCompletion: this.estimateCompletion(filesProcessed, totalFiles), }; this.options.progressCallback(progress); } } estimateCompletion(filesProcessed, totalFiles) { if (filesProcessed === 0 || this.stats.averageProcessingTime === 0) { return undefined; } const remainingFiles = totalFiles - filesProcessed; const estimatedRemainingTime = remainingFiles * this.stats.averageProcessingTime; return Date.now() + estimatedRemainingTime; } handleFileError(filePath, stage, error) { this.stats.errorsEncountered++; const analysisError = { filePath, stage, error, severity: this.determineSeverity(error), recoverable: this.isRecoverableError(error), }; if (this.options.errorCallback) { this.options.errorCallback(analysisError); } } handleCriticalError(error) { const criticalError = { filePath: "unknown", stage: "initialization", error, severity: "critical", recoverable: false, }; if (this.options.errorCallback) { this.options.errorCallback(criticalError); } } determineSeverity(error) { if (error.message.includes("ENOENT") || error.message.includes("not found")) { return "warning"; } if (error.message.includes("syntax") || error.message.includes("parse")) { return "error"; } return "error"; } isRecoverableError(error) { return (!error.message.includes("critical") && !error.message.includes("fatal") && !error.message.includes("out of memory")); } isCachedAndValid(filePath) { // Simple cache validation based on file existence return (this.cache.cachedSummaries.has(filePath) && this.scanResult.fileContents.has(filePath)); } cacheResult(filePath, summary) { this.cache.cachedSummaries.set(filePath, summary); // In a real implementation, you might also cache the file hash } updateProcessingStats(summary) { this.stats.totalTokensGenerated += summary.prompt.tokens.approximate; this.stats.compressionRatio = (this.stats.compressionRatio * this.stats.filesAnalyzed + summary.prompt.tokens.compressionRatio) / (this.stats.filesAnalyzed + 1); } generateMetadata(filePath) { return { fileSize: this.scanResult.fileContents.get(filePath)?.length || 0, lastModified: new Date().toISOString(), projectContext: { name: path.basename(this.srcDir), framework: "React", // Could be detected architecture: "component-based", }, }; } inferArchitectureType(summary) { if (summary.fileType === "react-component") return "component-based"; if (summary.fileType === "api-route") return "layered"; if (summary.fileType === "service") return "service-oriented"; return "modular"; } inferProjectType(summaries) { const hasComponents = summaries.some((s) => s.fileType === "react-component"); const hasApiRoutes = summaries.some((s) => s.fileType === "api-route"); const hasPages = summaries.some((s) => s.purpose.toLowerCase().includes("page")); if (hasComponents && hasApiRoutes) return "mixed"; if (hasComponents && hasPages) return "spa"; if (hasApiRoutes) return "api"; if (hasComponents) return "library"; return "static"; } inferProjectStructure(summaries) { const paths = summaries.map((s) => s.filePath); const hasFeatureFolders = paths.some((p) => p.includes("/features/") || p.includes("/modules/")); const hasLayerFolders = paths.some((p) => p.includes("/components/") && p.includes("/services/") && p.includes("/utils/")); const hasAtomicStructure = paths.some((p) => p.includes("/atoms/") || p.includes("/molecules/") || p.includes("/organisms/")); if (hasFeatureFolders) return "feature-based"; if (hasAtomicStructure) return "atomic"; if (hasLayerFolders) return "layer-based"; return "mixed"; } identifyModules(summaries) { // Group files by directory const moduleGroups = summaries.reduce((groups, summary) => { const dir = path.dirname(summary.filePath); if (!groups[dir]) { groups[dir] = []; } groups[dir].push(summary); return groups; }, {}); return Object.entries(moduleGroups) .filter(([_, files]) => files.length > 1) // Only modules with multiple files .map(([dir, files]) => ({ name: path.basename(dir), purpose: this.inferModulePurpose(files), files: files.map((f) => f.filePath), dependencies: this.extractModuleDependencies(files), })); } inferModulePurpose(files) { const purposes = files.map((f) => f.purpose.toLowerCase()); const commonWords = this.getCommonWords(purposes); return commonWords.join(" ") || "General module"; } extractModuleDependencies(files) { const allDeps = files.flatMap((f) => f.dependencies.external.map((dep) => dep.name)); return [...new Set(allDeps)].slice(0, 10); // Top 10 unique dependencies } calculateRelationshipStrength(relationship) { switch (relationship) { case "parent-child": return "strong"; case "utility-consumer": return "medium"; case "type-provider": return "medium"; case "service-consumer": return "strong"; default: return "weak"; } } deduplicateRelationships(relationships) { const seen = new Set(); return relationships.filter((rel) => { const key = `${rel.source}-${rel.target}-${rel.relationship}`; if (seen.has(key)) return false; seen.add(key); return true; }); } calculateAverageComplexity(complexities) { const scores = complexities.map((c) => { switch (c) { case "very-high": return 5; case "high": return 4; case "medium": return 3; case "low": return 2; default: return 1; } }); const avg = scores.reduce((sum, score) => sum + score, 0) / scores.length; if (avg >= 4.5) return "very-high"; if (avg >= 3.5) return "high"; if (avg >= 2.5) return "medium"; return "low"; } getMostCommonValue(values) { const counts = values.reduce((acc, val) => { acc.set(val, (acc.get(val) || 0) + 1); return acc; }, new Map()); return [...counts.entries()].reduce((a, b) => (a[1] > b[1] ? a : b))[0]; } getTopValues(values, limit) { const counts = values.reduce((acc, val) => { acc.set(val, (acc.get(val) || 0) + 1); return acc; }, new Map()); return [...counts.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, limit) .map(([value]) => value); } getCommonWords(texts) { const wordCounts = new Map(); texts.forEach((text) => { const words = text .toLowerCase() .split(/\s+/) .filter((word) => word.length > 3); // Filter short words words.forEach((word) => { wordCounts.set(word, (wordCounts.get(word) || 0) + 1); }); }); return [...wordCounts.entries()] .filter(([_, count]) => count > 1) // Only words that appear multiple times .sort((a, b) => b[1] - a[1]) .slice(0, 3) .map(([word]) => word); } groupSummariesForTemplates(summaries) { return summaries.reduce((groups, summary) => { const key = `${summary.fileType}-${summary.complexity}`; if (!groups[key]) { groups[key] = []; } groups[key].push(summary); return groups; }, {}); } extractCommonStructure(summaries) { // Extract common structural elements from prompt structures const structures = summaries.map((s) => s.prompt.structure); const commonElements = { header: this.findCommonHeaderPattern(structures), keyPoints: this.findCommonKeyPointPatterns(structures), sections: this.findCommonSections(structures), }; return commonElements; } findCommonHeaderPattern(structures) { const headers = structures.map((s) => s.header).filter((h) => h); if (headers.length === 0) return ""; // Find common prefix/suffix patterns const words = headers.map((h) => h.split(" ")); const commonStartWords = this.findCommonPrefix(words); const commonEndWords = this.findCommonSuffix(words); return [...commonStartWords, "...", ...commonEndWords].join(" "); } findCommonKeyPointPatterns(structures) { const allKeyPoints = structures.flatMap((s) => s.keyPoints || []); const patterns = this.extractPatterns(allKeyPoints); return patterns.slice(0, 5); // Top 5 patterns } findCommonSections(structures) { const allSections = structures.map((s) => Object.keys(s)).flat(); return this.getTopValues(allSections, 8); } findCommonPrefix(wordArrays) { if (wordArrays.length === 0) return []; const minLength = Math.min(...wordArrays.map((arr) => arr.length)); const commonPrefix = []; for (let i = 0; i < minLength; i++) { const word = wordArrays[0][i]; if (wordArrays.every((arr) => arr[i] === word)) { commonPrefix.push(word); } else { break; } } return commonPrefix; } findCommonSuffix(wordArrays) { if (wordArrays.length === 0) return []; const reversedArrays = wordArrays.map((arr) => [...arr].reverse()); const commonSuffix = this.findCommonPrefix(reversedArrays); return commonSuffix.reverse(); } extractPatterns(texts) { // Simple pattern extraction based on common phrases const patterns = new Set(); texts.forEach((text) => { // Extract patterns like "X complexity", "Exports X", etc. const complexityMatch = text.match(/(\w+)\s+complexity/); if (complexityMatch) patterns.add("{complexity} complexity"); const exportsMatch = text.match(/Exports\s+(\d+)/); if (exportsMatch) patterns.add("Exports {count}"); const usesMatch = text.match(/Uses\s+(\d+)/); if (usesMatch) patterns.add("Uses {count}"); }); return Array.from(patterns); } identifyVariableFields(summaries) { const variables = new Set(); summaries.forEach((summary) => { variables.add("fileName"); variables.add("purpose"); variables.add("complexity"); variables.add("fileType"); if (summary.dependencies.external.length > 0) { variables.add("externalDependencies"); } if (summary.businessLogic.operations.length > 0) { variables.add("businessOperations"); } if (summary.technicalContext) { variables.add("technicalDetails"); } }); return Array.from(variables); } buildTemplateString(commonStructure, variables) { const templateParts = []; // Header template if (commonStructure.header) { templateParts.push(commonStructure.header); } // Key points template if (commonStructure.keyPoints && commonStructure.keyPoints.length > 0) { templateParts.push("**Key Points:**"); commonStructure.keyPoints.forEach((pattern) => { templateParts.push(`• ${pattern}`); }); } // Dynamic sections based on variables if (variables.includes("businessOperations")) { templateParts.push("**Business Operations:** {businessOperations}"); } if (variables.includes("technicalDetails")) { templateParts.push("**Technical Details:** {technicalDetails}"); } if (variables.includes("externalDependencies")) { templateParts.push("**Dependencies:** {externalDependencies}"); } return templateParts.join("\n\n"); } createEmptyResult() { return { summaries: [], projectContext: { architecture: { type: "static", structure: "flat", modules: [], }, mainPatterns: [], technicalStack: [], complexity: "low", }, relationships: [], statistics: { totalFiles: 0, averageComplexity: "low", tokenReduction: { originalTokens: 0, reducedTokens: 0, reductionPercentage: 0, averageCompressionRatio: 1, }, patternDistribution: {}, }, promptTemplates: [], }; } getDefaultOptions() { return { config: { maxPromptLength: 1000, includeCodeExamples: false, prioritizeBusinessLogic: true, includePerformanceNotes: true, templatePreference: "detailed", customPatterns: [], }, parallelProcessing: true, maxConcurrency: 4, includeMetadata: true, generateRelationships: true, optimizePrompts: true, }; } initializeCache() { return { fileHashes: new Map(), cachedSummaries: new Map(), relationshipCache: new Map(), lastAnalysisTime: 0, }; } initializeStats() { return { filesAnalyzed: 0, errorsEncountered: 0, averageProcessingTime: 0, totalTokensGenerated: 0, compressionRatio: 1, qualityMetrics: { averagePromptQuality: 0, contextualRelevance: 0, informationDensity: 0, tokenEfficiency: 0, }, }; } /** * Public methods for advanced usage */ /** * Generates a single contextual summary for a specific file */ async analyzeSingleFile(filePath, options) { const mergedOptions = { ...this.options, ...options }; this.options = mergedOptions; return this.analyzeFile(filePath, 0, 1); } /** * Regenerates summaries for files that have changed */ async analyzeChangedFiles(changedFiles, options) { const mergedOptions = { ...this.options, ...options }; this.options = mergedOptions; const summaries = []; for (let i = 0; i < changedFiles.length; i++) { const summary = await this.analyzeFile(changedFiles[i], i, changedFiles.length); if (summary) { summaries.push(summary); } } return summaries; } /** * Updates project context after adding new files */ updateProjectContext(existingSummaries, newSummaries) { const allSummaries = [...existingSummaries, ...newSummaries]; return this.generateProjectContext(allSummaries); } /** * Generates specialized summaries for specific use cases */ async generateSpecializedSummaries(filePaths, personalization, options) { const mergedOptions = { ...this.options, ...options }; this.options = mergedOptions; const summaries = []; for (let i = 0; i < filePaths.length; i++) { const baseSummary = await this.analyzeFile(filePaths[i], i, filePaths.length); if (baseSummary) { // Generate adaptive prompt const adaptivePrompt = this.summaryBuilder.generateAdaptivePrompt(baseSummary, personalization); const specializedSummary = { ...baseSummary, prompt: adaptivePrompt, }; summaries.push(specializedSummary); } } return summaries; } /** * Validates and optimizes existing summaries */ optimizeSummaries(summaries, constraints) { return summaries.map((summary) => { let optimizedPrompt = summary.prompt; // Apply token constraints if specified if (constraints?.maxTokens && summary.prompt.tokens.approximate > constraints.maxTokens) { // Re-generate with constraints optimizedPrompt = this.summaryBuilder.generateAdaptivePrompt(summary, this.getPersonalizationForAudience(constraints.targetAudience || "ai-assistant"), { maxTokens: constraints.maxTokens }); } return { ...summary, prompt: optimizedPrompt, }; }); } /** * Generates quality metrics for summaries */ calculateQualityMetrics(summaries) { if (summaries.length === 0) { return { averagePromptQuality: 0, contextualRelevance: 0, informationDensity: 0, tokenEfficiency: 0, }; } // Calculate average prompt quality based on various factors const qualityScores = summaries.map((summary) => { let score = 0; // Completeness score (has all important sections) if (summary.prompt.structure.header) score += 20; if (summary.prompt.structure.keyPoints.length > 0) score += 20; if (summary.prompt.structure.dependencies) score += 15; if (summary.prompt.structure.exports) score += 15; if (summary.businessLogic.operations.length > 0) score += 15; if (summary.technicalContext) score += 15; return Math.min(100, score); }); const averagePromptQuality = qualityScores.reduce((sum, score) => sum + score, 0) / qualityScores.length; // Calculate contextual relevance (how well the summary matches the file content) const relevanceScores = summaries.map((summary) => { const purposeRelevance = summary.purpose.length > 10 ? 100 : 50; const dependencyRelevance = summary.dependencies.external.length > 0 ? 100 : 80; const complexityRelevance = summary.complexity !== "low" ? 100 : 90; return (purposeRelevance + dependencyRelevance + complexityRelevance) / 3; }); const contextualRelevance = relevanceScores.reduce((sum, score) => sum + score, 0) / relevanceScores.length; // Calculate information density (information per token) const densityScores = summaries.map((summary) => { const infoCount = summary.keyFeatures.length + summary.dependencies.external.length + summary.usagePatterns.length; const tokens = summary.prompt.tokens.approximate; return tokens > 0 ? (infoCount / tokens) * 1000 : 0; // Scale up for readability }); const informationDensity = densityScores.reduce((sum, score) => sum + score, 0) / densityScores.length; // Calculate token efficiency (compression ratio quality) const efficiencyScores = summaries.map((summary) => { const compressionRatio = summary.prompt.tokens.compressionRatio; // Good compression is around 0.6-0.8, perfect efficiency would preserve info while compressing return compressionRatio > 0.3 && compressionRatio < 0.9 ? 100 : 70; }); const tokenEfficiency = efficiencyScores.reduce((sum, score) => sum + score, 0) / efficiencyScores.length; return { averagePromptQuality, contextualRelevance, informationDensity, tokenEfficiency, }; } /** * Exports summaries in various formats */ exportSummaries(summaries, format) { switch (format) { case "json": return JSON.stringify(summaries, null, 2); case "markdown": return this.exportToMarkdown(summaries); case "csv": return this.exportToCSV(summaries); case "xml": return this.exportToXML(summaries); default: return JSON.stringify(summaries, null, 2); } } exportToMarkdown(summaries) { const sections = summaries.map((summary) => { return `# ${summary.fileName} **Purpose:** ${summary.purpose} **Complexity:** ${summary.complexity} **File Type:** ${summary.fileType} ## Key Features ${summary.keyFeatures.map((feature) => `- ${feature}`).join("\n")} ## Dependencies **External:** ${summary.dependencies.external.map((dep) => dep.name).join(", ")} **Internal:** ${summary.dependencies.internal.length} files ## Generated Summary ${summary.prompt.summary} --- `; }); return sections.join("\n"); } exportToCSV(summaries) { const headers = "FilePath,FileName,Purpose,Complexity,FileType,KeyFeatures,ExternalDeps,TokenCount"; const rows = summaries.map((summary) => { const keyFeatures = summary.keyFeatures.join(";"); const externalDeps = summary.dependencies.external .map((dep) => dep.name) .join(";"); return `"${summary.filePath}","${summary.fileName}","${summary.purpose}","${summary.complexity}","${summary.fileType}","${keyFeatures}","${externalDeps}",${summary.prompt.tokens.approximate}`; }); return [headers, ...rows].join("\n"); } exportToXML(summaries) { const xmlSummaries = summaries.map((summary) => { return ` <summary> <filePath>${this.escapeXml(summary.filePath)}</filePath> <fileName>${this.escapeXml(summary.fileName)}</fileName> <purpose>${this.escapeXml(summary.purpose)}</purpose> <complexity>${summary.complexity}</complexity> <fileType>${summary.fileType}</fileType> <keyFeatures> ${summary.keyFeatures .map((feature) => `<feature>${this.escapeXml(feature)}</feature>`) .join("\n ")} </keyFeatures> <dependencies> <external> ${summary.dependencies.external .map((dep) => `<dependency name="${this.escapeXml(dep.name)}" purpose="${dep.purpose}" />`) .join("\n ")} </external> <internal count="${summary.dependencies.internal.length}" /> </dependencies> <tokens> <approximate>${summary.prompt.tokens.approximate}</approximate> <original>${summary.prompt.tokens.originalSize}</original> <compression>${summary.prompt.tokens.compressionRatio}</compression> </tokens> <generatedSummary>${this.escapeXml(summary.prompt.summary)}</generatedSummary> </summary>`; }); return `<?xml version="1.0" encoding="UTF-8"?> <contextualSummaries> ${xmlSummaries.join("\n")} </contextualSummaries>`; } escapeXml(text) { return text .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#39;"); } getPersonalizationForAudience(audience) { const basePersonalization = { targetAudience: "ai-assistant", experienceLevel: "intermediate", context: "general", domain: "fullstack", }; switch (audience) { case "business-analyst": return { ...basePersonalization, targetAudience: "business-analyst", context: "documentation", }; case "developer": return { ...basePersonalization, targetAudience: "developer", context: "code-review", }; case "architect": return { ...basePersonalization, targetAudience: "architect", experienceLevel: "expert", context: "refactoring", }; default: return basePersonalization; } } /** * Gets current analysis statistics */ getAnalysisStatistics() { return { ...this.stats }; } /** * Clears the analysis cache */ clearCache() { this.cache = this.initializeCache(); } /** * Gets cached summaries count */ getCacheSize() { return this.cache.cachedSummaries.size; } } exports.ContextualSummariesAnalyzer = ContextualSummariesAnalyzer;