/** * pi-refactor — Iterative refactoring for Pi * * Analyze → Refactor → Verify cycle with git commits per pass. * Auto-reverts on verify failure. * * Commands: * /refactor [--passes N] [--verify ] * /refactor status * /refactor stop */ import type { ExtensionAPI } from '@anthropic-ai/claude-code' import { execSync } from 'child_process' interface RefactorState { active: boolean goal: string verifyCmd: string maxPasses: number currentPass: number phase: 'analyze' | 'refactor' | 'verify' | 'idle' startedAt: number successes: number failures: number } const state: RefactorState = { active: false, goal: '', verifyCmd: 'npm test', maxPasses: 5, currentPass: 0, phase: 'idle', startedAt: 0, successes: 0, failures: 0, } function reset() { state.active = false; state.goal = ''; state.phase = 'idle' state.currentPass = 0; state.successes = 0; state.failures = 0 } function runVerify(): { passed: boolean; output: string } { try { const out = execSync(state.verifyCmd, { encoding: 'utf-8', timeout: 120000, stdio: 'pipe' }) return { passed: true, output: out.slice(-500) } } catch (err: any) { return { passed: false, output: (err.stderr || err.stdout || err.message || '').slice(-500) } } } function gitCommit(message: string): boolean { try { execSync('git add -A', { stdio: 'pipe', timeout: 10000 }) execSync(`git commit -m "${message.replace(/"/g, '\\"')}" --allow-empty`, { stdio: 'pipe', timeout: 10000 }) return true } catch { return false } } function gitRevert(): boolean { try { execSync('git checkout -- .', { stdio: 'pipe', timeout: 10000 }) execSync('git clean -fd', { stdio: 'pipe', timeout: 10000 }) return true } catch { return false } } function formatStatus(): string { if (!state.active) return 'No active refactoring. Use `/refactor `.' const elapsed = Math.round((Date.now() - state.startedAt) / 1000) return [ '## Refactor Status', '', `**Goal:** ${state.goal}`, `**Phase:** ${state.phase}`, `**Pass:** ${state.currentPass}/${state.maxPasses}`, `**Verify:** \`${state.verifyCmd}\``, `**Results:** ${state.successes} success, ${state.failures} reverted`, `**Elapsed:** ${elapsed}s`, ].join('\n') } export default function init(pi: ExtensionAPI) { pi.on('agent_end', (event: any) => { if (!state.active) return if (state.phase === 'analyze') { // Agent analyzed — now refactor state.phase = 'refactor' pi.sendMessage({ content: `[Refactor pass ${state.currentPass}/${state.maxPasses}] Now apply the refactoring changes for: **${state.goal}**\n\nMake the code changes. After you're done, I'll run verification.`, display: true, }, { triggerTurn: true }) return } if (state.phase === 'refactor') { // Agent refactored — now verify state.phase = 'verify' const result = runVerify() if (result.passed) { state.successes++ gitCommit(`refactor(pass-${state.currentPass}): ${state.goal}`) state.currentPass++ if (state.currentPass > state.maxPasses) { const total = state.successes reset() pi.sendMessage({ content: `✅ **Refactoring complete!** ${total} successful passes committed.\n\n\`\`\`\n${result.output}\n\`\`\``, display: true, }, { triggerTurn: false }) return } // Next pass state.phase = 'analyze' pi.sendMessage({ content: `✅ **Pass ${state.currentPass - 1} passed!** Committed.\n\n\`\`\`\n${result.output}\n\`\`\`\n\n[Analyze pass ${state.currentPass}/${state.maxPasses}] Look for the next improvement opportunity for: **${state.goal}**`, display: true, }, { triggerTurn: true }) } else { state.failures++ gitRevert() pi.sendMessage({ content: `❌ **Verify failed — reverted.**\n\n\`\`\`\n${result.output}\n\`\`\`\n\n[Retry pass ${state.currentPass}/${state.maxPasses}] Try a different approach for: **${state.goal}**`, display: true, }, { triggerTurn: true }) state.phase = 'analyze' } } }) pi.addCommand({ name: 'refactor', description: 'Iterative refactoring — analyze → refactor → verify with git commits', handler: async (args) => { const parts = args.trim() if (!parts || parts === 'status') { pi.sendMessage({ content: formatStatus(), display: true }, { triggerTurn: false }); return } if (parts === 'stop') { const passes = state.currentPass reset() pi.sendMessage({ content: `Refactoring stopped after ${passes} passes.`, display: true }, { triggerTurn: false }); return } // Parse flags let goal = parts let maxPasses = 5 let verifyCmd = 'npm test' const passMatch = goal.match(/--passes\s+(\d+)/) if (passMatch) { maxPasses = parseInt(passMatch[1], 10); goal = goal.replace(/--passes\s+\d+/, '').trim() } const verifyMatch = goal.match(/--verify\s+"([^"]+)"/) || goal.match(/--verify\s+(\S+)/) if (verifyMatch) { verifyCmd = verifyMatch[1]; goal = goal.replace(/--verify\s+"[^"]+"/, '').replace(/--verify\s+\S+/, '').trim() } if (!goal) { pi.sendMessage({ content: 'Usage: /refactor [--passes N] [--verify "cmd"]', display: true }, { triggerTurn: false }); return } reset() state.active = true; state.goal = goal; state.maxPasses = maxPasses state.verifyCmd = verifyCmd; state.currentPass = 1 state.phase = 'analyze'; state.startedAt = Date.now() pi.sendMessage({ content: [ `🔄 **Refactoring started**`, ``, `**Goal:** ${goal}`, `**Passes:** ${maxPasses}`, `**Verify:** \`${verifyCmd}\``, ``, `[Analyze pass 1/${maxPasses}] Analyze the codebase and identify the first refactoring opportunity for: **${goal}**`, ].join('\n'), display: true, }, { triggerTurn: true }) }, }) pi.addTool({ name: 'refactor_status', description: 'Show current refactoring loop status.', parameters: { type: 'object', properties: {} }, handler: async () => formatStatus(), }) }