/** * Data Transformer * * Builds rich template data from database context. */ import fs from 'fs/promises'; import path from 'path'; import type { DatabaseClient } from '../db/client.js'; import type { TemplateData, ProjectTemplateData, ArchitectureTemplateData, KeyFilesTemplateData, CommandsTemplateData, WorkflowTemplateData, GotchaTemplateData } from './types.js'; /** * Build template data from database and project analysis * * @param db - Database client * @param projectRoot - Project root directory * @param k0ntextVersion - Current k0ntext version * @returns Complete template data */ export async function buildTemplateData( db: DatabaseClient, projectRoot: string, k0ntextVersion: string ): Promise { // Get all context items from database const allItems = db.getAllItems(); // Extract project metadata const project = await extractProjectData(projectRoot, allItems); // Extract architecture information const architecture = extractArchitectureData(allItems); // Extract key files const keyFiles = extractKeyFilesData(allItems); // Extract commands const commands = await extractCommandsData(projectRoot); // Extract workflows const workflows = extractWorkflowsData(allItems); // Extract gotchas const gotchas = extractGotchasData(allItems); // Build metadata const metadata = { generator_version: k0ntextVersion, timestamp: new Date().toISOString(), header: `Generated by k0ntext v${k0ntextVersion}` }; // Build coordination/footer const coordination = { footer: `*Generated by k0ntext v${k0ntextVersion}*`, version: k0ntextVersion, timestamp: new Date().toISOString() }; return { project, architecture, key_files: keyFiles, commands, workflows, gotchas: gotchas.length > 0 ? gotchas : undefined, metadata, coordination }; } /** * Extract project data from items and package.json */ async function extractProjectData(projectRoot: string, items: any[]): Promise { // Try to read package.json let packageData: any = {}; try { const packageJsonPath = path.join(projectRoot, 'package.json'); const packageJson = await fs.readFile(packageJsonPath, 'utf-8'); packageData = JSON.parse(packageJson); } catch { // No package.json } // Extract project info from items or package.json const projectItem = items.find(i => i.type === 'config' && i.name === 'project'); return { name: projectItem?.content?.name || packageData.name || path.basename(projectRoot), description: projectItem?.content?.description || packageData.description || '', type: projectItem?.content?.type || inferProjectType(packageData), tech_stack: extractTechStack(items, packageData), primary_language: projectItem?.content?.primary_language || inferPrimaryLanguage(packageData), version: packageData.version }; } /** * Infer project type from package.json */ function inferProjectType(packageData: any): string { if (packageData.dependencies?.next) return 'Next.js Application'; if (packageData.dependencies?.react) return 'React Application'; if (packageData.dependencies?.vue) return 'Vue Application'; if (packageData.dependencies?.express) return 'Express API'; if (packageData.dependencies?.fastify) return 'Fastify API'; if (packageData.dependencies?.['nestjs/core']) return 'NestJS Application'; return 'CLI Tool / Library'; } /** * Extract tech stack from items and package.json */ function extractTechStack(items: any[], packageData: any): string[] { const stack: string[] = []; // From package.json dependencies const deps = { ...packageData.dependencies, ...packageData.devDependencies }; if (deps.typescript) stack.push('TypeScript'); if (deps.javascript) stack.push('JavaScript'); if (deps.react || deps['react-dom']) stack.push('React'); if (deps.vue) stack.push('Vue'); if (deps.next) stack.push('Next.js'); if (deps.express) stack.push('Express'); if (deps.vitest || deps.jest) stack.push('Vitest'); if (deps.commander) stack.push('Commander.js'); // From items const codeItems = items.filter(i => i.type === 'code'); const languages = new Set(); for (const item of codeItems) { const ext = path.extname(item.filePath || ''); if (ext === '.ts' || ext === '.tsx') languages.add('TypeScript'); if (ext === '.js' || ext === '.jsx') languages.add('JavaScript'); if (ext === '.py') languages.add('Python'); if (ext === '.go') languages.add('Go'); if (ext === '.rs') languages.add('Rust'); } for (const lang of languages) { if (!stack.includes(lang)) { stack.push(lang); } } return stack.length > 0 ? stack : ['Node.js']; } /** * Infer primary language from package.json */ function inferPrimaryLanguage(packageData: any): string { if (packageData.dependencies?.typescript || packageData.devDependencies?.typescript) { return 'TypeScript'; } return 'JavaScript'; } /** * Extract architecture data from items */ function extractArchitectureData(items: any[]): ArchitectureTemplateData { const workflowItems = items.filter(i => i.type === 'workflow'); return { pattern: inferArchitecturePattern(workflowItems), layers: extractLayers(items), key_modules: extractKeyModules(items), integrations: extractIntegrations(items) }; } /** * Infer architecture pattern from workflows */ function inferArchitecturePattern(workflows: any[]): string { // Simple heuristic based on workflow names if (workflows.some(w => w.name?.includes('MVC'))) return 'MVC'; if (workflows.some(w => w.name?.includes('Layered'))) return 'Layered'; if (workflows.some(w => w.name?.includes('Micro'))) return 'Microservices'; if (workflows.some(w => w.name?.includes('Event'))) return 'Event-Driven'; return 'Modular'; } /** * Extract architectural layers */ function extractLayers(items: any[]) { const layers: ArchitectureTemplateData['layers'] = []; // Common layers to look for const commonLayers = [ { name: 'CLI', path: 'bin/' }, { name: 'Commands', path: 'src/cli/' }, { name: 'Database', path: 'src/db/' }, { name: 'Core Logic', path: 'src/' }, { name: 'Utilities', path: 'src/utils/' }, { name: 'Templates', path: 'templates/' }, { name: 'Tests', path: 'tests/' } ]; for (const layer of commonLayers) { const hasFiles = items.some(i => i.filePath?.startsWith(layer.path)); if (hasFiles) { layers.push({ name: layer.name, path: layer.path }); } } return layers; } /** * Extract key modules */ function extractKeyModules(items: any[]): string[] { return items .filter(i => i.type === 'code' && i.name) .map(i => i.name) .slice(0, 10); } /** * Extract integrations */ function extractIntegrations(items: any[]): string[] { const integrations: string[] = []; for (const item of items) { const content = item.content || ''; if (content.includes('better-sqlite3')) integrations.push('SQLite (better-sqlite3)'); if (content.includes('sqlite-vec')) integrations.push('sqlite-vec'); if (content.includes('openrouter') || content.includes('OpenRouter')) integrations.push('OpenRouter'); if (content.includes('commander')) integrations.push('Commander.js'); if (content.includes('vitest')) integrations.push('Vitest'); if (content.includes('inquirer')) integrations.push('Inquirer'); } return [...new Set(integrations)]; } /** * Extract key files data */ function extractKeyFilesData(items: any[]): KeyFilesTemplateData { return { entry_points: extractEntryPoints(items), api_routes: extractApiRoutes(items), config: extractConfigFiles(items), tests: extractTestFiles(items) }; } /** * Extract entry points */ function extractEntryPoints(items: any[]): string[] { const entryPoints: string[] = []; for (const item of items) { if (item.filePath?.includes('bin/') || item.filePath?.includes('index.') || item.filePath?.includes('main.')) { entryPoints.push(item.filePath); } } return entryPoints.slice(0, 5); } /** * Extract API routes */ function extractApiRoutes(items: any[]): string[] { const routes: string[] = []; for (const item of items) { if (item.type === 'code' && (item.filePath?.includes('routes/') || item.filePath?.includes('api/'))) { routes.push(item.filePath); } } return routes.slice(0, 5); } /** * Extract config files */ function extractConfigFiles(items: any[]): string[] { return items .filter(i => i.type === 'config' && i.filePath) .map(i => i.filePath) .slice(0, 5); } /** * Extract test files */ function extractTestFiles(items: any[]): string[] { return items .filter(i => i.type === 'code' && (i.filePath?.includes('.test.') || i.filePath?.includes('.spec.'))) .map(i => i.filePath) .slice(0, 5); } /** * Extract commands data */ async function extractCommandsData(projectRoot: string): Promise { const commands: CommandsTemplateData = {}; // Read package.json for scripts try { const packageJsonPath = path.join(projectRoot, 'package.json'); const packageJson = await fs.readFile(packageJsonPath, 'utf-8'); const packageData = JSON.parse(packageJson); const scripts = packageData.scripts || {}; commands.install = 'npm install'; commands.dev = scripts.dev || scripts.start || 'npm run dev'; commands.test = scripts.test || 'npm test'; commands.build = scripts.build || 'npm run build'; commands.lint = scripts.lint || 'npm run lint'; commands.clean = scripts.clean || 'npm run clean'; } catch { // Fallback commands commands.install = 'npm install'; commands.test = 'npm test'; commands.build = 'npm run build'; } return commands; } /** * Extract workflows data */ function extractWorkflowsData(items: any[]): WorkflowTemplateData[] { return items .filter(i => i.type === 'workflow') .slice(0, 10) .map(item => ({ name: item.name, category: item.metadata?.category, complexity: item.metadata?.complexity, description: item.content?.split('\n')[0]?.substring(0, 200) || '', files: [item.filePath].filter(Boolean) })); } /** * Extract gotchas data */ function extractGotchasData(items: any[]): GotchaTemplateData[] { return items .filter(i => i.type === 'knowledge' && (i.name?.includes('gotcha') || i.name?.includes('issue'))) .slice(0, 10) .map(item => ({ title: item.name, description: item.content?.substring(0, 500) || '', severity: item.metadata?.severity || 'medium' })); }