@aaswe/codebase-ai
Version:
AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs
811 lines • 33.5 kB
JavaScript
"use strict";
/**
* Project Analysis Service
*
* Core orchestrator for automatic project analysis and TTL generation.
* This service coordinates between AST analysis, RDF generation, and knowledge management
* to provide comprehensive project understanding and context generation.
*/
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.ProjectAnalysisService = void 0;
const events_1 = require("events");
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const logger_1 = __importDefault(require("../../utils/logger"));
const CodeIngestionService_1 = require("../layer1/code-ingestion/CodeIngestionService");
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 GoAnalyzer_1 = require("../layer1/ast-analyzer/GoAnalyzer");
const RustAnalyzer_1 = require("../layer1/ast-analyzer/RustAnalyzer");
const CppAnalyzer_1 = require("../layer1/ast-analyzer/CppAnalyzer");
/**
* Project Analysis Service
*
* Provides comprehensive project analysis capabilities including:
* - Project structure detection and analysis
* - Multi-language code analysis and AST generation
* - Automatic TTL knowledge file generation
* - Real-time file watching and incremental updates
* - Integration with knowledge management system
*/
class ProjectAnalysisService extends events_1.EventEmitter {
codeIngestionService;
moduleKnowledgeManager;
analyzers;
config;
isInitialized = false;
constructor(config) {
super();
this.config = {
outputDirectory: '.aaswe/knowledge',
languages: ['typescript', 'javascript', 'python', 'java', 'go', 'rust', 'cpp'],
includePatterns: ['**/*.ts', '**/*.js', '**/*.py', '**/*.java', '**/*.go', '**/*.rs', '**/*.cpp', '**/*.hpp'],
excludePatterns: [
'**/node_modules/**',
'**/.git/**',
'**/dist/**',
'**/build/**',
'**/*.test.*',
'**/*.spec.*',
'**/coverage/**',
'**/.next/**',
'**/.nuxt/**',
'**/venv/**',
'**/env/**',
'**/.venv/**',
'**/.env/**',
'**/__pycache__/**',
'**/*.pyc',
'**/.pytest_cache/**',
'**/target/**',
'**/vendor/**'
],
generateTTL: true,
enableWatching: true,
preserveBusinessContext: true,
analysisDepth: 'detailed',
...config
};
// Initialize analyzers
this.analyzers = new Map();
this.setupAnalyzers();
// Initialize services
this.moduleKnowledgeManager = new ModuleKnowledgeManager_1.ModuleKnowledgeManager({
autoValidate: true,
preserveBusinessContext: this.config.preserveBusinessContext || true,
enableConflictResolution: true,
enableLLMPreview: true
});
// Use TypeScript analyzer as primary for code ingestion
const primaryAnalyzer = this.analyzers.get('typescript') || this.analyzers.get('javascript');
this.codeIngestionService = new CodeIngestionService_1.CodeIngestionService(primaryAnalyzer, {
supportedLanguages: this.config.languages || ['typescript', 'javascript'],
defaultExcludePatterns: this.config.excludePatterns || []
});
this.setupEventHandlers();
}
/**
* Initialize the Project Analysis Service
*/
async initialize() {
if (this.isInitialized) {
return;
}
try {
logger_1.default.info('Initializing Project Analysis Service', {
rootPath: this.config.rootPath,
languages: this.config.languages,
analysisDepth: this.config.analysisDepth
});
// Initialize sub-services
await this.moduleKnowledgeManager.initialize();
await this.codeIngestionService.initialize();
// Ensure output directory exists
if (this.config.outputDirectory) {
await fs.mkdir(path.resolve(this.config.rootPath, this.config.outputDirectory), { recursive: true });
}
this.isInitialized = true;
logger_1.default.info('Project Analysis Service initialized successfully');
}
catch (error) {
logger_1.default.error('Failed to initialize Project Analysis Service', { error });
throw error;
}
}
/**
* Analyze entire project and generate TTL files
*/
async analyzeProject() {
this.ensureInitialized();
const analysisId = `analysis_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const startTime = new Date();
logger_1.default.info('Starting comprehensive project analysis', {
analysisId,
rootPath: this.config.rootPath,
analysisDepth: this.config.analysisDepth
});
try {
// Phase 1: Project Structure Discovery
logger_1.default.info('Phase 1: Discovering project structure');
const projectStructure = await this.discoverProjectStructure();
this.emit('structureDiscovered', { analysisId, structure: projectStructure });
// Phase 2: File Discovery and Filtering
logger_1.default.info('Phase 2: Discovering and filtering source files');
const sourceFiles = await this.discoverSourceFiles();
// Phase 3: Code Analysis
logger_1.default.info('Phase 3: Analyzing source code', { fileCount: sourceFiles.length });
const analysisResults = await this.analyzeSourceFiles(sourceFiles, analysisId);
// Phase 4: TTL Generation (if enabled)
let ttlResults = [];
if (this.config.generateTTL) {
logger_1.default.info('Phase 4: Generating TTL knowledge files');
ttlResults = await this.generateTTLFiles(analysisResults, analysisId);
}
// Phase 5: Setup Watching (if enabled)
if (this.config.enableWatching) {
logger_1.default.info('Phase 5: Setting up file watching');
await this.setupProjectWatching();
}
const endTime = new Date();
const duration = endTime.getTime() - startTime.getTime();
// Compile final results
const result = {
projectPath: this.config.rootPath,
analysisId,
startTime,
endTime,
duration,
summary: this.compileSummary(analysisResults, ttlResults),
files: analysisResults.map(result => ({
filePath: result.filePath,
language: result.language,
status: result.success ? 'success' : 'error',
ttlGenerated: result.ttlGenerated || false,
error: result.error
})),
errors: analysisResults
.filter(result => !result.success)
.map(result => ({
filePath: result.filePath,
error: result.error || 'Unknown error',
phase: 'analysis'
})),
warnings: [],
recommendations: this.generateRecommendations(projectStructure, analysisResults)
};
logger_1.default.info('Project analysis completed successfully', {
analysisId,
duration,
totalFiles: result.summary.totalFiles,
analyzedFiles: result.summary.analyzedFiles,
ttlFilesGenerated: result.summary.ttlFilesGenerated
});
this.emit('analysisCompleted', result);
return result;
}
catch (error) {
const endTime = new Date();
const duration = endTime.getTime() - startTime.getTime();
logger_1.default.error('Project analysis failed', { analysisId, error, duration });
const errorResult = {
projectPath: this.config.rootPath,
analysisId,
startTime,
endTime,
duration,
summary: {
totalFiles: 0,
analyzedFiles: 0,
skippedFiles: 0,
errorFiles: 1,
ttlFilesGenerated: 0,
languageBreakdown: {}
},
files: [],
errors: [{
filePath: this.config.rootPath,
error: error instanceof Error ? error.message : 'Unknown error',
phase: 'discovery'
}],
warnings: [],
recommendations: []
};
this.emit('analysisFailed', { analysisId, error, result: errorResult });
return errorResult;
}
}
/**
* Analyze specific files (incremental analysis)
*/
async analyzeFiles(filePaths) {
this.ensureInitialized();
const analysisId = `incremental_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const startTime = new Date();
logger_1.default.info('Starting incremental file analysis', {
analysisId,
fileCount: filePaths.length,
files: filePaths
});
try {
// Filter and validate file paths
const validFiles = await this.validateFilePaths(filePaths);
// Analyze files
const analysisResults = await this.analyzeSourceFiles(validFiles, analysisId);
// Generate TTL files if enabled
let ttlResults = [];
if (this.config.generateTTL) {
ttlResults = await this.generateTTLFiles(analysisResults, analysisId);
}
const endTime = new Date();
const duration = endTime.getTime() - startTime.getTime();
const result = {
projectPath: this.config.rootPath,
analysisId,
startTime,
endTime,
duration,
summary: this.compileSummary(analysisResults, ttlResults),
files: analysisResults.map(result => ({
filePath: result.filePath,
language: result.language,
status: result.success ? 'success' : 'error',
ttlGenerated: result.ttlGenerated || false,
error: result.error
})),
errors: analysisResults
.filter(result => !result.success)
.map(result => ({
filePath: result.filePath,
error: result.error || 'Unknown error',
phase: 'analysis'
})),
warnings: [],
recommendations: []
};
logger_1.default.info('Incremental analysis completed', {
analysisId,
duration,
analyzedFiles: result.summary.analyzedFiles
});
this.emit('incrementalAnalysisCompleted', result);
return result;
}
catch (error) {
logger_1.default.error('Incremental analysis failed', { analysisId, error });
throw error;
}
}
/**
* Get project structure information
*/
async getProjectStructure() {
this.ensureInitialized();
return await this.discoverProjectStructure();
}
/**
* Get analysis status and metrics
*/
getAnalysisMetrics() {
return {
codeIngestion: this.codeIngestionService.getMetrics(),
knowledgeFiles: {
totalFiles: 0, // Would be populated from module knowledge manager
validFiles: 0,
businessContextEnhanced: 0
},
analyzers: Array.from(this.analyzers.keys()),
lastAnalysis: new Date()
};
}
/**
* Shutdown the service
*/
async shutdown() {
logger_1.default.info('Shutting down Project Analysis Service');
try {
await this.codeIngestionService.shutdown();
this.removeAllListeners();
this.isInitialized = false;
logger_1.default.info('Project Analysis Service shutdown completed');
}
catch (error) {
logger_1.default.error('Error during Project Analysis Service shutdown', { error });
throw error;
}
}
// Private methods
setupAnalyzers() {
this.analyzers.set('typescript', new TypeScriptAnalyzer_1.TypeScriptAnalyzer());
this.analyzers.set('javascript', new TypeScriptAnalyzer_1.TypeScriptAnalyzer()); // TypeScript analyzer handles JS too
this.analyzers.set('python', new PythonAnalyzer_1.PythonAnalyzer());
this.analyzers.set('java', new JavaAnalyzer_1.JavaAnalyzer());
this.analyzers.set('go', new GoAnalyzer_1.GoAnalyzer());
this.analyzers.set('rust', new RustAnalyzer_1.RustAnalyzer());
this.analyzers.set('cpp', new CppAnalyzer_1.CppAnalyzer());
}
setupEventHandlers() {
// Code ingestion events
this.codeIngestionService.on('analysisCompleted', (result) => {
this.emit('codeAnalysisCompleted', result);
});
this.codeIngestionService.on('jobCompleted', (job) => {
this.emit('analysisJobCompleted', job);
});
this.codeIngestionService.on('jobFailed', (job) => {
this.emit('analysisJobFailed', job);
});
// Module knowledge events
this.moduleKnowledgeManager.on('file_updated', (event) => {
this.emit('ttlFileUpdated', event);
});
this.moduleKnowledgeManager.on('file_validated', (event) => {
this.emit('ttlFileValidated', event);
});
}
async discoverProjectStructure() {
const rootPath = this.config.rootPath;
try {
// Discover package files
const packageFiles = await this.discoverPackageFiles(rootPath);
// Discover source files
const sourceFiles = await this.discoverSourceFiles();
// Discover directories
const directories = await this.discoverDirectories(rootPath);
// Determine project type
const projectType = this.determineProjectType(packageFiles, sourceFiles);
// Detect frameworks
const frameworks = this.detectFrameworks(packageFiles, sourceFiles);
// Detect build tools
const buildTools = this.detectBuildTools(packageFiles);
return {
rootPath,
packageFiles,
sourceFiles,
directories,
projectType,
frameworks,
buildTools
};
}
catch (error) {
logger_1.default.error('Failed to discover project structure', { rootPath, error });
throw error;
}
}
async discoverPackageFiles(rootPath) {
const packageFiles = [];
const packageFilePatterns = [
{ pattern: 'package.json', type: 'package.json' },
{ pattern: 'requirements.txt', type: 'requirements.txt' },
{ pattern: 'pom.xml', type: 'pom.xml' },
{ pattern: 'go.mod', type: 'go.mod' },
{ pattern: 'Cargo.toml', type: 'Cargo.toml' },
{ pattern: 'CMakeLists.txt', type: 'CMakeLists.txt' }
];
for (const { pattern, type } of packageFilePatterns) {
const filePath = path.join(rootPath, pattern);
try {
await fs.access(filePath);
const dependencies = await this.extractDependencies(filePath, type);
packageFiles.push({ path: filePath, type, dependencies });
}
catch {
// File doesn't exist, continue
}
}
return packageFiles;
}
async discoverSourceFiles() {
const { glob } = await Promise.resolve().then(() => __importStar(require('glob')));
const sourceFiles = [];
for (const pattern of this.config.includePatterns) {
try {
const files = await glob(pattern, {
cwd: this.config.rootPath,
ignore: this.config.excludePatterns || [],
absolute: true
});
for (const filePath of files) {
try {
const stats = await fs.stat(filePath);
const language = this.getLanguageFromFile(filePath);
sourceFiles.push({
path: filePath,
language,
size: stats.size,
lastModified: stats.mtime
});
}
catch (error) {
logger_1.default.warn('Failed to stat source file', { filePath, error });
}
}
}
catch (error) {
logger_1.default.warn('Failed to glob pattern', { pattern, error });
}
}
return sourceFiles;
}
async discoverDirectories(rootPath) {
const directories = [];
try {
const entries = await fs.readdir(rootPath, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && !entry.name.startsWith('.')) {
const dirPath = path.join(rootPath, entry.name);
const type = this.classifyDirectory(entry.name);
const fileCount = await this.countFilesInDirectory(dirPath);
directories.push({
path: dirPath,
type,
fileCount
});
}
}
}
catch (error) {
logger_1.default.warn('Failed to discover directories', { rootPath, error });
}
return directories;
}
async analyzeSourceFiles(sourceFiles, analysisId) {
const results = [];
const batchSize = 10; // Process files in batches
logger_1.default.info('Analyzing source files', {
totalFiles: sourceFiles.length,
batchSize,
analysisId
});
for (let i = 0; i < sourceFiles.length; i += batchSize) {
const batch = sourceFiles.slice(i, i + batchSize);
const batchPromises = batch.map(async (file) => {
try {
const analyzer = this.analyzers.get(file.language);
if (!analyzer) {
return {
filePath: file.path,
language: file.language,
success: false,
error: `No analyzer available for language: ${file.language}`
};
}
logger_1.default.debug('Analyzing file', { filePath: file.path, language: file.language });
const analysisResult = await analyzer.analyzeFile(file.path);
return {
filePath: file.path,
language: file.language,
success: true,
analysisResult,
ttlGenerated: false
};
}
catch (error) {
logger_1.default.error('Failed to analyze file', { filePath: file.path, error });
return {
filePath: file.path,
language: file.language,
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
// Emit progress
this.emit('analysisProgress', {
analysisId,
processed: Math.min(i + batchSize, sourceFiles.length),
total: sourceFiles.length,
currentBatch: batchResults
});
}
return results;
}
async generateTTLFiles(analysisResults, analysisId) {
const ttlResults = [];
logger_1.default.info('Generating TTL knowledge files', {
fileCount: analysisResults.filter(r => r.success).length,
analysisId
});
for (const result of analysisResults) {
if (!result.success || !result.analysisResult) {
continue;
}
try {
const ttlResult = await this.moduleKnowledgeManager.updateKnowledgeFileFromCode(result.filePath, result.analysisResult);
if (ttlResult.success) {
result.ttlGenerated = true;
ttlResults.push({
sourceFile: result.filePath,
ttlFile: ttlResult.data?.filePath,
success: true
});
}
else {
ttlResults.push({
sourceFile: result.filePath,
success: false,
error: ttlResult.error
});
}
}
catch (error) {
logger_1.default.error('Failed to generate TTL file', {
sourceFile: result.filePath,
error
});
ttlResults.push({
sourceFile: result.filePath,
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
});
}
}
return ttlResults;
}
async setupProjectWatching() {
try {
// Add the project as a repository to the code ingestion service
await this.codeIngestionService.addRepository({
name: path.basename(this.config.rootPath),
path: this.config.rootPath,
enableFileWatcher: true,
includePatterns: this.config.includePatterns || [],
excludePatterns: this.config.excludePatterns || [],
languages: this.config.languages || []
});
logger_1.default.info('Project watching setup completed', {
rootPath: this.config.rootPath
});
}
catch (error) {
logger_1.default.error('Failed to setup project watching', { error });
throw error;
}
}
async validateFilePaths(filePaths) {
const validFiles = [];
for (const filePath of filePaths) {
try {
const stats = await fs.stat(filePath);
const language = this.getLanguageFromFile(filePath);
if (this.config.languages.includes(language)) {
validFiles.push({
path: filePath,
language,
size: stats.size,
lastModified: stats.mtime
});
}
}
catch (error) {
logger_1.default.warn('Invalid file path', { filePath, error });
}
}
return validFiles;
}
compileSummary(analysisResults, ttlResults) {
const languageBreakdown = {};
for (const result of analysisResults) {
languageBreakdown[result.language] = (languageBreakdown[result.language] || 0) + 1;
}
return {
totalFiles: analysisResults.length,
analyzedFiles: analysisResults.filter(r => r.success).length,
skippedFiles: 0,
errorFiles: analysisResults.filter(r => !r.success).length,
ttlFilesGenerated: ttlResults.filter(r => r.success).length,
languageBreakdown
};
}
generateRecommendations(structure, analysisResults) {
const recommendations = [];
// Check for missing package files
if (structure.packageFiles.length === 0) {
recommendations.push('Consider adding a package management file (package.json, requirements.txt, etc.)');
}
// Check for test coverage
const hasTests = analysisResults.some(r => r.filePath.includes('test') || r.filePath.includes('spec'));
if (!hasTests) {
recommendations.push('Consider adding test files to improve code quality and documentation');
}
// Check for documentation
const hasReadme = structure.sourceFiles.some(f => path.basename(f.path).toLowerCase().includes('readme'));
if (!hasReadme) {
recommendations.push('Consider adding a README.md file to document your project');
}
return recommendations;
}
// Utility methods
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',
'.cc': 'cpp',
'.cxx': 'cpp'
};
return languageMap[ext] || 'unknown';
}
async extractDependencies(filePath, type) {
try {
const content = await fs.readFile(filePath, 'utf-8');
switch (type) {
case 'package.json':
const pkg = JSON.parse(content);
return [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.devDependencies || {})
];
case 'requirements.txt':
return content.split('\n')
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'))
.map(line => line.split('==')[0].split('>=')[0].split('<=')[0]);
default:
return [];
}
}
catch (error) {
logger_1.default.warn('Failed to extract dependencies', { filePath, type, error });
return [];
}
}
determineProjectType(packageFiles, sourceFiles) {
// Simple heuristics for project type detection
const hasWebFiles = sourceFiles.some(f => f.path.includes('html') || f.path.includes('css') || f.path.includes('react') || f.path.includes('vue'));
const hasApiFiles = sourceFiles.some(f => f.path.includes('api') || f.path.includes('server') || f.path.includes('route'));
const hasCliFiles = sourceFiles.some(f => f.path.includes('cli') || f.path.includes('bin'));
// Check package files for additional context
const hasWebDependencies = packageFiles.some(pkg => pkg.dependencies.some(dep => ['react', 'vue', 'angular', 'svelte'].includes(dep.toLowerCase())));
if ((hasWebFiles || hasWebDependencies) && hasApiFiles)
return 'mixed';
if (hasWebFiles || hasWebDependencies)
return 'web';
if (hasApiFiles)
return 'api';
if (hasCliFiles)
return 'cli';
return 'library';
}
detectFrameworks(packageFiles, sourceFiles) {
const frameworks = [];
// Check package dependencies
for (const pkgFile of packageFiles) {
const deps = pkgFile.dependencies.join(' ').toLowerCase();
if (deps.includes('react'))
frameworks.push('React');
if (deps.includes('vue'))
frameworks.push('Vue');
if (deps.includes('angular'))
frameworks.push('Angular');
if (deps.includes('express'))
frameworks.push('Express');
if (deps.includes('fastapi'))
frameworks.push('FastAPI');
if (deps.includes('django'))
frameworks.push('Django');
if (deps.includes('spring'))
frameworks.push('Spring');
}
// Check source files for framework indicators
const sourceContent = sourceFiles.map(f => f.path.toLowerCase()).join(' ');
if (sourceContent.includes('react') && !frameworks.includes('React'))
frameworks.push('React');
if (sourceContent.includes('vue') && !frameworks.includes('Vue'))
frameworks.push('Vue');
if (sourceContent.includes('angular') && !frameworks.includes('Angular'))
frameworks.push('Angular');
return [...new Set(frameworks)];
}
detectBuildTools(packageFiles) {
const buildTools = [];
for (const pkgFile of packageFiles) {
switch (pkgFile.type) {
case 'package.json':
buildTools.push('npm/yarn');
break;
case 'pom.xml':
buildTools.push('Maven');
break;
case 'go.mod':
buildTools.push('Go Modules');
break;
case 'Cargo.toml':
buildTools.push('Cargo');
break;
case 'CMakeLists.txt':
buildTools.push('CMake');
break;
}
}
return buildTools;
}
classifyDirectory(dirName) {
const name = dirName.toLowerCase();
if (name.includes('test') || name.includes('spec'))
return 'test';
if (name.includes('src') || name.includes('lib'))
return 'source';
if (name.includes('config') || name.includes('conf'))
return 'config';
if (name.includes('doc') || name.includes('readme'))
return 'docs';
if (name.includes('build') || name.includes('dist'))
return 'build';
return 'other';
}
ensureInitialized() {
if (!this.isInitialized) {
throw new Error('Project Analysis Service not initialized. Call initialize() first.');
}
}
async countFilesInDirectory(dirPath) {
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
let count = 0;
for (const entry of entries) {
if (entry.isFile()) {
count++;
}
else if (entry.isDirectory() && !entry.name.startsWith('.')) {
count += await this.countFilesInDirectory(path.join(dirPath, entry.name));
}
}
return count;
}
catch (error) {
logger_1.default.warn('Failed to count files in directory', { dirPath, error });
return 0;
}
}
}
exports.ProjectAnalysisService = ProjectAnalysisService;
//# sourceMappingURL=ProjectAnalysisService.js.map