sicua
Version:
A tool for analyzing project structure and dependencies
1,123 lines (1,117 loc) • 45.6 kB
JavaScript
;
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, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
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;