import * as fs from 'fs'; import * as path from 'path'; import yaml from 'js-yaml'; import { ContextOutput, TemplateContext, OutputFormat, TemplateType, ConfigOptions } from '../types'; import { getProjectInfo } from '../analyzers/projectTypeDetector'; import { analyzeDependencies } from '../analyzers/dependencyAnalyzer'; import { extractConventions } from '../analyzers/conventionExtractor'; import { getAllFiles } from '../utils/fileUtils'; import { formatContextForAI } from '../templates/templateLoader'; import { loadConfig } from '../config/configLoader'; const SOURCE_DIR_PATTERNS = ['src/', 'lib/', 'app/', 'packages/', 'internal/']; const TEST_DIR_PATTERNS = ['test/', 'tests/', '__tests__/', 'spec/', 'specs/']; export async function generateContext( directory: string, options?: Partial ): Promise { const config = loadConfig(undefined, directory); const mergedOptions = { ...config, ...options }; const projectInfo = await getProjectInfo(directory); const dependencyAnalysis = await analyzeDependencies(directory, projectInfo.type); const conventions = await extractConventions(directory, projectInfo.type); const fileStructure = await analyzeFileStructure(directory, mergedOptions); const context: ContextOutput = { version: '1.0.0', generatedAt: new Date().toISOString(), project: projectInfo, dependencies: dependencyAnalysis, conventions, structure: fileStructure, config: mergedOptions }; return context; } export async function generateFormattedContext( directory: string, format: OutputFormat = 'yaml', _templateType: TemplateType = 'generic', options?: Partial ): Promise { const context = await generateContext(directory, options); if (format === 'yaml') { return yaml.dump(context, { indent: 2, lineWidth: -1, noRefs: true }); } return JSON.stringify(context, null, 2); } export async function generateAIContext( directory: string, templateType: TemplateType = 'generic', options?: Partial ): Promise { const context = await generateContext(directory, options); const templateContext: TemplateContext = { projectInfo: context.project, dependencyAnalysis: context.dependencies, conventions: context.conventions, fileStructure: context.structure }; return formatContextForAI(templateContext, templateType); } export function saveContext( output: string, content: string, format: OutputFormat ): boolean { try { if (output === '-' || output === 'stdout') { console.log(content); return true; } let finalContent = content; if (format === 'yaml' && !output.endsWith('.yaml') && !output.endsWith('.yml')) { finalContent = '# YAML format context\n' + content; } fs.writeFileSync(output, finalContent, 'utf-8'); return true; } catch { return false; } } async function analyzeFileStructure( directory: string, options: ConfigOptions ) { const allFiles = getAllFiles(directory, 5); const directories = new Set(); const sourceDirectories = new Set(); const testDirectories = new Set(); const keyFiles: string[] = []; for (const file of allFiles) { if (file.endsWith('/')) { const relativePath = path.relative(directory, file); if (relativePath) { directories.add(relativePath); for (const pattern of SOURCE_DIR_PATTERNS) { if (relativePath.startsWith(pattern)) { sourceDirectories.add(relativePath); break; } } for (const pattern of TEST_DIR_PATTERNS) { if (relativePath.startsWith(pattern)) { testDirectories.add(relativePath); break; } } } } else { const shouldInclude = options.includes.some(pattern => { if (pattern.startsWith('**')) return true; if (pattern.startsWith('*')) return pattern.endsWith(path.extname(file)); return true; }); const shouldExclude = options.excludes.some(pattern => { if (pattern.endsWith('**')) { return file.includes(pattern.slice(0, -3)); } return file.endsWith(pattern.replace('*', '')); }); if (shouldInclude && !shouldExclude) { const relativePath = path.relative(directory, file); keyFiles.push(relativePath); } } } const sortedKeyFiles = keyFiles .sort((a, b) => { const aDepth = a.split('/').length; const bDepth = b.split('/').length; if (aDepth !== bDepth) return aDepth - bDepth; return a.localeCompare(b); }) .slice(0, 50); return { totalFiles: allFiles.filter(f => !f.endsWith('/')).length, directories: Array.from(directories).sort(), keyFiles: sortedKeyFiles, sourceDirectories: Array.from(sourceDirectories).sort(), testDirectories: Array.from(testDirectories).sort() }; } export async function validateContext(context: ContextOutput): Promise { if (!context.version) { console.error('Error: Context missing version'); return false; } if (!context.project) { console.error('Error: Context missing project info'); return false; } if (!context.generatedAt) { console.error('Error: Context missing generatedAt timestamp'); return false; } return true; }