@aaswe/codebase-ai
Version:
AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs
471 lines (466 loc) • 20 kB
JavaScript
;
/**
* RDF Service
*
* Main orchestrator for RDF generation, validation, and management.
* Provides high-level interface for creating and maintaining .module-knowledge.ttl files.
*/
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.RDFService = void 0;
const path = __importStar(require("path"));
const fs = __importStar(require("fs/promises"));
const logger_1 = __importDefault(require("../../../utils/logger"));
const RDFGenerator_1 = require("./RDFGenerator");
const RDFValidator_1 = require("./RDFValidator");
/**
* Production-Quality RDF Service
*
* Orchestrates the complete RDF generation workflow from AST analysis
* to validated TTL files with comprehensive error handling and logging.
*/
class RDFService {
generator;
validator;
constructor(options = {}) {
this.generator = new RDFGenerator_1.RDFGenerator(options);
this.validator = new RDFValidator_1.RDFValidator();
}
/**
* Generate RDF from AST Analysis Result
*/
async generateRDF(astResult, modulePath, options = {}) {
const startTime = Date.now();
try {
logger_1.default.info('Starting RDF generation', {
modulePath,
language: astResult.language,
classCount: astResult.classes.length,
functionCount: astResult.functions.length
});
// Generate RDF content
const result = await this.generator.generateFromAST(astResult, modulePath);
// Validate generated RDF
if (options.validateOutput !== false) {
const validationResult = await this.validator.validateContent(result.rdfContent);
if (!validationResult.isValid) {
logger_1.default.warn('RDF validation failed', {
modulePath,
errors: validationResult.errors.length,
warnings: validationResult.warnings.length
});
// Add validation warnings to result
result.warnings.push(...validationResult.errors.map(error => ({
type: 'invalid_uri',
message: `Validation error: ${error.message}`,
suggestion: 'Fix RDF syntax or schema compliance issues'
})));
}
}
const totalTime = Date.now() - startTime;
logger_1.default.info('RDF generation completed', {
modulePath,
size: result.size,
triples: result.statistics.totalTriples,
generationTime: result.generationTime,
totalTime,
warnings: result.warnings.length
});
return result;
}
catch (error) {
logger_1.default.error('RDF generation failed', { error, modulePath });
throw new Error(`RDF generation failed for ${modulePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Generate RDF for Multiple Modules
*/
async generateBatchRDF(astResults, options = {}) {
const results = new Map();
const errors = [];
logger_1.default.info('Starting batch RDF generation', {
moduleCount: astResults.size
});
// Process modules in parallel with concurrency limit
const concurrency = options.batchConcurrency || 5;
const entries = Array.from(astResults.entries());
for (let i = 0; i < entries.length; i += concurrency) {
const batch = entries.slice(i, i + concurrency);
const batchPromises = batch.map(async ([modulePath, astResult]) => {
try {
// Add timeout to prevent hanging
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('RDF generation timeout')), 5000);
});
// Add small delay between generations to prevent N3 Writer conflicts
await new Promise(resolve => setTimeout(resolve, 10));
const generationPromise = this.generateRDF(astResult, modulePath, options);
const result = await Promise.race([generationPromise, timeoutPromise]);
results.set(modulePath, result);
}
catch (error) {
logger_1.default.error('Batch generation error for module', {
modulePath,
error: error instanceof Error ? error.message : String(error),
errorName: error instanceof Error ? error.name : 'Unknown',
stack: error instanceof Error ? error.stack : undefined
});
errors.push({
path: modulePath,
error: error instanceof Error ? error : new Error('Unknown error')
});
}
});
await Promise.all(batchPromises);
}
if (errors.length > 0) {
logger_1.default.warn('Batch RDF generation completed with errors', {
successful: results.size,
failed: errors.length,
errors: errors.map(e => ({ path: e.path, message: e.error.message }))
});
}
else {
logger_1.default.info('Batch RDF generation completed successfully', {
moduleCount: results.size
});
}
return results;
}
/**
* Validate Existing TTL File
*/
async validateTTLFile(filePath) {
try {
logger_1.default.info('Validating TTL file', { filePath });
const result = await this.validator.validateFile(filePath);
logger_1.default.info('TTL validation completed', {
filePath,
isValid: result.isValid,
errors: result.errors.length,
warnings: result.warnings.length,
triples: result.statistics.totalTriples
});
return result;
}
catch (error) {
logger_1.default.error('TTL validation failed', { error, filePath });
throw new Error(`TTL validation failed for ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Detect Modules in Directory
*/
async detectModules(rootPath) {
const modules = [];
const errors = [];
let totalFiles = 0;
let supportedFiles = 0;
const skippedFiles = [];
try {
logger_1.default.info('Starting module detection', { rootPath });
await this.scanDirectory(rootPath, modules, errors, {
totalFiles: () => totalFiles++,
supportedFiles: () => supportedFiles++,
skippedFiles: (file) => skippedFiles.push(file)
});
logger_1.default.info('Module detection completed', {
rootPath,
modulesFound: modules.length,
totalFiles,
supportedFiles,
skippedFiles: skippedFiles.length,
errors: errors.length
});
return {
modules,
totalFiles,
supportedFiles,
skippedFiles,
errors
};
}
catch (error) {
logger_1.default.error('Module detection failed', { error, rootPath });
throw new Error(`Module detection failed for ${rootPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Update Existing TTL File with New Data
*/
async updateTTLFile(filePath, astResult, preserveBusinessContext = true) {
try {
logger_1.default.info('Updating TTL file', { filePath, preserveBusinessContext });
let existingBusinessContext = null;
// Extract existing business context if preserving
if (preserveBusinessContext) {
try {
const existingContent = await fs.readFile(filePath, 'utf8');
existingBusinessContext = this.extractBusinessContext(existingContent);
}
catch (error) {
logger_1.default.warn('Could not read existing TTL file for context preservation', { filePath, error });
}
}
// Generate new RDF
const result = await this.generateRDF(astResult, path.dirname(filePath));
// Merge with existing business context if available
let finalContent = result.rdfContent;
if (existingBusinessContext) {
finalContent = this.mergeBusinessContext(result.rdfContent, existingBusinessContext);
}
// Write updated content
await fs.writeFile(filePath, finalContent, 'utf8');
// Return updated result
const updatedResult = {
...result,
rdfContent: finalContent,
size: Buffer.byteLength(finalContent, 'utf8')
};
logger_1.default.info('TTL file updated successfully', {
filePath,
size: result.size,
preservedContext: !!existingBusinessContext
});
return updatedResult;
}
catch (error) {
logger_1.default.error('TTL file update failed', { error, filePath });
throw new Error(`TTL file update failed for ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Synchronize TTL Files with Code Changes
*/
async synchronizeTTLFiles(changedFiles, astResults) {
const results = new Map();
logger_1.default.info('Starting TTL synchronization', {
changedFiles: changedFiles.length
});
for (const filePath of changedFiles) {
const astResult = astResults.get(filePath);
if (!astResult) {
logger_1.default.warn('No AST result found for changed file', { filePath });
continue;
}
try {
const ttlPath = this.getTTLPath(filePath);
const result = await this.updateTTLFile(ttlPath, astResult, true);
results.set(filePath, result);
}
catch (error) {
logger_1.default.error('Failed to synchronize TTL file', { error, filePath });
}
}
logger_1.default.info('TTL synchronization completed', {
synchronized: results.size,
requested: changedFiles.length
});
return results;
}
/**
* Generate Module Summary Report
*/
async generateSummaryReport(results) {
const totalModules = results.size;
const totalTriples = Array.from(results.values()).reduce((sum, result) => sum + result.statistics.totalTriples, 0);
const totalSize = Array.from(results.values()).reduce((sum, result) => sum + result.size, 0);
const totalWarnings = Array.from(results.values()).reduce((sum, result) => sum + result.warnings.length, 0);
const avgTriplesPerModule = totalModules > 0 ? Math.round(totalTriples / totalModules) : 0;
const avgSizePerModule = totalModules > 0 ? Math.round(totalSize / totalModules) : 0;
const report = `# RDF Generation Summary Report
## Overview
- Total Modules: ${totalModules}
- Total RDF Triples: ${totalTriples.toLocaleString()}
- Total Size: ${(totalSize / 1024).toFixed(2)} KB
- Total Warnings: ${totalWarnings}
## Averages
- Triples per Module: ${avgTriplesPerModule}
- Size per Module: ${(avgSizePerModule / 1024).toFixed(2)} KB
## Module Details
${Array.from(results.entries()).map(([path, result]) => `
### ${path}
- Triples: ${result.statistics.totalTriples}
- Classes: ${result.statistics.classCount}
- Methods: ${result.statistics.methodCount}
- Size: ${(result.size / 1024).toFixed(2)} KB
- Generation Time: ${result.generationTime}ms
- Warnings: ${result.warnings.length}
`).join('')}
## Quality Metrics
- Documentation Coverage: ${this.calculateDocumentationCoverage(results)}%
- Business Context Coverage: ${this.calculateBusinessContextCoverage(results)}%
- Average Complexity: ${this.calculateAverageComplexity(results)}
Generated at: ${new Date().toISOString()}`;
return report.trim();
}
// Private helper methods
async scanDirectory(dirPath, modules, errors, counters) {
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
// Skip node_modules and other common directories
if (!['node_modules', '.git', 'dist', 'build', '.next'].includes(entry.name)) {
await this.scanDirectory(fullPath, modules, errors, counters);
}
}
else if (entry.isFile()) {
counters.totalFiles();
if (this.isSupportedFile(entry.name)) {
counters.supportedFiles();
// Detect if this is a module entry point
const module = await this.detectModule(fullPath);
if (module) {
modules.push(module);
}
}
else {
counters.skippedFiles(fullPath);
}
}
}
}
catch (error) {
errors.push({
file: dirPath,
error: error instanceof Error ? error.message : 'Unknown error',
type: 'access_denied'
});
}
}
isSupportedFile(filename) {
const supportedExtensions = ['.ts', '.js', '.py', '.java', '.go', '.rs', '.cpp', '.hpp', '.c', '.h'];
return supportedExtensions.some(ext => filename.endsWith(ext));
}
async detectModule(filePath) {
try {
const stats = await fs.stat(filePath);
const ext = path.extname(filePath);
const basename = path.basename(filePath, ext);
// Simple heuristics for module detection
const isEntryPoint = ['index', 'main', 'app'].includes(basename.toLowerCase()) ||
filePath.includes('src/') ||
filePath.includes('lib/');
if (isEntryPoint) {
return {
path: filePath,
name: basename,
type: this.inferModuleType(filePath),
language: this.getLanguageFromExtension(ext),
entryPoints: [filePath],
configFiles: [],
dependencies: [],
estimatedSize: stats.size
};
}
return null;
}
catch {
return null;
}
}
inferModuleType(filePath) {
if (filePath.includes('test') || filePath.includes('spec'))
return 'test';
if (filePath.includes('util') || filePath.includes('helper'))
return 'utility';
if (filePath.includes('service') || filePath.includes('api'))
return 'service';
if (filePath.includes('lib') || filePath.includes('package'))
return 'library';
return 'application';
}
getLanguageFromExtension(ext) {
const languageMap = {
'.ts': 'typescript',
'.js': 'javascript',
'.py': 'python',
'.java': 'java',
'.go': 'go',
'.rs': 'rust',
'.cpp': 'cpp',
'.c': 'cpp'
};
return languageMap[ext] || 'unknown';
}
extractBusinessContext(ttlContent) {
// Extract business context comments and triples
const businessContextRegex = /# Business[\s\S]*?(?=\n\n|\n@|\ncode:)/g;
const matches = ttlContent.match(businessContextRegex);
return matches ? matches.join('\n\n') : null;
}
mergeBusinessContext(newContent, existingContext) {
// Replace placeholder business context with existing context
return newContent.replace(/# Business Domain: \[BUSINESS_DOMAIN\][\s\S]*?(?=\n\n|\n@|\ncode:)/g, existingContext);
}
getTTLPath(sourceFilePath) {
const dir = path.dirname(sourceFilePath);
return path.join(dir, '.module-knowledge.ttl');
}
calculateDocumentationCoverage(results) {
const totalElements = Array.from(results.values()).reduce((sum, result) => sum + result.statistics.classCount + result.statistics.methodCount, 0);
const documentedElements = Array.from(results.values()).reduce((sum, result) => sum + result.statistics.documentationTriples, 0);
return totalElements > 0 ? Math.round((documentedElements / totalElements) * 100) : 0;
}
calculateBusinessContextCoverage(results) {
const totalModules = results.size;
const modulesWithContext = Array.from(results.values()).filter(result => result.statistics.businessContextTriples > 0).length;
return totalModules > 0 ? Math.round((modulesWithContext / totalModules) * 100) : 0;
}
calculateAverageComplexity(results) {
const complexityValues = [];
results.forEach(result => {
// Extract complexity from RDF statistics
const totalElements = result.statistics.classCount + result.statistics.methodCount;
if (totalElements > 0) {
// Calculate average complexity based on quality metrics
const avgComplexity = result.statistics.qualityMetricTriples / totalElements;
complexityValues.push(avgComplexity);
}
});
if (complexityValues.length === 0)
return 0;
const sum = complexityValues.reduce((acc, val) => acc + val, 0);
return Math.round((sum / complexityValues.length) * 100) / 100;
}
}
exports.RDFService = RDFService;
//# sourceMappingURL=RDFService.js.map