diff --git a/tools/refactoring/error-as-todo/parse-args.ts b/tools/refactoring/error-as-todo/parse-args.ts new file mode 100644 index 000000000..a8afba710 --- /dev/null +++ b/tools/refactoring/error-as-todo/parse-args.ts @@ -0,0 +1,42 @@ +import { Priority } from './types' + +export interface ParsedArgs { + dryRun: boolean + verbose: boolean + limit?: number + priority?: Priority + showHelp: boolean +} + +function printHelp() { + console.log('Error-as-TODO Refactoring Runner\n') + console.log('Treats all errors as actionable TODO items!\n') + console.log('Usage: tsx error-as-todo-refactor.ts [options] [priority]\n') + console.log('Options:') + console.log(' -d, --dry-run Preview without writing') + console.log(' -v, --verbose Show detailed output') + console.log(' --limit=N Process only N files') + console.log(' high|medium|low Filter by priority') + console.log(' -h, --help Show help\n') + console.log('Examples:') + console.log(' tsx error-as-todo-refactor.ts high --limit=5') + console.log(' tsx error-as-todo-refactor.ts --dry-run medium') +} + +export function parseArgs(rawArgs: string[]): ParsedArgs { + const args = rawArgs.slice() + const showHelp = args.includes('--help') || args.includes('-h') + + if (showHelp) { + printHelp() + return { dryRun: false, verbose: false, showHelp: true } + } + + const dryRun = args.includes('--dry-run') || args.includes('-d') + const verbose = args.includes('--verbose') || args.includes('-v') + const limitArg = args.find(a => a.startsWith('--limit=')) + const limit = limitArg ? parseInt(limitArg.split('=')[1], 10) : undefined + const priority = args.find(a => ['high', 'medium', 'low', 'all'].includes(a)) as Priority | undefined + + return { dryRun, verbose, limit, priority, showHelp } +} diff --git a/tools/refactoring/error-as-todo/reporting.ts b/tools/refactoring/error-as-todo/reporting.ts new file mode 100644 index 000000000..23374cfce --- /dev/null +++ b/tools/refactoring/error-as-todo/reporting.ts @@ -0,0 +1,97 @@ +import * as fs from 'fs/promises' +import * as path from 'path' +import { RefactorSession, TodoItem } from './types' + +const PROGRESS_PATH = path.join(process.cwd(), 'docs/todo/LAMBDA_REFACTOR_PROGRESS.md') +const TODO_MARKDOWN_PATH = path.join(process.cwd(), 'docs/todo/REFACTOR_TODOS.md') +const TODO_JSON_PATH = path.join(process.cwd(), 'docs/todo/REFACTOR_TODOS.json') + +type TodoLogger = (todo: TodoItem) => void +const sectionDivider = '\n## ' + +export async function loadFilesFromReport(addTodo: TodoLogger): Promise { + try { + const content = await fs.readFile(PROGRESS_PATH, 'utf-8') + const files: string[] = [] + for (const line of content.split('\n')) { + if (line.includes('### Skipped')) break + const match = line.match(/- \[ \] `([^`]+)`/) + if (match) files.push(match[1]) + } + return files + } catch (error) { + addTodo({ + file: PROGRESS_PATH, + category: 'parse_error', + severity: 'high', + message: 'Could not load progress report - run refactor-to-lambda.ts first', + suggestion: 'npx tsx tools/refactoring/cli/refactor-to-lambda.ts' + }) + return [] + } +} + +export function generateTodoReport(todos: TodoItem[]): string { + const byCategory = todos.reduce>((acc, todo) => ({ ...acc, [todo.category]: (acc[todo.category] || 0) + 1 }), {}) + const bySeverity = todos.reduce>((acc, todo) => ({ ...acc, [todo.severity]: (acc[todo.severity] || 0) + 1 }), {}) + const append = (line: string) => (report += line) + let report = '# Lambda Refactoring TODO List\n\n' + append(`**Generated:** ${new Date().toISOString()}\n\n`) + append('## Summary\n\n') + append(`**Philosophy:** Errors are good - they're our TODO list! ๐ŸŽฏ\n\n`) + append(`- Total items: ${todos.length}\n- ๐Ÿ”ด High priority: ${bySeverity.high || 0}\n- ๐ŸŸก Medium priority: ${bySeverity.medium || 0}\n- ๐ŸŸข Low priority: ${bySeverity.low || 0}\n- ๐Ÿ’ก Successes: ${bySeverity.info || 0}\n\n`) + + append('## By Category\n\n') + for (const [category, count] of Object.entries(byCategory).sort((a, b) => b[1] - a[1])) { + const emoji = { parse_error: '๐Ÿ”ง', type_error: '๐Ÿ“˜', import_error: '๐Ÿ“ฆ', test_failure: '๐Ÿงช', lint_warning: 'โœจ', manual_fix_needed: '๐Ÿ‘ท', success: 'โœ…' }[category] || '๐Ÿ“‹' + append(`- ${emoji} ${category.replace(/_/g, ' ')}: ${count}\n`) + } + + for (const severity of ['high', 'medium', 'low', 'info'] as const) { + const items = todos.filter(t => t.severity === severity) + if (items.length === 0) continue + const emoji = { high: '๐Ÿ”ด', medium: '๐ŸŸก', low: '๐ŸŸข', info: '๐Ÿ’ก' }[severity] + append(`${sectionDivider}${emoji} ${severity.toUpperCase()} Priority\n\n`) + const byFile = items.reduce>((acc, todo) => ({ ...acc, [todo.file]: [...(acc[todo.file] || []), todo] }), {}) + for (const [file, todosForFile] of Object.entries(byFile)) { + append(`### \`${file}\`\n\n`) + todosForFile.forEach(todo => { + const categoryEmoji = { parse_error: '๐Ÿ”ง', type_error: '๐Ÿ“˜', import_error: '๐Ÿ“ฆ', test_failure: '๐Ÿงช', lint_warning: 'โœจ', manual_fix_needed: '๐Ÿ‘ท', success: 'โœ…' }[todo.category] || '๐Ÿ“‹' + append(`- [ ] ${categoryEmoji} **${todo.category.replace(/_/g, ' ')}**: ${todo.message}\n`) + if (todo.location) append(` - ๐Ÿ“ Location: \`${todo.location}\`\n`) + if (todo.suggestion) append(` - ๐Ÿ’ก Suggestion: ${todo.suggestion}\n`) + if (todo.relatedFiles && todo.relatedFiles.length > 0) append(` - ๐Ÿ“ Related files: ${todo.relatedFiles.length} files created\n`) + append('\n') + }) + } + } + + append(`${sectionDivider}Quick Fixes\n\n`) + append( + `### For "this" references:\n\`\`\`typescript\nconst result = this.helperMethod()\n\nimport { helperMethod } from './helper-method'\nconst result = helperMethod()\n\`\`\`\n\n` + + '### For import cleanup:\n```bash\nnpm run lint:fix\n```\n\n' + + '### For type errors:\n```bash\nnpm run typecheck\n```\n\n' + ) + + append('## Next Steps\n\n') + append(`1. Address high-priority items first (${bySeverity.high || 0} items)\n2. Fix "this" references in extracted functions\n3. Run \`npm run lint:fix\` to clean up imports\n4. Run \`npm run typecheck\` to verify types\n5. Run \`npm run test:unit\` to verify functionality\n6. Commit working batches incrementally\n\n`) + append('## Remember\n\n**Errors are good!** They\'re not failures - they\'re a TODO list telling us exactly what needs attention. โœจ\n') + return report +} + +export function buildSession(filesProcessed: number, todos: TodoItem[]): RefactorSession { + return { + timestamp: new Date().toISOString(), + filesProcessed, + successCount: todos.filter(t => t.category === 'success').length, + todosGenerated: todos.filter(t => t.category !== 'success').length, + todos + } +} + +export async function writeReports(session: RefactorSession, markdown: string): Promise { + await fs.writeFile(TODO_MARKDOWN_PATH, markdown, 'utf-8') + await fs.writeFile(TODO_JSON_PATH, JSON.stringify(session, null, 2), 'utf-8') + console.log(`โœ… TODO report saved: ${TODO_MARKDOWN_PATH}`) + console.log(`โœ… JSON data saved: ${TODO_JSON_PATH}`) +} diff --git a/tools/refactoring/error-as-todo/runner.ts b/tools/refactoring/error-as-todo/runner.ts new file mode 100644 index 000000000..9788f9c86 --- /dev/null +++ b/tools/refactoring/error-as-todo/runner.ts @@ -0,0 +1,126 @@ +import * as fs from 'fs/promises' +import { MultiLanguageLambdaRefactor } from '../multi-lang-refactor' +import { RunnerOptions, TodoItem } from './types' + +type TodoLogger = (todo: TodoItem) => void +interface RunnerContext extends RunnerOptions { addTodo: TodoLogger } +const createLogger = (verbose: boolean) => (message: string) => verbose && console.log(message) + +async function detectPostRefactorIssues(files: string[], addTodo: TodoLogger, logVerbose: (message: string) => void) { + logVerbose(' ๐Ÿ” Checking for common issues...') + for (const file of files) { + if (!file.endsWith('.ts')) continue + try { + const content = await fs.readFile(file, 'utf-8') + const checks: Array<[boolean, TodoItem]> = [ + [ + content.includes('this.'), + { + file, + category: 'manual_fix_needed', + severity: 'high', + message: 'Contains "this" reference - needs manual conversion from class method to function', + location: file, + suggestion: 'Replace "this.methodName" with direct function calls or pass data as parameters' + } + ], + [ + content.includes('import') && content.split('import').length > 10, + { + file, + category: 'import_error', + severity: 'low', + message: 'Many imports detected - may need optimization', + suggestion: 'Review imports and remove unused ones' + } + ], + [ + content.split('\n').length > 100, + { + file, + category: 'manual_fix_needed', + severity: 'medium', + message: 'Extracted function is still large - may need further breakdown', + suggestion: 'Consider breaking into smaller functions' + } + ] + ] + checks.filter(([condition]) => condition).forEach(([, todo]) => addTodo(todo)) + } catch (error) { + addTodo({ + file, + category: 'parse_error', + severity: 'medium', + message: `Unexpected issue reading file: ${error instanceof Error ? error.message : String(error)}`, + suggestion: 'Verify generated file is accessible' + }) + } + } +} + +async function refactorWithTodoCapture(context: RunnerContext, addTodo: TodoLogger) { + const { files, dryRun, limit, verbose } = context + const logVerbose = createLogger(verbose) + const targetFiles = limit ? files.slice(0, limit) : files + const refactor = new MultiLanguageLambdaRefactor({ dryRun, verbose: false }) + console.log('๐ŸŽฏ Error-as-TODO Refactoring Runner') + console.log(' Philosophy: Errors are good - they tell us what to fix!\n') + console.log(` Mode: ${dryRun ? '๐Ÿ” DRY RUN' : 'โšก LIVE'}`) + console.log(` Files: ${targetFiles.length}\n`) + + for (let i = 0; i < targetFiles.length; i++) { + const file = targetFiles[i] + console.log(`\n[${i + 1}/${targetFiles.length}] ๐Ÿ“ ${file}`) + try { + try { + await fs.access(file) + } catch { + addTodo({ file, category: 'parse_error', severity: 'low', message: 'File not found - may have been moved or deleted', suggestion: 'Update progress report or verify file location' }) + continue + } + + const result = await refactor.refactorFile(file) + if (result.success) { + console.log(' โœ… Refactored successfully') + addTodo({ file, category: 'success', severity: 'info', message: `Successfully refactored into ${result.newFiles.length} files`, relatedFiles: result.newFiles }) + } else if (result.errors.some(e => e.includes('skipping'))) { + console.log(' โญ๏ธ Skipped (not enough functions)') + addTodo({ file, category: 'manual_fix_needed', severity: 'low', message: 'File has < 3 functions - manual refactoring may not be needed', suggestion: 'Review file to see if refactoring would add value' }) + } else { + console.log(' โš ๏ธ Encountered issues') + result.errors.forEach(error => + addTodo({ file, category: 'parse_error', severity: 'medium', message: error, suggestion: 'May need manual intervention or tool improvement' }) + ) + } + + if (result.success && !dryRun) await detectPostRefactorIssues(result.newFiles, addTodo, logVerbose) + } catch (error) { + console.log(' โŒ Error occurred') + addTodo({ file, category: 'parse_error', severity: 'high', message: `Unexpected error: ${error instanceof Error ? error.message : String(error)}`, suggestion: 'Report this error for tool improvement' }) + } + await new Promise(resolve => setTimeout(resolve, 50)) + } +} + +export async function runRefactorSession(context: RunnerContext): Promise { + const todos: TodoItem[] = [] + const forwardTodo: TodoLogger = todo => { + todos.push(todo) + context.addTodo(todo) + } + await refactorWithTodoCapture({ ...context, addTodo: forwardTodo }, forwardTodo) + + console.log('\n' + '='.repeat(60)) + console.log('๐Ÿ“Š SESSION SUMMARY') + console.log('='.repeat(60)) + const filesProcessed = context.limit ? Math.min(context.limit, context.files.length) : context.files.length + const countBy = (predicate: (todo: TodoItem) => boolean) => todos.filter(predicate).length + console.log(`Files processed: ${filesProcessed}`) + console.log(`โœ… Successes: ${countBy(t => t.category === 'success')}`) + console.log(`๐Ÿ“‹ TODOs generated: ${countBy(t => t.category !== 'success')}`) + console.log(` ๐Ÿ”ด High: ${countBy(t => t.severity === 'high')}`) + console.log(` ๐ŸŸก Medium: ${countBy(t => t.severity === 'medium')}`) + console.log(` ๐ŸŸข Low: ${countBy(t => t.severity === 'low')}`) + console.log('\n๐Ÿ’ก Remember: Errors are good! They tell us exactly what to fix.') + return todos +} diff --git a/tools/refactoring/error-as-todo/types.ts b/tools/refactoring/error-as-todo/types.ts new file mode 100644 index 000000000..ec12c1a52 --- /dev/null +++ b/tools/refactoring/error-as-todo/types.ts @@ -0,0 +1,37 @@ +export type TodoCategory = + | 'parse_error' + | 'type_error' + | 'import_error' + | 'test_failure' + | 'lint_warning' + | 'manual_fix_needed' + | 'success' + +export type TodoSeverity = 'high' | 'medium' | 'low' | 'info' + +export interface TodoItem { + file: string + category: TodoCategory + severity: TodoSeverity + message: string + location?: string + suggestion?: string + relatedFiles?: string[] +} + +export interface RefactorSession { + timestamp: string + filesProcessed: number + successCount: number + todosGenerated: number + todos: TodoItem[] +} + +export interface RunnerOptions { + files: string[] + dryRun: boolean + verbose: boolean + limit?: number +} + +export type Priority = 'high' | 'medium' | 'low' | 'all'