@aaswe/codebase-ai
Version:
AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs
1,302 lines • 63.9 kB
JavaScript
"use strict";
/**
* Auto Analysis Workflow
*
* Enhanced workflow for automatic project analysis with comprehensive
* codebase understanding, TTL generation, and knowledge graph population.
*/
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 () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AutoAnalysisWorkflow = void 0;
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const events_1 = require("events");
const logger_1 = __importDefault(require("../../utils/logger"));
const ProjectAnalysisService_1 = require("./ProjectAnalysisService");
const RDFService_1 = require("../layer1/rdf-generator/RDFService");
const EnhancedRDFGenerator_1 = require("../layer1/rdf-generator/EnhancedRDFGenerator");
const ModuleKnowledgeManager_1 = require("../layer1/module-knowledge/ModuleKnowledgeManager");
const TypeScriptAnalyzer_1 = require("../layer1/ast-analyzer/TypeScriptAnalyzer");
const PythonAnalyzer_1 = require("../layer1/ast-analyzer/PythonAnalyzer");
const JavaAnalyzer_1 = require("../layer1/ast-analyzer/JavaAnalyzer");
const BusinessContextPreserver_1 = require("./BusinessContextPreserver");
/**
* Auto Analysis Workflow
*
* Provides enhanced automatic analysis with concrete information extraction,
* comprehensive TTL generation, and knowledge graph population.
*/
class AutoAnalysisWorkflow extends events_1.EventEmitter {
config;
projectAnalysisService;
rdfService;
businessContextPreserver;
moduleKnowledgeManager;
analyzers;
isInitialized = false;
constructor(config = {}) {
super();
this.config = {
projectRoot: process.cwd(),
outputDirectory: '.aaswe/knowledge',
enableConcreteExtraction: true,
enableBusinessContextPlaceholders: true,
enableKnowledgeGraphPopulation: true,
enableMCPContextLoading: true,
preserveBusinessContext: true,
analysisDepth: 'comprehensive',
languages: ['typescript', 'javascript', 'python', 'java'],
includePatterns: ['**/*.ts', '**/*.js', '**/*.py', '**/*.java'],
excludePatterns: [
// General build/dependency directories
'**/node_modules/**',
'**/.git/**',
'**/dist/**',
'**/build/**',
'**/out/**',
'**/target/**',
'**/bin/**',
'**/obj/**',
'**/lib/**',
'**/libs/**',
// Python
'**/venv/**',
'**/env/**',
'**/.venv/**',
'**/.env/**',
'**/site-packages/**',
'**/__pycache__/**',
'**/*.pyc',
'**/*.pyo',
'**/*.pyd',
'**/pip-log.txt',
'**/pip-delete-this-directory.txt',
// Java
'**/target/**',
'**/.m2/**',
'**/.gradle/**',
'**/gradle/**',
'**/gradlew*',
'**/*.class',
'**/*.jar',
'**/*.war',
'**/*.ear',
// .NET/C#
'**/bin/**',
'**/obj/**',
'**/packages/**',
'**/*.dll',
'**/*.exe',
'**/*.pdb',
// Go
'**/vendor/**',
'**/*.exe',
// Rust
'**/target/**',
'**/Cargo.lock',
// C/C++
'**/*.o',
'**/*.so',
'**/*.dylib',
'**/*.a',
// IDE and editor files
'**/.vscode/**',
'**/.idea/**',
'**/*.swp',
'**/*.swo',
'**/*~',
// Test and coverage
'**/*.test.*',
'**/*.spec.*',
'**/test/**',
'**/tests/**',
'**/coverage/**',
'**/.nyc_output/**',
// Temporary and cache
'**/tmp/**',
'**/temp/**',
'**/.cache/**',
'**/cache/**',
// OS files
'**/.DS_Store',
'**/Thumbs.db',
// Logs
'**/*.log',
'**/logs/**'
],
...config
};
// Initialize services
this.projectAnalysisService = new ProjectAnalysisService_1.ProjectAnalysisService({
rootPath: this.config.projectRoot,
outputDirectory: this.config.outputDirectory,
languages: this.config.languages,
includePatterns: this.config.includePatterns,
excludePatterns: this.config.excludePatterns,
generateTTL: true,
enableWatching: false, // Controlled by trigger system
preserveBusinessContext: this.config.preserveBusinessContext,
analysisDepth: this.config.analysisDepth
});
this.rdfService = new RDFService_1.RDFService({
includeBusinessContext: this.config.enableBusinessContextPlaceholders,
generatePlaceholders: this.config.enableBusinessContextPlaceholders,
optimizeForLLM: true,
optimizeForNeo4j: this.config.enableKnowledgeGraphPopulation
});
this.moduleKnowledgeManager = new ModuleKnowledgeManager_1.ModuleKnowledgeManager({
preserveBusinessContext: this.config.preserveBusinessContext,
enableConflictResolution: true,
enableLLMPreview: this.config.enableMCPContextLoading
});
// Initialize business context preserver
this.businessContextPreserver = new BusinessContextPreserver_1.BusinessContextPreserver({
preservationEnabled: this.config.preserveBusinessContext,
backupEnabled: true,
backupDirectory: path.join(this.config.projectRoot, '.aaswe', 'backups', 'business-context'),
conflictResolution: 'merge',
maxBackupVersions: 10
});
// Initialize analyzers
this.analyzers = new Map();
this.setupAnalyzers();
}
/**
* Initialize the workflow
*/
async initialize() {
if (this.isInitialized) {
return;
}
try {
logger_1.default.info('Initializing Auto Analysis Workflow', {
projectRoot: this.config.projectRoot,
analysisDepth: this.config.analysisDepth,
enableConcreteExtraction: this.config.enableConcreteExtraction
});
// Initialize all services
await this.projectAnalysisService.initialize();
await this.moduleKnowledgeManager.initialize();
await this.businessContextPreserver.initialize();
// Ensure output directory exists
await fs.mkdir(path.resolve(this.config.projectRoot, this.config.outputDirectory), {
recursive: true
});
this.isInitialized = true;
logger_1.default.info('Auto Analysis Workflow initialized successfully');
}
catch (error) {
logger_1.default.error('Failed to initialize Auto Analysis Workflow', { error });
throw error;
}
}
/**
* Execute comprehensive automatic analysis
*/
async executeComprehensiveAnalysis() {
if (!this.isInitialized) {
await this.initialize();
}
const startTime = Date.now();
logger_1.default.info('Starting comprehensive automatic analysis');
try {
// Phase 1: Standard project analysis
logger_1.default.info('Phase 1: Executing standard project analysis');
const standardResult = await this.projectAnalysisService.analyzeProject();
// Phase 2: Extract concrete information
logger_1.default.info('Phase 2: Extracting concrete information');
const concreteInformation = await this.extractConcreteInformation(standardResult);
// Phase 3: Generate enhanced TTL files
logger_1.default.info('Phase 3: Generating enhanced TTL files');
const ttlResults = await this.generateEnhancedTTLFiles(standardResult, concreteInformation);
// Phase 4: Populate knowledge graph
let knowledgeGraphStats = { nodes: 0, relationships: 0 };
if (this.config.enableKnowledgeGraphPopulation) {
logger_1.default.info('Phase 4: Populating knowledge graph');
try {
// Check if Neo4j is available before attempting population
const neo4jAvailable = await this.checkNeo4jAvailability();
if (neo4jAvailable) {
knowledgeGraphStats = await this.populateKnowledgeGraph(ttlResults);
logger_1.default.info('✅ Knowledge graph populated successfully');
}
else {
logger_1.default.warn('⚠️ Neo4j not available - skipping knowledge graph population');
logger_1.default.info('💡 To enable Neo4j: Set NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD in .env.aaswe');
// Still count entities for statistics from TTL files
knowledgeGraphStats = await this.populateKnowledgeGraph(ttlResults);
}
}
catch (error) {
logger_1.default.error('❌ Knowledge graph population failed', { error });
logger_1.default.info('💡 TTL files generated successfully - knowledge graph population is optional');
// Don't fail the entire workflow for Neo4j issues
}
}
// Phase 5: Load MCP context
let mcpContextSize = 0;
if (this.config.enableMCPContextLoading) {
logger_1.default.info('Phase 5: Loading MCP context');
mcpContextSize = await this.loadMCPContext(ttlResults);
}
// Phase 6: Preserve and restore business context
let preservationResult;
if (this.config.preserveBusinessContext) {
logger_1.default.info('Phase 6: Preserving business context');
preservationResult = await this.businessContextPreserver.preserveBusinessContext(Array.from(ttlResults.keys()));
// Restore business context to the newly generated TTL files
if (preservationResult.success) {
logger_1.default.info('Phase 6b: Restoring business context to TTL files');
const restorationResult = await this.businessContextPreserver.restoreContext(Array.from(ttlResults.keys()));
// Update preservation result with restoration stats
if (restorationResult.success) {
preservationResult.statistics.enhancementsPreserved += restorationResult.statistics.enhancementsPreserved;
preservationResult.conflictsResolved += restorationResult.conflictsResolved;
}
}
}
const endTime = Date.now();
const totalDuration = endTime - startTime;
// Create enhanced result
const enhancedResult = {
...standardResult,
duration: totalDuration,
concreteInformation,
knowledgeGraphNodes: knowledgeGraphStats.nodes,
knowledgeGraphRelationships: knowledgeGraphStats.relationships,
mcpContextSize,
businessContextPreserved: preservationResult?.success || false,
enhancementsPreserved: preservationResult?.preservedEnhancements || 0,
...(preservationResult && { preservationResult }),
summary: {
...standardResult.summary,
ttlFilesGenerated: ttlResults.size
}
};
logger_1.default.info('Comprehensive analysis completed successfully', {
duration: totalDuration,
analyzedFiles: enhancedResult.summary.analyzedFiles,
ttlFilesGenerated: enhancedResult.summary.ttlFilesGenerated,
knowledgeGraphNodes: knowledgeGraphStats.nodes,
mcpContextSize,
businessContextPreserved: preservationResult?.success || false,
enhancementsPreserved: preservationResult?.preservedEnhancements || 0
});
this.emit('analysis_completed', enhancedResult);
return enhancedResult;
}
catch (error) {
logger_1.default.error('Comprehensive analysis failed', { error });
this.emit('analysis_failed', { error });
throw error;
}
}
/**
* Execute incremental analysis for changed files
*/
async executeIncrementalAnalysis(changedFiles) {
if (!this.isInitialized) {
await this.initialize();
}
logger_1.default.info('Starting incremental analysis', {
changedFiles: changedFiles.length
});
try {
// Analyze only changed files
const standardResult = await this.projectAnalysisService.analyzeFiles(changedFiles);
// Extract concrete information for changed files only
const concreteInformation = await this.extractConcreteInformation(standardResult);
// Update TTL files for changed modules
const ttlResults = await this.updateTTLFiles(changedFiles, concreteInformation);
// Update knowledge graph incrementally
let knowledgeGraphStats = { nodes: 0, relationships: 0 };
if (this.config.enableKnowledgeGraphPopulation) {
knowledgeGraphStats = await this.updateKnowledgeGraph(ttlResults);
}
// Update MCP context
let mcpContextSize = 0;
if (this.config.enableMCPContextLoading) {
mcpContextSize = await this.updateMCPContext(ttlResults);
}
// Phase 6: Preserve and restore business context for incremental analysis
let preservationResult;
if (this.config.preserveBusinessContext) {
preservationResult = await this.businessContextPreserver.preserveBusinessContext(Array.from(ttlResults.keys()));
// Restore business context to the updated TTL files
if (preservationResult && preservationResult.success) {
const restorationResult = await this.businessContextPreserver.restoreContext(Array.from(ttlResults.keys()));
// Update preservation result with restoration stats
if (restorationResult.success) {
preservationResult.statistics.enhancementsPreserved += restorationResult.statistics.enhancementsPreserved;
preservationResult.conflictsResolved += restorationResult.conflictsResolved;
}
}
}
const enhancedResult = {
...standardResult,
concreteInformation,
knowledgeGraphNodes: knowledgeGraphStats.nodes,
knowledgeGraphRelationships: knowledgeGraphStats.relationships,
mcpContextSize,
businessContextPreserved: preservationResult?.success || false,
enhancementsPreserved: preservationResult?.preservedEnhancements || 0,
...(preservationResult && { preservationResult })
};
logger_1.default.info('Incremental analysis completed', {
changedFiles: changedFiles.length,
duration: enhancedResult.duration
});
this.emit('incremental_analysis_completed', enhancedResult);
return enhancedResult;
}
catch (error) {
logger_1.default.error('Incremental analysis failed', { error });
throw error;
}
}
// Private methods
setupAnalyzers() {
this.analyzers.set('typescript', new TypeScriptAnalyzer_1.TypeScriptAnalyzer());
this.analyzers.set('javascript', new TypeScriptAnalyzer_1.TypeScriptAnalyzer());
this.analyzers.set('python', new PythonAnalyzer_1.PythonAnalyzer());
this.analyzers.set('java', new JavaAnalyzer_1.JavaAnalyzer());
}
async extractConcreteInformation(analysisResult) {
const concreteInfo = new Map();
if (!this.config.enableConcreteExtraction) {
return concreteInfo;
}
logger_1.default.debug('Extracting concrete information using MultiLanguageCodeGraphAnalyzer');
try {
// Use MultiLanguageCodeGraphAnalyzer for reliable multi-language analysis
const { MultiLanguageCodeGraphAnalyzer } = await Promise.resolve().then(() => __importStar(require('./MultiLanguageCodeGraphAnalyzer')));
const { Neo4jDatabaseService } = await Promise.resolve().then(() => __importStar(require('../layer2/neo4j-database/Neo4jDatabaseService')));
// Create and connect Neo4j service
const neo4jService = new Neo4jDatabaseService();
// Try to connect to Neo4j if credentials are available
const neo4jUri = process.env.NEO4J_URI || 'bolt://localhost:7687';
const neo4jUsername = process.env.NEO4J_USERNAME || 'neo4j';
const neo4jPassword = process.env.NEO4J_PASSWORD || 'aaswe-password';
const config = {
uri: neo4jUri,
username: neo4jUsername,
password: neo4jPassword,
database: 'neo4j',
encrypted: false
};
await neo4jService.connect(config);
const analyzer = new MultiLanguageCodeGraphAnalyzer(neo4jService);
// Analyze the project using the enhanced analyzer
const analysisResult = await analyzer.analyzeMultiLanguageCodebase(this.config.projectRoot);
// Disconnect after analysis
await neo4jService.disconnect();
// Convert MultiLanguageCodeGraphAnalyzer results to ConcreteInformation format
for (const entity of analysisResult.entities) {
if (entity.type === 'File') {
const filePath = entity.filePath;
// Get all entities for this file
const fileEntities = analysisResult.entities.filter(e => e.filePath === filePath);
const classes = fileEntities.filter(e => e.type === 'Class');
const methods = fileEntities.filter(e => e.type === 'Method' || e.type === 'Function');
const concrete = {
actualClassNames: classes.map(c => c.name),
actualMethodSignatures: methods.map(m => `${m.name}()`), // Simplified signature
actualDependencies: [], // Would need relationship analysis
actualExports: classes.filter(c => c.metadata?.visibility === 'public').map(c => c.name),
actualImports: [], // Would need import analysis
architecturalPatterns: this.detectArchitecturalPatternsFromNames([...classes.map(c => c.name), ...methods.map(m => m.name)]),
businessDomainIndicators: this.detectBusinessDomainFromNames([...classes.map(c => c.name), ...methods.map(m => m.name)]),
qualityMetrics: {
complexity: entity.metadata?.complexity || 1,
maintainability: 80, // Default value
testCoverage: 0,
documentation: 0
}
};
concreteInfo.set(filePath, concrete);
}
}
logger_1.default.info('✅ Concrete information extracted using MultiLanguageCodeGraphAnalyzer', {
filesProcessed: concreteInfo.size,
totalEntities: analysisResult.entities.length,
languages: analysisResult.languages.join(', ')
});
}
catch (error) {
logger_1.default.warn('⚠️ MultiLanguageCodeGraphAnalyzer failed, falling back to old analyzers', { error });
// Fallback to old analyzer approach
for (const fileResult of analysisResult.files) {
if (fileResult.status !== 'success') {
continue;
}
try {
const analyzer = this.analyzers.get(fileResult.language);
if (!analyzer) {
continue;
}
const detailedAnalysis = await analyzer.analyzeFile(fileResult.filePath);
const concrete = await this.buildConcreteInformation(detailedAnalysis);
concreteInfo.set(fileResult.filePath, concrete);
}
catch (error) {
logger_1.default.warn('Failed to extract concrete information', {
filePath: fileResult.filePath,
error
});
}
}
}
logger_1.default.debug('Concrete information extraction completed', {
filesProcessed: concreteInfo.size
});
return concreteInfo;
}
async buildConcreteInformation(analysis) {
return {
actualClassNames: analysis.classes.map(cls => cls.name),
actualMethodSignatures: [
...analysis.classes.flatMap(cls => cls.methods.map(method => `${cls.name}.${method.name}(${method.parameters.map(p => p.name).join(', ')})`)),
...analysis.functions.map(func => `${func.name}(${func.parameters.map(p => p.name).join(', ')})`)
],
actualDependencies: analysis.dependencies,
actualExports: analysis.exports.map(exp => exp.name),
actualImports: analysis.imports.map(imp => imp.source),
architecturalPatterns: this.detectArchitecturalPatterns(analysis),
businessDomainIndicators: this.detectBusinessDomainIndicators(analysis),
qualityMetrics: {
complexity: analysis.complexity.cyclomaticComplexity,
maintainability: analysis.complexity.maintainabilityIndex,
testCoverage: 0, // Would need test analysis
documentation: this.calculateDocumentationScore(analysis)
}
};
}
detectArchitecturalPatterns(analysis) {
const patterns = [];
// Detect common patterns based on class and method names
const classNames = analysis.classes.map(cls => cls.name.toLowerCase());
const methodNames = analysis.functions.map(func => func.name.toLowerCase());
if (classNames.some(name => name.includes('factory'))) {
patterns.push('Factory Pattern');
}
if (classNames.some(name => name.includes('singleton'))) {
patterns.push('Singleton Pattern');
}
if (classNames.some(name => name.includes('observer'))) {
patterns.push('Observer Pattern');
}
if (classNames.some(name => name.includes('builder'))) {
patterns.push('Builder Pattern');
}
if (methodNames.some(name => name.includes('middleware'))) {
patterns.push('Middleware Pattern');
}
if (analysis.classes.some(cls => cls.methods.some(m => m.name === 'execute'))) {
patterns.push('Command Pattern');
}
return patterns;
}
detectBusinessDomainIndicators(analysis) {
const indicators = [];
// Analyze class and method names for business domain clues
const allNames = [
...analysis.classes.map(cls => cls.name),
...analysis.functions.map(func => func.name),
...analysis.classes.flatMap(cls => cls.methods.map(m => m.name))
].map(name => name.toLowerCase());
const domainKeywords = {
'E-commerce': ['order', 'cart', 'payment', 'product', 'customer', 'checkout'],
'Finance': ['account', 'transaction', 'balance', 'payment', 'invoice', 'billing'],
'Healthcare': ['patient', 'medical', 'diagnosis', 'treatment', 'doctor', 'appointment'],
'Education': ['student', 'course', 'grade', 'assignment', 'teacher', 'class'],
'HR': ['employee', 'payroll', 'attendance', 'performance', 'recruitment'],
'CRM': ['lead', 'contact', 'opportunity', 'pipeline', 'sales'],
'Content Management': ['article', 'post', 'content', 'publish', 'editor'],
'Authentication': ['user', 'login', 'auth', 'token', 'session', 'permission']
};
for (const [domain, keywords] of Object.entries(domainKeywords)) {
const matches = keywords.filter(keyword => allNames.some(name => name.includes(keyword)));
if (matches.length >= 2) {
indicators.push(domain);
}
}
return indicators;
}
calculateDocumentationScore(analysis) {
const totalElements = analysis.classes.length + analysis.functions.length;
if (totalElements === 0)
return 0;
// Since documentation property doesn't exist in the types,
// we'll use a heuristic based on naming conventions and comments
const documentedElements = [
...analysis.classes.filter(cls => cls.name.length > 3 && // Meaningful names
cls.methods.some(method => method.name.startsWith('get') || method.name.startsWith('set'))),
...analysis.functions.filter(func => func.name.length > 3 && // Meaningful names
func.parameters.length > 0 // Has parameters (likely documented)
)
].length;
return Math.round((documentedElements / totalElements) * 100);
}
async generateEnhancedTTLFiles(analysisResult, concreteInfo) {
const ttlResults = new Map();
const enhancedRdfGenerator = new EnhancedRDFGenerator_1.EnhancedRDFGenerator({
includeBusinessContext: this.config.enableBusinessContextPlaceholders,
optimizeForLLM: this.config.enableMCPContextLoading,
optimizeForNeo4j: this.config.enableKnowledgeGraphPopulation
});
// Group files by module/directory for TTL generation
const moduleGroups = this.groupFilesByModule(analysisResult.files);
for (const [modulePath, files] of moduleGroups) {
try {
// Create combined analysis result for the module
const moduleAnalysis = await this.createModuleAnalysis(files, concreteInfo);
// Generate enhanced RDF with concrete information
const rdfResult = await enhancedRdfGenerator.generateEnhancedRDF(moduleAnalysis, modulePath);
// Write TTL file to the specified output directory
await this.writeTTLToOutputDirectory(modulePath, rdfResult);
ttlResults.set(modulePath, rdfResult);
logger_1.default.info('Enhanced TTL generated for module', {
modulePath,
size: rdfResult.size,
classCount: rdfResult.statistics.classCount,
methodCount: rdfResult.statistics.methodCount
});
}
catch (error) {
logger_1.default.error('Failed to generate enhanced TTL for module', { modulePath, error });
}
}
return ttlResults;
}
groupFilesByModule(files) {
const groups = new Map();
for (const file of files) {
const moduleDir = path.dirname(file.filePath);
const ttlPath = path.join(moduleDir, '.module-knowledge.ttl');
if (!groups.has(ttlPath)) {
groups.set(ttlPath, []);
}
groups.get(ttlPath).push(file);
}
return groups;
}
async createModuleAnalysis(files, concreteInfo) {
// Get the module directory path
const fullModuleDir = path.dirname(files[0]?.filePath || '');
const moduleName = path.basename(fullModuleDir);
// Convert to relative path for Neo4j query since Neo4j stores relative paths
// Remove the project root to get the relative path that matches Neo4j storage
const relativePath = path.relative(this.config.projectRoot, fullModuleDir);
const moduleDir = relativePath.replace(/\\/g, '/'); // Normalize path separators for cross-platform compatibility
logger_1.default.info('Creating module analysis with Neo4j data', {
fullModuleDir,
moduleDir,
moduleName,
filesCount: files.length,
projectRoot: this.config.projectRoot,
pathConversion: `${fullModuleDir} -> ${moduleDir}`
});
// Initialize combined analysis
const combinedAnalysis = {
filePath: fullModuleDir,
language: files[0]?.language || 'java',
nodes: [],
functions: [],
classes: [],
imports: [],
exports: [],
dependencies: [],
complexity: {
cyclomaticComplexity: 0,
cognitiveComplexity: 0,
linesOfCode: 0,
maintainabilityIndex: 100,
technicalDebt: 0
},
errors: [],
timestamp: new Date()
};
try {
// Query Neo4j directly for concrete information about all files in this module
const { Neo4jDatabaseService } = await Promise.resolve().then(() => __importStar(require('../layer2/neo4j-database/Neo4jDatabaseService')));
const neo4jService = new Neo4jDatabaseService();
const config = {
uri: process.env.NEO4J_URI || 'bolt://localhost:7687',
username: process.env.NEO4J_USERNAME || 'neo4j',
password: process.env.NEO4J_PASSWORD || 'aaswe-password',
database: 'neo4j',
encrypted: false
};
await neo4jService.connect(config);
// Query for all entities in this module directory
// Handle both absolute and relative paths by using CONTAINS instead of STARTS WITH
const moduleQuery = `
MATCH (f:File)-[:CONTAINS]->(entity)
WHERE f.filePath CONTAINS $moduleDir OR f.filePath STARTS WITH $moduleDir
RETURN f.filePath as filePath, f.name as fileName,
collect({
type: labels(entity)[0],
name: entity.name,
id: entity.id,
startLine: entity.startLine,
endLine: entity.endLine,
complexity: entity.complexity,
visibility: entity.visibility,
isExported: entity.isExported,
parameters: entity.parameters,
returnType: entity.returnType
}) as entities
`;
const session = neo4jService.getSession();
logger_1.default.info('🔍 Neo4j Query Debug Info', {
query: moduleQuery.replace(/\s+/g, ' ').trim(),
moduleDir: moduleDir,
queryParams: { moduleDir },
moduleDirLength: moduleDir.length
});
const result = await session.run(moduleQuery, { moduleDir });
logger_1.default.info('📊 Neo4j Query Results', {
recordCount: result.records.length,
moduleDir: moduleDir,
queryExecuted: moduleQuery.replace(/\s+/g, ' ').trim(),
sampleRecords: result.records.slice(0, 3).map(record => ({
filePath: record.get('filePath'),
fileName: record.get('fileName'),
entitiesCount: record.get('entities')?.length || 0,
firstEntity: record.get('entities')?.[0] || null
}))
});
// If no records found, let's debug what's actually in Neo4j
if (result.records.length === 0) {
logger_1.default.warn('No records found for module, debugging Neo4j content', { moduleDir });
// Query to see what files are actually in Neo4j
const debugQuery = `
MATCH (f:File)
WHERE f.filePath CONTAINS "properties"
RETURN f.filePath as filePath, f.name as fileName
LIMIT 10
`;
const debugResult = await session.run(debugQuery);
const propertiesFiles = debugResult.records.map(record => ({
filePath: record.get('filePath'),
fileName: record.get('fileName')
}));
logger_1.default.info('🔍 Files in Neo4j containing "properties"', {
query: debugQuery.replace(/\s+/g, ' ').trim(),
foundFilesCount: propertiesFiles.length,
foundFiles: propertiesFiles
});
// Additional debug: Show what the moduleDir looks like vs actual stored paths
const allFilesQuery = `
MATCH (f:File)
RETURN f.filePath as filePath
LIMIT 5
`;
const allFilesResult = await session.run(allFilesQuery);
const samplePaths = allFilesResult.records.map(record => record.get('filePath'));
logger_1.default.info('🔍 Sample file paths in Neo4j vs moduleDir', {
moduleDir: moduleDir,
moduleDir_length: moduleDir.length,
sampleStoredPaths: samplePaths,
pathComparison: samplePaths.map(path => ({
storedPath: path,
startsWithModuleDir: path.startsWith(moduleDir),
pathLength: path.length,
moduleDir: moduleDir
}))
});
}
let totalClasses = 0;
let totalMethods = 0;
let totalComplexity = 0;
let totalLines = 0;
// Process each file's entities
for (const record of result.records) {
const filePath = record.get('filePath');
const entities = record.get('entities');
totalLines += 50; // Estimate lines per file
for (const entity of entities) {
if (entity.type === 'Class') {
totalClasses++;
const classId = `class_${entity.name}_${entity.id}`;
// Query for methods in this class
const methodQuery = `
MATCH (c:Class {id: $classId})-[:HAS_METHOD]->(m:Method)
RETURN m.name as name, m.id as id, m.parameters as parameters,
m.returnType as returnType, m.complexity as complexity,
m.startLine as startLine, m.endLine as endLine,
m.visibility as visibility, m.isExported as isExported
`;
const methodResult = await session.run(methodQuery, { classId: entity.id });
const methods = methodResult.records.map(methodRecord => {
totalMethods++;
totalComplexity += methodRecord.get('complexity') || 1;
return {
id: `method_${methodRecord.get('name')}_${methodRecord.get('id')}`,
name: methodRecord.get('name'),
parameters: JSON.parse(methodRecord.get('parameters') || '[]'),
returnType: methodRecord.get('returnType') || 'void',
complexity: methodRecord.get('complexity') || 1,
startLine: methodRecord.get('startLine') || 1,
endLine: methodRecord.get('endLine') || 1,
filePath: filePath,
isAsync: false,
isExported: methodRecord.get('isExported') || false,
visibility: methodRecord.get('visibility') || 'public',
dependencies: [],
calls: []
};
});
combinedAnalysis.classes.push({
id: classId,
name: entity.name,
methods: methods,
properties: [],
extends: undefined,
implements: [],
startLine: entity.startLine || 1,
endLine: entity.endLine || 1,
filePath: filePath,
isExported: entity.isExported || false,
visibility: entity.visibility || 'public',
isAbstract: false
});
}
if (entity.type === 'Method' || entity.type === 'Function') {
totalMethods++;
totalComplexity += entity.complexity || 1;
combinedAnalysis.functions.push({
id: `func_${entity.name}_${entity.id}`,
name: entity.name,
parameters: JSON.parse(entity.parameters || '[]'),
returnType: entity.returnType || 'void',
complexity: entity.complexity || 1,
startLine: entity.startLine || 1,
endLine: entity.endLine || 1,
filePath: filePath,
isAsync: false,
isExported: entity.isExported || false,
visibility: entity.visibility || 'public',
dependencies: [],
calls: []
});
}
}
}
// Query for dependencies and imports
const dependencyQuery = `
MATCH (f:File)-[:IMPORTS]->(dep)
WHERE f.filePath CONTAINS $moduleDir OR f.filePath STARTS WITH $moduleDir
RETURN collect(DISTINCT dep.name) as dependencies
`;
const depResult = await session.run(dependencyQuery, { moduleDir });
if (depResult.records.length > 0) {
const deps = depResult.records[0].get('dependencies') || [];
combinedAnalysis.dependencies = deps;
// Create import declarations
for (const dep of deps) {
combinedAnalysis.imports.push({
id: `import_${dep}_${Math.random().toString(36).substr(2, 9)}`,
source: dep,
imports: [],
filePath: moduleDir,
startLine: 1,
endLine: 1
});
}
}
// Update complexity metrics
combinedAnalysis.complexity.cyclomaticComplexity = totalComplexity;
combinedAnalysis.complexity.linesOfCode = totalLines;
combinedAnalysis.complexity.maintainabilityIndex = Math.max(20, 100 - (totalComplexity / totalMethods || 1) * 10);
await session.close();
await neo4jService.disconnect();
logger_1.default.info('✅ Module analysis created with concrete Neo4j data', {
moduleDir,
filesCount: files.length,
classesCount: totalClasses,
methodsCount: totalMethods,
dependenciesCount: combinedAnalysis.dependencies.length,
totalComplexity,
totalLines
});
}
catch (error) {
logger_1.default.warn('⚠️ Failed to query Neo4j for concrete data, falling back to file-based analysis', { error });
// Fallback to the original approach if Neo4j is not available
for (const file of files) {
const concrete = concreteInfo.get(file.filePath);
if (concrete) {
// Use the concrete information from the map
for (const className of concrete.actualClassNames) {
const classId = `class_${className}_${Math.random().toString(36).substr(2, 9)}`;
combinedAnalysis.classes.push({
id: classId,
name: className,
methods: [],
properties: [],
extends: undefined,
implements: [],
startLine: 1,
endLine: 1,
filePath: file.filePath,
isExported: concrete.actualExports.includes(className),
visibility: 'public',
isAbstract: false
});
}
combinedAnalysis.dependencies.push(...concrete.actualDependencies);
combinedAnalysis.complexity.cyclomaticComplexity += concrete.qualityMetrics.complexity;
}
}
}
return combinedAnalysis;
}
async populateKnowledgeGraph(ttlResults) {
let totalNodes = 0;
let totalRelationships = 0;
// Check if Neo4j is available before attempting population
const neo4jAvailable = await this.checkNeo4jAvailability();
if (!neo4jAvailable) {
logger_1.default.info('Neo4j not available, skipping knowledge graph population');
logger_1.default.info('TTL files generated successfully and can be used by MCP servers');
// Still count entities for statistics
for (const [ttlPath, result] of ttlResults) {
try {
const content = result.rdfContent;
const classMatches = content.match(/aide:Class/g) || [];
const methodMatches = content.match(/aide:Method/g) || [];
const functionMatches = content.match(/aide:Function/g) || [];
const dependencyMatches = content.match(/aide:dependsOn/g) || [];
const extendsMatches = content.match(/aide:extends/g) || [];
const nodes = classMatches.length + methodMatches.length + functionMatches.length;
const relationships = dependencyMatches.length + extendsMatches.length;
totalNodes += nodes;
totalRelationships += relationships;
logger_1.default.debug('Counted entities in TTL', { ttlPath, nodes, relationships });
}
catch (error) {
logger_1.default.warn('Failed to count entities in TTL', { ttlPath, error });
}
}
return { nodes: totalNodes, relationships: totalRelationships };
}
// Neo4j is available, use MultiLanguageCodeGraphAnalyzer for actual source code analysis
try {
logger_1.default.info('🔍 Using MultiLanguageCodeGraphAnalyzer for comprehensive source code analysis');
const { Neo4jDatabaseService } = await Promise.resolve().then(() => __importStar(require('../layer2/neo4j-database/Neo4jDatabaseService')));
const { MultiLanguageCodeGraphAnalyzer } = await Promise.resolve().then(() => __importStar(require('./MultiLanguageCodeGraphAnalyzer')));
const neo4jService = new Neo4jDatabaseService();
const config = {
uri: process.env.NEO4J_URI,
username: process.env.NEO4J_USERNAME,
password: process.env.NEO4J_PASSWORD,
database: 'neo4j',
encrypted: false
};
await neo4jService.connect(config);
const analyzer = new MultiLanguageCodeGraphAnalyzer(neo4jService);
// Analyze the entire project with multi-language support
logger_1.default.info('🚀 Starting multi-language codebase analysis...');
const analysisResult = await analyzer.analyzeMultiLanguageCodebase(this.config.projectRoot);
totalNodes = analysisResult.entities.length;
totalRelationships = analysisResult.relationships.length;
logger_1.default.info('✅ Multi-language analysis completed successfully', {
languages: analysisResult.languages.join(', '),
totalFiles: analysisResult.statistics.totalFiles,
totalClasses: analysisResult.statistics.totalClasses,
totalMethods: analysisResult.statistics.totalMethods,
totalLines: analysisResult.statistics.totalLines,
totalNodes,
totalRelationships
});
await neo4jService.disconnect();
}
catch (error) {
logger_1.default.error('❌ Multi-language analysis failed, falling back to TTL-based population', { error });
// Fallback to TTL-based population
for (const [ttlPath, result] of ttlResults) {
try {
const content = result.rdfContent;
const classMatches = content.match(/aide:Class/g) || [];
const methodMatches = content.match(/aide:Method/g) || [];
const functionMatches = content.match(/aide:Function/g) || [];
const dependencyMatches = content.match(/aide:dependsOn/g) || [];
const extendsMatches = content.match(/aide:extends/g) || [];
const nodes = classMatches.length + methodMatches.length + functionMatches.length;
const relationships = dependencyMatches.length + extendsMatches.length;
totalNodes += nodes;
totalRelationships += relationships;
// Create Neo4j nodes and relationships
await this.createKnowledgeGraphEntities(ttlPath, content, nodes, relationships);
}
catch (error) {
logger_1.default.error('Failed to populate knowledge graph for TTL', { ttlPath, error });
}
}
}
logger_1.default.info('Knowledge graph population completed', {
totalNodes,
totalRelationships,
ttlFiles: ttlResults.size
});
return { nodes: totalNodes, relationships: totalRelationships };
}
async loadMCPContext(ttlResults) {
let totalSize = 0;
for (const [ttlPath, result] of ttlResults) {
try {
// Format content for MCP consumption
const mcpContext = {
moduleId: path.basename(ttlPath, '.module-knowledge.ttl'),
filePath: ttlPath,
content: result.rdfContent,
timestamp: new Date().toISOString(),
size: result.size,
businessContext: this.extractBusinessContextFromTTL(result.rdfContent),
technicalSummary: this.extractTechnicalSummaryFromTTL(result.rdfContent)
};
// Store in MCP context cache
await this.storeMCPContext(ttlPath, mcpContext);
totalSize += result.size;
}
catch (error) {
logger_1.default.error('Failed to load MCP context', { ttlPath, error });
}
}
logger_1.default.info('MCP context loading completed', {
totalSize,
ttlFiles: ttlResults.size
});
return totalSize;
}
async updateTTLFiles(changedFiles, concreteInfo) {
const ttlResults = new Map();
for (const filePath of changedFiles) {
try {
const ttlPath = path.join(path.dirname(filePath), '.module-knowledge.ttl');
const concrete = concreteInfo.get(filePath);
if (concrete) {
// Create analysis result for RDF generation
const analysisResult = {
filePath,
language: this.getLanguageFromFile(filePath),
nodes: [],
functions: [],
classes: [],
imports: [],
exports: [],
dependencies: concrete.actualDependencies,
complexity: {
cyclomaticComplexity: concrete.qualityMetrics.complexity,
cognitiveComplexity: concrete.qualityMetrics.complexity,
linesOfCode: 100,
maintainabilityIndex: concrete.qualityMetrics.maintainability,
technicalDebt: 0
},
errors: [],
timestamp: new Date()
};
// Update TTL file with preservation of business context
const rdfResult = await this.rdfService.updateTTLFile(ttlPath, analysisResult, true);
ttlResults.set(ttlPath, rdfResult);
logger_1.default.debug('Updated TTL file', { filePath, ttlPath });
}
}
catch (error) {
logger_1.default.error('Failed to update TTL file', { filePath, error });
}
}
return ttlResults;
}
async updateKnowledgeGraph(ttlResults) {
let totalNodes = 0;
let totalRelationships = 0;
for (const [ttlPath, result] of ttlResults) {
try {
// Parse TTL content and extract entities for incremental update
const content = result.rdfContent;
const moduleId = this.extractModuleIdFromTTL(content);
// Count entities
const classMatches = content.match(/aide:Class/g) || [];
const methodMatches = content.match(/aide:Method/g) || [];
const dependencyMatches = content.match(/aide:dependsOn/g) || [];
const nodes = classMatches.length + methodMatches.length;
const relationships = dependencyMatches.length;
totalNodes += nodes;
totalRelationships += relationships;
// Update specific module in knowledge graph
await this.updateModuleInKnowledgeGraph(moduleId, content);
}
catch (error) {
logger_1.default.error('Failed to update knowledge graph incrementally', { ttlPath, error });
}
}
return { nodes: totalNodes, relationships: totalRelationships };
}
async updateMCPContext(ttlResults) {
let totalSize = 0;
for (const [ttlPath, result] of ttlResults) {
try {
const moduleId = this.extractModuleIdFromTTL(result.rdfContent);
// Update MCP context for specific module
const mcpContext = {
moduleId,
filePath: ttlPath,
content: result.rdfContent,
timestamp: new Date().toISOString(),
size: result.size,
lastUpdated: new Date().toISOString()
};
await this.updateMCPContextForModule(moduleId, mcpContext);
totalSize += result.size;
}
catch (error) {
logger_1.default.error('Failed to update MCP context incrementally', { ttlPath, error });
}
}
return totalSize;
}
// Helper methods for the implemented functionality
/**
* Check if Neo4j is available for knowledge graph operations
*/
async checkNeo4jAvailability() {
try {
// Check if Neo4j environment variables are set
const neo4jUri = process.env.NEO4J_URI;
const neo4jUsername = process.env.NEO4J_USERNAME;
const neo4jPassword = process.env.NEO4J_PASSWORD;
if (!neo4jUri || !neo4jUsername || !neo4jPassword) {
logger_1.default.debug('Neo4j credentials not configured');
return false;
}
// Try to connect to Neo4j directly
const { Neo4jDatabaseService } = await Promise.resolve().then(() => __importStar(require('../layer2/neo4j-database/Neo4jDatabaseService')));
const neo4jService = new Neo4jDatabaseService();
const config = {
uri: neo4jUri,
username: neo4jUsername,
password: neo4jPassword,
database: 'neo4j'
};
await neo4jService.connect(config);
const isHealthy = await neo4jService.testConnection();
await neo4jService.disconnect();
return isHealthy;
}
catch (error) {
logger_1.default.debug('Neo4j availability check failed', { error: error instanceof Error ? error.message : 'Unknown error' });
return false;
}
}
async createKnowledgeGraphEntities(_ttlPath, content, nodes, relationships) {
const moduleId = this.extractModuleIdFromTTL(content);
// Create module node in Neo4j
const moduleQuery = `
MERGE (m:Module {id: $moduleId})
SET m.filePath = $filePath,
m.lastUpdated = datetime(),
m.nodeCount = $nodes,
m.relationshipCount = $relationships
`;
logger_1.default.debug('Would execute Neo4j query for module', { moduleId, nodes, relationships, query: moduleQuery });
logger_1.default.debug('Would execute Neo4j query for module', { moduleId, nodes, relationships });
// Extract and create class nodes
const classMatches = content.match(/aide:(\w+)\s+a\s+aide:Class/g) || [];
for (const match of classMatches) {
const classNameMatch = match.match(/aide:(\w+)/);
if (classNameMatch) {
const className = classNameMatch[1];
logger_1.default.debug('Would create class node', { moduleId, className });
}
}
}
async storeMCPContext(_ttlPath, context) {
// Store in file system cache
const cacheDir = path.join(this.config.projectRoot, '.aaswe', 'mcp-cache');
await fs.mkdir(cacheDir, { recursive: true });
const cacheFile = path.join(cacheDir, `${context.moduleId}.json`);
await fs.writeFile(cacheFile, JSON.stringify(context, null, 2), 'utf8');
logger_1.default.debug('Stored MCP context', { moduleId: context.moduleId, cacheFile });
}
extractBusinessContextFromTTL(content) {
const businessContext = {};
// Extract business domain
const domainMatch = content.match(/aide:businessDomain\s+"([^"]+)"/);
if (domainMatch) {
businessContext.domain = domainMatch[1];
}
// Extract business rules
const rulesMatches = content.match(/aide:businessRule\s+"([^"]+)"/g) || [];
businessContext.rules = rulesMatches.map(match => match.replace(/aide:businessRule\s+"([^"]+)"/, '$1'));
return businessContext;
}
extractTechnicalSummaryFromTTL(content) {
const summary = {};
// Count technical elements
summary.classes = (content.match(/aide:Class/g) || []).length;
summary.methods = (content.match(/aide:Method/g) || []).length;
summary.functions = (content.match(/aide:Function/g) || []).length;
summary.dependencies = (content.match(/aide:dependsOn/g) || []).length;
return summary;
}
extractModuleIdFromTTL(content) {
const moduleMatch = content.match(/aide:(\w+)\s+a\s+aide:Module/);
return moduleMatch ? moduleMatch[1] : 'unknown_module';
}
async updateModuleInKnowledgeGraph(moduleId, content) {
// Update specific module in Neo4j
logger_1.default.debug('Would update module in knowledge graph', { moduleId });
// Extract updated information and sync to graph
const classes = (content.match(/aide:Class/g) || []).length;
const methods = (content.match(/aide:Method/g) || []).length;
logger_1.default.debug('Module update stats', { moduleId, classes, methods });
}
async updateMCPContextForModule(moduleId, context) {
// Update specific module context
const cacheDir = path.join(this.config.projectRoot, '.aaswe', 'mcp-cache');
await fs.mkdir(cacheDir, { recursive: true });
const cacheFile = path.join(cacheDir, `${moduleId}.json`);
await fs.writeFile(cacheFile, JSON.stringify(context, null, 2), 'utf8');
logger_1.default.debug('Updated MCP context for module', { moduleId });
}
/**
* Write TTL content to the specified output directory
*/
async writeTTLToOutputDirectory(modulePath, rdfResult) {
try {
// Create output directory if it doesn't exist
await fs.mkdir(this.config.outputDirectory, { recursive: true });
// Generate output file name based on module path
// modulePath is like "src/services/.module-knowledge.ttl"
const moduleDir = path.dirname(modulePath);
const moduleDirName = path.basename(moduleDir);
const outputFileName = `${moduleDirName}.module-knowledge.ttl`;
const outputPath = path.join(this.config.outputDirectory, outputFileName);
// Write TTL content to output file
await fs.writeFile(outputPath, rdfResult.rdfContent, 'utf8');
logger_1.default.info('TTL file written to output directory', {
modulePath,
outputPath,
moduleDir,
moduleDirName,
size: rdfResult.size
});
}
catch (error) {
logger_1.default.error('Failed to write TTL to output directory', { modulePath, error });
throw error;
}
}
getLanguageFromFile(filePath) {
const ext = path.extname(filePath).toLowerCase();
const languageMap = {
'.ts': 'typescript',
'.tsx': 'typescript',
'.js': 'javascript',
'.jsx': 'javascript',
'.py': 'python',
'.java': 'java',
'.go': 'go',
'.rs': 'rust',
'.cpp': 'cpp',
'.hpp': 'cpp'
};
return languageMap[ext] || 'typescript';
}
/**
* Detect architectural patterns from class and method names
*/
detectArchitecturalPatternsFromNames(names) {
const patterns = [];
const lowerNames = names.map(name => name.toLowerCase());
if (lowerNames.some(name => name.includes('factory'))) {
patterns.push('Factory Pattern');
}
if (lowerNames.some(name => name.includes('singleton'))) {
patterns.push('Singleton Pattern');
}
if (lowerNames.some(name => name.includes('observer'))) {
patterns.push('Observer Pattern');
}
if (lowerNames.some(name => name.includes('builder'))) {
patterns.push('Builder Pattern');
}
if (lowerNames.some(name => name.includes('middleware'))) {
patterns.push('Middleware Pattern');
}
if (lowerNames.some(name => name.includes('command'))) {
patterns.push('Command Pattern');
}
if (lowerNames.some(name => name.includes('strategy'))) {
patterns.push('Strategy Pattern');
}
if (lowerNames.some(name => name.includes('adapter'))) {
patterns.push('Adapter Pattern');
}
return patterns;
}
/**
* Detect business domain indicators from class and method names
*/
detectBusinessDomainFromNames(names) {
const indicators = [];
const lowerNames = names.map(name => name.toLowerCase());
const domainKeywords = {
'E-commerce': ['order', 'cart', 'payment', 'product', 'customer', 'checkout'],
'Finance': ['account', 'transaction', 'balance', 'payment', 'invoice', 'billing'],
'Healthcare': ['patient', 'medical', 'diagnosis', 'treatment', 'doctor', 'appointment'],
'Education': ['student', 'course', 'grade', 'assignment', 'teacher', 'class'],
'HR': ['employee', 'payroll', 'attendance', 'performance', 'recruitment'],
'CRM': ['lead', 'contact', 'opportunity', 'pipeline', 'sales'],
'Content Management': ['article', 'post', 'content', 'publish', 'editor'],
'Authentication': ['user', 'login', 'auth', 'token', 'session', 'permission'],
'Configuration Management': ['config', 'properties', 'settings', 'environment']
};
for (const [domain, keywords] of Object.entries(domainKeywords)) {
const matches = keywords.filter(keyword => lowerNames.some(name => name.includes(keyword)));
if (matches.length >= 1) {
indicators.push(domain);
}
}
return indicators;
}
}
exports.AutoAnalysisWorkflow = AutoAnalysisWorkflow;
//# sourceMappingURL=AutoAnalysisWorkflow.js.map