| "use strict"; |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.main = main; |
| const options_1 = require("./options"); |
| const prompts_1 = require("./prompts"); |
| const bot_1 = require("./bot"); |
| const local_context_1 = require("./local-context"); |
| const console_reporter_1 = require("./console-reporter"); |
| const child_process_1 = require("child_process"); |
| const fs_1 = require("fs"); |
| const HELP_TEXT = ` |
| PRIX CLI - Local AI PR Reviewer |
| |
| Usage: |
| prix-cli [options] |
| |
| Options: |
| --staged Review staged files (git diff --cached) |
| --diff <ref> Review files changed since given git ref |
| --files <paths> Review specific files (comma-separated) |
| --model <name> LLM model to use (default: llama-3.1-8b-instant) |
| --min-confidence <n> Minimum confidence threshold (0-100) |
| --min-severity <lvl> Minimum severity to report (critical|major|minor|info) |
| --disable-review Skip code review, only generate summary |
| --disable-notes Skip release notes generation |
| --max-files <n> Maximum files to review (0 = unlimited) |
| --path-filters <p> Path filters (comma-separated, e.g., "src/**,lib/**") |
| --system-message <m> Additional system message |
| --language <lang> Output language (default: en-US) |
| --output <fmt> 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) { |
| const args = {}; |
| 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]; |
| 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]; |
| break; |
| case '--help': |
| case '-h': |
| args.help = true; |
| break; |
| } |
| } |
| return args; |
| } |
| function getStagedFiles() { |
| try { |
| const output = (0, child_process_1.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) { |
| try { |
| const output = (0, child_process_1.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) { |
| 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 = []; |
| 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 => (0, fs_1.existsSync)(f)); |
| info(`π Reviewing ${filesToReview.length} specified files\n`); |
| } |
| else { |
| error('β Error: Must specify --staged, --diff <ref>, or --files <paths>'); |
| return 1; |
| } |
| if (filesToReview.length === 0) { |
| info('β
No files to review'); |
| return 0; |
| } |
| const options = new options_1.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_1.Prompts(); |
| const localContext = new local_context_1.LocalContextEngine(filesToReview); |
| const reporter = new console_reporter_1.LocalConsoleReporter(); |
| info('π€ Initializing AI models...'); |
| let lightBot = null; |
| let heavyBot = null; |
| try { |
| lightBot = new bot_1.Bot(options, new options_1.AIOptions(options.lightModel, options.lightTokenLimits, options.modelTemperature)); |
| } |
| catch (e) { |
| error(`β Failed to initialize light model: ${e}`); |
| return 1; |
| } |
| try { |
| heavyBot = new bot_1.Bot(options, new options_1.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; |
| } |
| } |
| async function main(argv) { |
| 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); |
| }); |
| } |
| |