"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const options_1 = require("./src/options"); const prompts_1 = require("./src/prompts"); const review_1 = require("./src/review"); const review_comment_1 = require("./src/review-comment"); const context_1 = require("./src/context"); const octokit_1 = require("./src/octokit"); const checkout_1 = require("./src/checkout"); const promises_1 = require("fs/promises"); const path_1 = require("path"); const os_1 = require("os"); const feedback_capture_1 = require("./src/feedback-capture"); const feedback_tracker_1 = require("./src/feedback-tracker"); const lru_cache_1 = require("lru-cache"); const pino_1 = __importDefault(require("pino")); const zod_1 = require("zod"); const prom_client_1 = __importDefault(require("prom-client")); const bot_1 = require("./src/bot"); const options_2 = require("./src/options"); const issue_handler_1 = require("./src/issue-handler"); // Environment variable validation schema const envSchema = zod_1.z.object({ GROQ_API_KEY: zod_1.z.string().min(1, 'GROQ_API_KEY is required'), AI_API_KEY: zod_1.z.string().optional(), DEBUG: zod_1.z.enum(['true', 'false']).default('false'), DISABLE_REVIEW: zod_1.z.enum(['true', 'false']).default('false'), MAX_FILES: zod_1.z.coerce.number().min(0).max(1000).default(0), REVIEW_SIMPLE_CHANGES: zod_1.z.enum(['true', 'false']).default('false'), REVIEW_COMMENT_LGTM: zod_1.z.enum(['true', 'false']).default('false'), PATH_FILTERS: zod_1.z.string().optional(), SYSTEM_MESSAGE: zod_1.z.string().default(''), LIGHT_MODEL: zod_1.z.string().default('meta-llama/llama-4-scout-17b-16e-instruct'), HEAVY_MODEL: zod_1.z.string().default('llama-3.3-70b-versatile'), MODEL_TEMPERATURE: zod_1.z.coerce.number().min(0).max(2).default(0), RETRIES: zod_1.z.coerce.number().min(0).max(10).default(3), TIMEOUT_MS: zod_1.z.coerce.number().min(1000).max(300000).default(120000), CONCURRENCY_LIMIT: zod_1.z.coerce.number().min(1).max(10).default(1), GITHUB_CONCURRENCY_LIMIT: zod_1.z.coerce.number().min(1).max(10).default(1), API_BASE_URL: zod_1.z.string().url().default('https://api.groq.com/openai/v1'), LANGUAGE: zod_1.z.string().default('en-US'), MINIMUM_SEVERITY: zod_1.z.enum(['critical', 'major', 'minor', 'info']).default('minor'), MIN_CONFIDENCE: zod_1.z.coerce.number().min(0).max(100).default(0), DENSE_MODE: zod_1.z.enum(['true', 'false']).default('false'), SHOW_IMPACT_ANALYSIS: zod_1.z.enum(['true', 'false']).default('true'), LOG_LEVEL: zod_1.z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']).default('info') }); // Validate env vars on startup try { envSchema.parse(process.env); } catch (error) { console.error('Invalid environment variables:', error); process.exit(1); } const logger = (0, pino_1.default)({ level: process.env.LOG_LEVEL || 'info' }); // Webhook rate limiter: max 30 requests per repo per minute const webhookRateLimiter = new lru_cache_1.LRUCache({ max: 1000, // Track 1000 repos ttl: 60000, // 1 minute window updateAgeOnGet: false }); function checkRateLimit(repo) { const current = webhookRateLimiter.get(repo) || 0; if (current >= 30) { logger.warn({ repo, count: current }, 'Webhook rate limit exceeded'); return false; } webhookRateLimiter.set(repo, current + 1); return true; } /** * PRIX (AI PR Reviewer and Auditor) * Probot Service Entry Point */ exports.default = (app, { getRouter }) => { app.log.info('PRIX module loading...'); // 0. Health Check for Hugging Face if (getRouter) { const router = getRouter(); router.get('/', (_req, res) => { res.status(200).send(` PRIX | AI Code Auditor

PRIX AUDITOR

Enterprise-grade AI Code Security & Symbol Engine is humming.

SERVICE ACTIVE
`); }); router.get('/ping', (_req, res) => { res.status(200).send('pong'); }); // Deep health check with external service validation router.get('/health', async (_req, res) => { const checks = {}; // 1. Check GitHub App configuration try { const appId = process.env.APP_ID; checks.github = { ok: !!appId }; } catch (error) { checks.github = { ok: false, error: error.message }; } // 2. Check AI API connectivity try { const apiKey = process.env.GROQ_API_KEY || process.env.AI_API_KEY; if (!apiKey) { checks.ai = { ok: false, error: 'No API key configured' }; } else { // Simple check - verify key exists (don't make actual API call to avoid costs) checks.ai = { ok: true }; } } catch (error) { checks.ai = { ok: false, error: error.message }; } // 3. Check disk space (basic) try { const tempDir = (0, os_1.tmpdir)(); checks.disk = { ok: !!tempDir }; } catch (error) { checks.disk = { ok: false, error: error.message }; } // 4. Check memory usage const memUsage = process.memoryUsage(); checks.memory = { ok: memUsage.heapUsed < 1024 * 1024 * 1024, // Less than 1GB }; const allOk = Object.values(checks).every(c => c.ok); res.status(allOk ? 200 : 503).json({ status: allOk ? 'healthy' : 'unhealthy', checks, timestamp: new Date().toISOString(), version: process.env.npm_package_version || '1.1.0' }); }); // Prometheus metrics endpoint const register = new prom_client_1.default.Registry(); prom_client_1.default.collectDefaultMetrics({ register }); // Custom metrics const prReviewCounter = new prom_client_1.default.Counter({ name: 'prix_pr_reviews_total', help: 'Total number of PRs reviewed', labelNames: ['status'], registers: [register] }); const reviewDurationHistogram = new prom_client_1.default.Histogram({ name: 'prix_review_duration_seconds', help: 'Time spent reviewing PRs', buckets: [10, 30, 60, 120, 300, 600], registers: [register] }); router.get('/metrics', async (_req, res) => { try { res.set('Content-Type', register.contentType); res.end(await register.metrics()); } catch (ex) { res.status(500).end(ex.message); } }); } app.log.info('PRIX Service Started'); function parseSeverityLevel(value) { const validLevels = [ 'critical', 'major', 'minor', 'info' ]; if (value && validLevels.includes(value)) { return value; } return 'minor'; } // Helper to initialize options from environment variables const getOptions = () => { return new options_1.Options(process.env.DEBUG === 'true', process.env.DISABLE_REVIEW === 'true', process.env.DISABLE_RELEASE_NOTES === 'true', process.env.MAX_FILES || '0', process.env.REVIEW_SIMPLE_CHANGES === 'true', process.env.REVIEW_COMMENT_LGTM === 'true', process.env.PATH_FILTERS ? process.env.PATH_FILTERS.split(',') : null, process.env.SYSTEM_MESSAGE || '', process.env.LIGHT_MODEL || 'meta-llama/llama-4-scout-17b-16e-instruct', process.env.HEAVY_MODEL || 'llama-3.3-70b-versatile', process.env.MODEL_TEMPERATURE || '0.0', process.env.RETRIES || '3', process.env.TIMEOUT_MS || '120000', process.env.CONCURRENCY_LIMIT || '1', process.env.GITHUB_CONCURRENCY_LIMIT || '1', process.env.API_BASE_URL || 'https://api.groq.com/openai/v1', process.env.LANGUAGE || 'en-US', true, parseSeverityLevel(process.env.MINIMUM_SEVERITY), process.env.MIN_CONFIDENCE || '0', process.env.DENSE_MODE || 'false', process.env.SHOW_IMPACT_ANALYSIS || 'true', process.env.ENABLE_AUTO_PR === 'true'); }; // 1. Event Listeners for PR Audit app.on([ 'pull_request.opened', 'pull_request.synchronize', 'pull_request.reopened' ], async (context) => { const { payload } = context; // Skip audits for PRs opened by bots to prevent recursive loops const user = payload.pull_request.user; if (user?.type === 'Bot') { app.log.info(`Skipping audit for PR #${payload.pull_request.number} because it was opened by a bot (${user.login})`); return; } // Rate limiting: max 30 webhooks per repo per minute const repoKey = `${context.repo().owner}/${context.repo().repo}`; if (!checkRateLimit(repoKey)) { logger.warn({ repo: repoKey, pr: payload.pull_request.number }, 'Rate limit exceeded, skipping audit'); return; } app.log.info(`Received ${context.name}.${payload.action} for PR #${payload.pull_request.number}`); try { const options = getOptions(); const prompts = new prompts_1.Prompts(); // Create a unique temporary directory for this audit in the system temp folder const tempDir = (0, path_1.join)((0, os_1.tmpdir)(), `prix_tmp_${payload.pull_request.id}_${Date.now()}`); // Create a safe, isolated execution context await context_1.als.run({ probotContext: context, octokit: context.octokit, repo: context.repo(), workingDir: tempDir }, async () => { try { // Clone the repo locally so Git commands work await (0, checkout_1.checkoutRepo)(tempDir); // Initialize shims (0, octokit_1.setOctokit)(context.octokit); // Trigger the main PRIX Audit await (0, review_1.run)(context, options, prompts); } finally { // Cleanup temp directory (async non-blocking) try { await (0, promises_1.rm)(tempDir, { recursive: true, force: true }); logger.debug({ tempDir }, 'Temp directory cleaned up'); } catch (cleanupErr) { logger.error({ tempDir, error: cleanupErr }, 'Failed to cleanup temp directory'); } } }); app.log.info(`Successfully processed audit for PR #${payload.pull_request.number}`); } catch (err) { app.log.error(`Failed to process audit for PR #${payload.pull_request.number}: ${err.message}`); // Log specific reason without crashing as per requirements } }); // 1b. Event Listener for Autonomous PR (Push to default branch) app.on('push', async (context) => { const { payload } = context; // Check if push is to default branch const defaultBranch = payload.repository.default_branch; const refBranch = payload.ref.replace('refs/heads/', ''); if (refBranch !== defaultBranch) { app.log.info(`Skipping autonomous audit on non-default branch: ${refBranch}`); return; } app.log.info(`Received push event on default branch ${defaultBranch}, triggering autonomous audit...`); try { const options = getOptions(); if (!options.enableAutoPR) { app.log.info('Auto PR is disabled, skipping autonomous audit.'); return; } const prompts = new prompts_1.Prompts(); const tempDir = (0, path_1.join)((0, os_1.tmpdir)(), `prix_auto_pr_${payload.after}_${Date.now()}`); await context_1.als.run({ probotContext: context, octokit: context.octokit, repo: context.repo(), workingDir: tempDir }, async () => { try { await (0, checkout_1.checkoutRepo)(tempDir, { ref: payload.after }); (0, octokit_1.setOctokit)(context.octokit); // Assuming autonomousAudit takes (context, options, prompts) const { autonomousAudit } = await Promise.resolve().then(() => __importStar(require('./src/review'))); await autonomousAudit(context, options, prompts); } finally { try { await (0, promises_1.rm)(tempDir, { recursive: true, force: true }); } catch (e) { logger.error({ error: e }, 'Failed to cleanup temp directory'); } } }); app.log.info('Successfully processed autonomous audit.'); } catch (err) { app.log.error(`Failed to process autonomous audit: ${err.message}`); } }); // 2. Event Listener for "Chat with Bot" (Reply to comments) and Reaction Feedback // Handles both chat replies AND reaction capture for PR comments app.on('pull_request_review_comment.created', async (context) => { const { payload } = context; app.log.info(`Received ${context.name}.${payload.action} for comment ${payload.comment.id}`); const isBotComment = payload.comment?.user?.type === 'Bot'; try { // 2a. Chat with Bot handler (skip bot's own comments) if (!isBotComment) { const options = getOptions(); const prompts = new prompts_1.Prompts(); await context_1.als.run({ probotContext: context, octokit: context.octokit, repo: context.repo() }, async () => { (0, octokit_1.setOctokit)(context.octokit); await (0, review_comment_1.run)(context, options, prompts); }); app.log.info(`Successfully processed chat reply for comment ${payload.comment.id}`); } // 2b. Reaction Feedback handler (captures 👍/❤️ as acceptance, 👀/-1 as rejection) if (!isBotComment) { await context_1.als.run({ probotContext: context, octokit: context.octokit, repo: context.repo() }, async () => { (0, octokit_1.setOctokit)(context.octokit); const pullNumber = payload.pull_request?.number; if (pullNumber) { await feedback_capture_1.feedbackCapture.checkReactionFeedback(pullNumber); app.log.info(`Processed reaction feedback for PR #${pullNumber}`); } }); } } catch (err) { app.log.error(`Failed to process pull_request_review_comment: ${err.message}`); } }); // 4. Event Listener for PR Review Submission // Tracks when a review is submitted to monitor suggestion acceptance app.on('pull_request_review.submitted', async (context) => { const { payload } = context; app.log.info(`Received review submitted for PR #${payload.pull_request?.number}, review ID: ${payload.review?.id}`); try { await context_1.als.run({ probotContext: context, octokit: context.octokit, repo: context.repo() }, async () => { (0, octokit_1.setOctokit)(context.octokit); const pullNumber = payload.pull_request?.number; const reviewId = payload.review?.id; const commitId = payload.review?.commit_id; if (pullNumber && reviewId && commitId) { await feedback_capture_1.feedbackCapture.trackReviewSubmission(pullNumber, reviewId, commitId); app.log.info(`Tracked review submission: PR #${pullNumber}`); } }); } catch (err) { app.log.error(`Failed to track review submission: ${err.message}`); } }); // 5. Event Listener for Reaction on Issues (for standalone issue comments) app.on('issue_comment.created', async (context) => { const { payload } = context; // Only process user comments (not bot comments) if (payload.comment?.user?.type === 'Bot') { return; } try { await context_1.als.run({ probotContext: context, octokit: context.octokit, repo: context.repo() }, async () => { (0, octokit_1.setOctokit)(context.octokit); // Check for feedback reactions on issue comments const { data: reactions } = await context.octokit.rest.reactions.listForIssueComment({ owner: context.repo().owner, repo: context.repo().repo, // eslint-disable-next-line camelcase comment_id: payload.comment.id }); for (const reaction of reactions) { if (reaction.content === '+1' || reaction.content === 'heart') { feedback_tracker_1.feedbackTracker.recordFeedback({ patternType: 'general', severity: 'info', filenamePattern: 'issue', repoSlug: context.repo().repo, teamId: context.repo().owner, confidence: 50, feedbackType: 'accepted', commentId: String(payload.comment.id) }); } else if (reaction.content === '-1' || reaction.content === 'eyes') { feedback_tracker_1.feedbackTracker.recordFeedback({ patternType: 'general', severity: 'info', filenamePattern: 'issue', repoSlug: context.repo().repo, teamId: context.repo().owner, confidence: 50, feedbackType: 'rejected', commentId: String(payload.comment.id) }); } } }); } catch (err) { app.log.error(`Failed to process issue reaction feedback: ${err.message}`); } }); // 6. Event Listener for @prix mention in Issue Comments // When someone mentions @prix in an issue comment, analyze the issue and generate a fix plan app.on('issue_comment.created', async (context) => { const { payload } = context; const comment = payload.comment; if (!comment || comment.user?.type === 'Bot') { return; } if (!comment.body.includes('@prix')) { return; } app.log.info(`Received @prix mention in issue #${payload.issue?.number} from @${comment.user?.login}`); try { const options = getOptions(); const prompts = new prompts_1.Prompts(); const heavyAIOptions = new options_2.AIOptions(options.heavyModel, options.heavyTokenLimits); const heavyBot = new bot_1.Bot(options, heavyAIOptions); await context_1.als.run({ probotContext: context, octokit: context.octokit, repo: context.repo() }, async () => { (0, octokit_1.setOctokit)(context.octokit); await (0, issue_handler_1.handleIssueComment)(heavyBot, options, prompts); }); app.log.info(`Successfully processed @prix mention in issue #${payload.issue?.number}`); } catch (err) { app.log.error(`Failed to process @prix mention in issue #${payload.issue?.number}: ${err.message}`); } }); }; //# sourceMappingURL=index.js.map