contaigents
Version:
Modular AI Content Ecosystem with Audio Generation
267 lines (266 loc) • 9.45 kB
JavaScript
import { FileService } from '../services/fileService.js';
import path from 'path';
import fs from 'fs/promises';
/**
* Manages project context and understanding
*/
export class ProjectContext {
constructor() {
this.contextCache = new Map();
this.fileService = new FileService();
}
/**
* Initialize project context for a given path
*/
async initializeContext(projectPath) {
const normalizedPath = path.resolve(projectPath);
// Check cache first
if (this.contextCache.has(normalizedPath)) {
const cached = this.contextCache.get(normalizedPath);
// Return cached if less than 5 minutes old
if (Date.now() - cached.metadata.lastAnalyzed < 5 * 60 * 1000) {
return cached;
}
}
try {
// Build file tree
const fileTree = await this.buildFileTree(normalizedPath);
// Analyze project metadata
const metadata = await this.analyzeProjectMetadata(normalizedPath, fileTree);
// Find dependencies
const dependencies = await this.findFileDependencies(fileTree);
const context = {
projectPath: normalizedPath,
fileTree,
dependencies,
metadata,
knowledgeBase: []
};
// Cache the context
this.contextCache.set(normalizedPath, context);
return context;
}
catch (error) {
console.error('Error initializing project context:', error);
throw error;
}
}
/**
* Build enhanced file tree with metadata
*/
async buildFileTree(projectPath) {
const tree = await this.fileService.getFileTree();
return this.enhanceFileTree(tree, projectPath);
}
/**
* Enhance file tree with metadata
*/
async enhanceFileTree(nodes, basePath) {
const enhanced = [];
for (const node of nodes) {
try {
const fullPath = path.join(basePath, node.path);
const stats = await fs.stat(fullPath);
const enhancedNode = {
path: node.path,
name: node.name,
type: node.type,
size: stats.size,
lastModified: stats.mtime.getTime()
};
if (node.type === 'file') {
enhancedNode.metadata = await this.analyzeFileMetadata(fullPath);
}
if (node.children) {
enhancedNode.children = await this.enhanceFileTree(node.children, basePath);
}
enhanced.push(enhancedNode);
}
catch (error) {
// Skip files that can't be accessed
console.warn(`Could not access ${node.path}:`, error);
}
}
return enhanced;
}
/**
* Analyze file metadata
*/
async analyzeFileMetadata(filePath) {
try {
const content = await this.fileService.readFile(filePath);
const ext = path.extname(filePath).toLowerCase();
const metadata = {
contentType: this.getContentType(ext),
lastAnalyzed: Date.now()
};
// Count lines and words for text files
if (this.isTextFile(ext)) {
const lines = content.split('\n');
metadata.lineCount = lines.length;
metadata.wordCount = content.split(/\s+/).filter(word => word.length > 0).length;
// Detect language for code files
if (this.isCodeFile(ext)) {
metadata.language = this.detectLanguage(ext);
}
// Generate summary for markdown files
if (ext === '.md' || ext === '.mdx') {
metadata.summary = this.generateMarkdownSummary(content);
}
}
return metadata;
}
catch (error) {
return {
contentType: 'unknown',
lastAnalyzed: Date.now()
};
}
}
/**
* Analyze project metadata
*/
async analyzeProjectMetadata(projectPath, fileTree) {
const projectName = path.basename(projectPath);
// Analyze file types to determine project type
const fileTypes = this.analyzeFileTypes(fileTree);
const projectType = this.determineProjectType(fileTypes);
// Look for package.json, README, etc. for more info
const description = await this.findProjectDescription(projectPath);
return {
name: projectName,
description,
type: projectType,
lastAnalyzed: Date.now()
};
}
/**
* Find file dependencies
*/
async findFileDependencies(fileTree) {
const dependencies = [];
// Simple dependency detection for now
for (const file of this.flattenFileTree(fileTree)) {
if (file.type === 'file' && file.metadata?.language) {
const deps = await this.findFileDependenciesInFile(file);
dependencies.push(...deps);
}
}
return dependencies;
}
/**
* Find dependencies in a single file
*/
async findFileDependenciesInFile(file) {
const dependencies = [];
try {
const content = await this.fileService.readFile(file.path);
// Look for import statements, requires, includes, etc.
const importPatterns = [
/import.*from\s+['"]([^'"]+)['"]/g, // ES6 imports
/require\(['"]([^'"]+)['"]\)/g, // CommonJS requires
/\[([^\]]+)\]\([^)]+\)/g, // Markdown links
];
for (const pattern of importPatterns) {
let match;
while ((match = pattern.exec(content)) !== null) {
dependencies.push({
source: file.path,
target: match[1],
type: 'import',
strength: 0.8
});
}
}
}
catch (error) {
// Skip files that can't be read
}
return dependencies;
}
/**
* Helper methods
*/
getContentType(ext) {
const types = {
'.md': 'markdown',
'.mdx': 'markdown',
'.js': 'javascript',
'.ts': 'typescript',
'.py': 'python',
'.json': 'json',
'.txt': 'text',
'.html': 'html',
'.css': 'css'
};
return types[ext] || 'unknown';
}
isTextFile(ext) {
const textExts = ['.md', '.mdx', '.js', '.ts', '.py', '.json', '.txt', '.html', '.css', '.yml', '.yaml'];
return textExts.includes(ext);
}
isCodeFile(ext) {
const codeExts = ['.js', '.ts', '.py', '.html', '.css', '.json'];
return codeExts.includes(ext);
}
detectLanguage(ext) {
const languages = {
'.js': 'javascript',
'.ts': 'typescript',
'.py': 'python',
'.html': 'html',
'.css': 'css',
'.json': 'json'
};
return languages[ext] || 'unknown';
}
generateMarkdownSummary(content) {
const lines = content.split('\n');
const firstHeading = lines.find(line => line.startsWith('#'));
const firstParagraph = lines.find(line => line.trim().length > 0 && !line.startsWith('#'));
return [firstHeading, firstParagraph].filter(Boolean).join(' - ').substring(0, 200);
}
analyzeFileTypes(fileTree) {
const types = {};
for (const file of this.flattenFileTree(fileTree)) {
if (file.type === 'file' && file.metadata?.contentType) {
types[file.metadata.contentType] = (types[file.metadata.contentType] || 0) + 1;
}
}
return types;
}
determineProjectType(fileTypes) {
const total = Object.values(fileTypes).reduce((sum, count) => sum + count, 0);
const markdownRatio = (fileTypes.markdown || 0) / total;
const codeRatio = ((fileTypes.javascript || 0) + (fileTypes.typescript || 0) + (fileTypes.python || 0)) / total;
if (markdownRatio > 0.7)
return 'documentation';
if (codeRatio > 0.5)
return 'code';
if (markdownRatio > 0.3)
return 'content';
return 'mixed';
}
async findProjectDescription(projectPath) {
try {
const readmePath = path.join(projectPath, 'README.md');
const content = await this.fileService.readFile(readmePath);
const lines = content.split('\n');
const descLine = lines.find(line => line.trim().length > 0 && !line.startsWith('#'));
return descLine?.substring(0, 200);
}
catch {
return undefined;
}
}
flattenFileTree(nodes) {
const flattened = [];
for (const node of nodes) {
flattened.push(node);
if (node.children) {
flattened.push(...this.flattenFileTree(node.children));
}
}
return flattened;
}
}