#!/usr/bin/env tsx import { readFileSync, readdirSync, statSync, existsSync } from 'fs'; import { join, relative } from 'path'; interface Export { file: string; line: number; exportName: string; exportType: 'function' | 'class' | 'interface' | 'type' | 'const' | 'enum' | 'default'; content: string; } const IGNORE_DIRS = ['node_modules', 'dist', '.next', '__tests__', '__mocks__', '.git']; const INCLUDE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx']; function walkDirectory(dir: string): string[] { const files: string[] = []; try { const items = readdirSync(dir); for (const item of items) { const fullPath = join(dir, item); const stat = statSync(fullPath); if (stat.isDirectory()) { if (!IGNORE_DIRS.includes(item)) { files.push(...walkDirectory(fullPath)); } } else if (stat.isFile()) { const ext = item.substring(item.lastIndexOf('.')); if (INCLUDE_EXTENSIONS.includes(ext)) { files.push(fullPath); } } } } catch (error) { console.error(`Error reading directory ${dir}:`, error); } return files; } function parseExports(filePath: string): Export[] { const exports: Export[] = []; try { const content = readFileSync(filePath, 'utf-8'); const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; const trimmed = line.trim(); if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed === '') { continue; } const exportMatch = trimmed.match(/^export\s+(.+)/); if (!exportMatch) continue; const exportContent = exportMatch[1]; if (exportContent.startsWith('default')) { exports.push({ file: filePath, line: i + 1, exportName: 'default', exportType: 'default', content: trimmed }); } else if (exportContent.startsWith('function')) { const funcMatch = exportContent.match(/^function\s+(\w+)/); if (funcMatch) { exports.push({ file: filePath, line: i + 1, exportName: funcMatch[1], exportType: 'function', content: trimmed }); } } else if (exportContent.startsWith('class')) { const classMatch = exportContent.match(/^class\s+(\w+)/); if (classMatch) { exports.push({ file: filePath, line: i + 1, exportName: classMatch[1], exportType: 'class', content: trimmed }); } } else if (exportContent.startsWith('interface')) { const interfaceMatch = exportContent.match(/^interface\s+(\w+)/); if (interfaceMatch) { exports.push({ file: filePath, line: i + 1, exportName: interfaceMatch[1], exportType: 'interface', content: trimmed }); } } else if (exportContent.startsWith('type')) { const typeMatch = exportContent.match(/^type\s+(\w+)/); if (typeMatch) { exports.push({ file: filePath, line: i + 1, exportName: typeMatch[1], exportType: 'type', content: trimmed }); } } else if (exportContent.startsWith('const') || exportContent.startsWith('let') || exportContent.startsWith('var')) { const varMatch = exportContent.match(/^(?:const|let|var)\s+(\w+)/); if (varMatch) { exports.push({ file: filePath, line: i + 1, exportName: varMatch[1], exportType: 'const', content: trimmed }); } } else if (exportContent.startsWith('enum')) { const enumMatch = exportContent.match(/^enum\s+(\w+)/); if (enumMatch) { exports.push({ file: filePath, line: i + 1, exportName: enumMatch[1], exportType: 'enum', content: trimmed }); } } else if (exportContent.startsWith('{')) { const namedExportMatch = exportContent.match(/^\{\s*([^}]+)\s*\}/); if (namedExportMatch) { const names = namedExportMatch[1].split(',').map(n => n.trim()); for (const name of names) { const cleanName = name.replace(/\s+as\s+\w+/, '').trim(); if (cleanName) { exports.push({ file: filePath, line: i + 1, exportName: cleanName, exportType: 'const', content: trimmed }); } } } } } } catch (error) { console.error(`Error parsing file ${filePath}:`, error); } return exports; } function isUsedInFile(exportName: string, filePath: string, exportFile: string): boolean { if (filePath === exportFile) return false; try { const content = readFileSync(filePath, 'utf-8'); const importRegex = new RegExp(`import\\s+.*\\b${exportName}\\b.*from`, 'g'); if (importRegex.test(content)) return true; const requireRegex = new RegExp(`require\\(.*\\).*\\b${exportName}\\b`, 'g'); if (requireRegex.test(content)) return true; const lines = content.split('\n'); for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith('//') || trimmed.startsWith('export')) continue; if (trimmed.includes(exportName)) return true; } return false; } catch (error) { return false; } } async function main() { const desktopDir = '/Users/michaelkantor/CascadeProjects/hashgraph-online/desktop'; const standardsDir = '/Users/michaelkantor/CascadeProjects/hashgraph-online/standards-agent-kit'; const conversationalDir = '/Users/michaelkantor/CascadeProjects/hashgraph-online/conversational-agent'; console.log('šŸ“ Gathering files...'); const desktopFiles = walkDirectory(desktopDir); const standardsFiles = existsSync(standardsDir) ? walkDirectory(standardsDir) : []; const conversationalFiles = existsSync(conversationalDir) ? walkDirectory(conversationalDir) : []; const allFiles = [...desktopFiles, ...standardsFiles, ...conversationalFiles]; console.log(`Found ${desktopFiles.length} desktop files`); console.log(`Found ${standardsFiles.length} standards-agent-kit files`); console.log(`Found ${conversationalFiles.length} conversational-agent files`); console.log('\nšŸ” Parsing exports from desktop...'); const allExports: Export[] = []; for (const file of desktopFiles) { const exports = parseExports(file); allExports.push(...exports); } console.log(`Found ${allExports.length} exports in desktop`); console.log('\nšŸ”Ž Analyzing usage (this may take a moment)...'); const unused: Export[] = []; const used: Export[] = []; let processed = 0; for (const exp of allExports) { let isUsed = false; for (const file of allFiles) { if (isUsedInFile(exp.exportName, file, exp.file)) { isUsed = true; break; } } if (isUsed) { used.push(exp); } else { unused.push(exp); } processed++; if (processed % 50 === 0) { console.log(` Processed ${processed}/${allExports.length} exports...`); } } console.log('\nšŸ“Š RESULTS:\n'); console.log(`āœ… Used exports: ${used.length}`); console.log(`āŒ Unused exports: ${unused.length}`); if (unused.length > 0) { console.log('\nšŸ—‘ļø UNUSED EXPORTS TO REMOVE:\n'); const byFile = new Map(); for (const exp of unused) { const relativePath = relative(desktopDir, exp.file); if (!byFile.has(relativePath)) { byFile.set(relativePath, []); } byFile.get(relativePath)!.push(exp); } for (const [file, exports] of byFile.entries()) { console.log(`šŸ“„ ${file} (${exports.length} unused exports)`); for (const exp of exports) { console.log(` āŒ ${exp.exportType} ${exp.exportName} (line ${exp.line})`); console.log(` ${exp.content}`); } console.log(); } } console.log('\nšŸ“‹ SUMMARY BY TYPE:\n'); const byType = new Map(); for (const exp of unused) { byType.set(exp.exportType, (byType.get(exp.exportType) || 0) + 1); } for (const [type, count] of byType.entries()) { console.log(`${type}: ${count} unused`); } } main().catch(console.error);