import {Options, AIOptions} from './options' import {Prompts} from './prompts' import {Bot} from './bot' import {codeReview} from './review' import {LocalContextEngine} from './local-context' import {LocalConsoleReporter} from './console-reporter' import {feedbackTracker} from './feedback-tracker' import {execSync} from 'child_process' import {existsSync} from 'fs' interface CLIArgs { staged?: boolean diff?: string files?: string[] model?: string minConfidence?: number minSeverity?: 'critical' | 'major' | 'minor' | 'info' disableReview?: boolean disableReleaseNotes?: boolean maxFiles?: number pathFilters?: string[] systemMessage?: string language?: string output?: 'console' | 'json' help?: boolean } const HELP_TEXT = ` PRIX CLI - Local AI PR Reviewer Usage: prix-cli [options] Options: --staged Review staged files (git diff --cached) --diff Review files changed since given git ref --files Review specific files (comma-separated) --model LLM model to use (default: llama-3.1-8b-instant) --min-confidence Minimum confidence threshold (0-100) --min-severity Minimum severity to report (critical|major|minor|info) --disable-review Skip code review, only generate summary --disable-notes Skip release notes generation --max-files Maximum files to review (0 = unlimited) --path-filters

Path filters (comma-separated, e.g., "src/**,lib/**") --system-message Additional system message --language Output language (default: en-US) --output Output format: console (default) or json --help, -h Show this help message Examples: prix-cli --staged Review staged changes prix-cli --diff HEAD~1 Review changes since last commit prix-cli --files src/app.ts,lib/util.ts Review specific files prix-cli --min-severity major Only show major/critical issues Pre-commit Hook: Add to .git/hooks/pre-commit: npx prix-cli --staged --output console ` function parseArgs(argv: string[]): CLIArgs { const args: CLIArgs = {} for (let i = 2; i < argv.length; i++) { const arg = argv[i] switch (arg) { case '--staged': args.staged = true break case '--diff': args.diff = argv[++i] break case '--files': args.files = argv[++i].split(',') break case '--model': args.model = argv[++i] break case '--min-confidence': args.minConfidence = parseInt(argv[++i], 10) break case '--min-severity': args.minSeverity = argv[++i] as any break case '--disable-review': args.disableReview = true break case '--disable-notes': args.disableReleaseNotes = true break case '--max-files': args.maxFiles = parseInt(argv[++i], 10) break case '--path-filters': args.pathFilters = argv[++i].split(',') break case '--system-message': args.systemMessage = argv[++i] break case '--language': args.language = argv[++i] break case '--output': args.output = argv[++i] as any break case '--help': case '-h': args.help = true break } } return args } function getStagedFiles(): string[] { try { const output = execSync('git diff --cached --name-only', { encoding: 'utf8', cwd: process.cwd() }) return output.split('\n').filter(f => f.trim()) } catch (e) { console.error('Failed to get staged files:', e) return [] } } function getChangedFiles(sinceRef: string): string[] { try { const output = execSync(`git diff --name-only ${sinceRef}`, { encoding: 'utf8', cwd: process.cwd() }) return output.split('\n').filter(f => f.trim()) } catch (e) { console.error(`Failed to get changed files since ${sinceRef}:`, e) return [] } } async function runLocalReview(args: CLIArgs): Promise { const info = console.log const error = console.error info('šŸ” PRIX CLI - Starting local code review...\n') if (!process.env.GROQ_API_KEY && !process.env.AI_API_KEY) { error( 'āŒ Error: GROQ_API_KEY or AI_API_KEY environment variable is required' ) return 1 } let filesToReview: string[] = [] if (args.staged) { filesToReview = getStagedFiles() info(`šŸ“‹ Reviewing ${filesToReview.length} staged files\n`) } else if (args.diff) { filesToReview = getChangedFiles(args.diff) info( `šŸ“‹ Reviewing ${filesToReview.length} files changed since ${args.diff}\n` ) } else if (args.files) { filesToReview = args.files.filter(f => existsSync(f)) info(`šŸ“‹ Reviewing ${filesToReview.length} specified files\n`) } else { error('āŒ Error: Must specify --staged, --diff , or --files ') return 1 } if (filesToReview.length === 0) { info('āœ… No files to review') return 0 } const options = new Options( false, args.disableReview || false, args.disableReleaseNotes || false, args.maxFiles?.toString() || '0', false, false, args.pathFilters || null, args.systemMessage || '', args.model || 'llama-3.1-8b-instant', 'llama-3.3-70b-versatile', '0.0', '3', '120000', '6', '6', process.env.API_BASE_URL || 'https://api.groq.com/openai/v1', args.language || 'en-US', false, args.minSeverity || 'minor', args.minConfidence?.toString() || '0', 'false', 'true' ) const prompts = new Prompts() const localContext = new LocalContextEngine(filesToReview) const reporter = new LocalConsoleReporter() info('šŸ¤– Initializing AI models...') let lightBot: Bot | null = null let heavyBot: Bot | null = null try { lightBot = new Bot( options, new AIOptions( options.lightModel, options.lightTokenLimits, options.modelTemperature ) ) } catch (e) { error(`āŒ Failed to initialize light model: ${e}`) return 1 } try { heavyBot = new Bot( options, new AIOptions( options.heavyModel, options.heavyTokenLimits, options.modelTemperature ) ) } catch (e) { error(`āŒ Failed to initialize heavy model: ${e}`) return 1 } info('āœ… AI models initialized\n') try { await localContext.initialize() const findings = await localContext.runReview( lightBot, heavyBot, options, prompts ) reporter.printFindings(findings, { output: args.output || 'console', minSeverity: options.minimumSeverity, minConfidence: options.minConfidence }) const critical = findings.filter(f => f.severity === 'critical').length const major = findings.filter(f => f.severity === 'major').length info( `\nšŸ“Š Summary: ${critical} critical, ${major} major, ${findings.length} total findings` ) if (critical > 0) { info('\nāš ļø Critical issues found! Consider fixing before committing.') return 1 } else if (major > 0) { info('\nā„¹ļø Major issues found. Review recommended before committing.') return 0 } else { info('\nāœ… No significant issues found.') return 0 } } catch (e) { error(`\nāŒ Review failed: ${e}`) return 1 } } export async function main(argv: string[]): Promise { const args = parseArgs(argv) if (args.help) { console.log(HELP_TEXT) return 0 } return runLocalReview(args) } if (require.main === module) { main(process.argv) .then(code => { process.exit(code) }) .catch(e => { console.error('Fatal error:', e) process.exit(1) }) }