"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.feedbackTracker = exports.FeedbackTracker = void 0; const fs_1 = require("fs"); const path_1 = require("path"); const minimatch_1 = require("minimatch"); const info = console.log; function getPersistBaseDir() { if (process.env.PRIX_DATA_DIR) return process.env.PRIX_DATA_DIR; if (process.env.HF_HOME) return (0, path_1.join)(process.env.HF_HOME, 'prix-data'); if (process.env.HUGGING_FACE_HUB_TOKEN) return '/tmp/prix-hf'; return '/tmp/prix'; } class FeedbackTracker { feedbackStore = []; teamInsights = new Map(); persistPath; maxStoreSize = 10000; initialized = false; constructor(persistDir) { const baseDir = persistDir || getPersistBaseDir(); this.persistPath = (0, path_1.join)(baseDir, 'feedback'); this.ensureStorageDir(); this.loadPersisted(); } ensureStorageDir() { try { if (!(0, fs_1.existsSync)(this.persistPath)) { (0, fs_1.mkdirSync)(this.persistPath, { recursive: true }); } } catch (e) { info(`Failed to create feedback storage dir: ${e}`); } } loadPersisted() { try { const feedbackFile = (0, path_1.join)(this.persistPath, 'feedback.json'); if ((0, fs_1.existsSync)(feedbackFile)) { const data = JSON.parse((0, fs_1.readFileSync)(feedbackFile, 'utf8')); if (Array.isArray(data)) { this.feedbackStore = data.slice(-this.maxStoreSize); this.rebuildTeamInsights(); this.initialized = true; info(`Loaded ${this.feedbackStore.length} feedback entries`); } } } catch (e) { info(`Failed to load feedback data: ${e}`); } } persist() { try { const feedbackFile = (0, path_1.join)(this.persistPath, 'feedback.json'); (0, fs_1.writeFileSync)(feedbackFile, JSON.stringify(this.feedbackStore.slice(-this.maxStoreSize))); } catch (e) { info(`Failed to persist feedback: ${e}`); } } rebuildTeamInsights() { const teamMap = new Map(); for (const fb of this.feedbackStore) { const key = `${fb.teamId}:${fb.repoSlug}`; if (!teamMap.has(key)) { teamMap.set(key, []); } teamMap.get(key).push(fb); } for (const [key, feedbacks] of teamMap) { const [teamId, repoSlug] = key.split(':'); const insights = this.computeInsights(teamId, repoSlug, feedbacks); this.teamInsights.set(key, insights); } } computeInsights(teamId, repoSlug, feedbacks) { const patternMap = new Map(); for (const fb of feedbacks) { if (!patternMap.has(fb.patternType)) { patternMap.set(fb.patternType, []); } patternMap.get(fb.patternType).push(fb); } const patternStats = new Map(); for (const [patternType, pFeedbacks] of patternMap) { const accepted = pFeedbacks.filter(f => f.feedbackType === 'accepted').length; const rejected = pFeedbacks.filter(f => f.feedbackType === 'rejected').length; const ignored = pFeedbacks.filter(f => f.feedbackType === 'ignored').length; const total = pFeedbacks.length; const avgConfidence = pFeedbacks.reduce((sum, f) => sum + f.confidence, 0) / total; patternStats.set(patternType, { patternType, total, accepted, rejected, ignored, acceptanceRate: total > 0 ? accepted / total : 0, avgConfidence }); } const allAccepted = feedbacks.filter(f => f.feedbackType === 'accepted').length; const overallAcceptanceRate = feedbacks.length > 0 ? allAccepted / feedbacks.length : 0; return { teamId, repoSlug, patternStats, overallAcceptanceRate, lastUpdated: Date.now() }; } recordFeedback(feedback) { const id = `fb_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const fullFeedback = { ...feedback, id, timestamp: Date.now() }; this.feedbackStore.push(fullFeedback); if (this.feedbackStore.length > this.maxStoreSize) { this.feedbackStore = this.feedbackStore.slice(-this.maxStoreSize); } const key = `${feedback.teamId}:${feedback.repoSlug}`; const existing = this.teamInsights.get(key); if (existing) { const updatedFeedbacks = [ ...this.feedbackStore.filter(f => f.teamId === feedback.teamId && f.repoSlug === feedback.repoSlug) ]; const insights = this.computeInsights(feedback.teamId, feedback.repoSlug, updatedFeedbacks); this.teamInsights.set(key, insights); } this.persist(); return id; } recordAcceptance(patternType, severity, filename, repoSlug, teamId, confidence, commentId, reviewId) { return this.recordFeedback({ patternType, severity, filenamePattern: filename, repoSlug, teamId, confidence, feedbackType: 'accepted', commentId, reviewId }); } recordRejection(patternType, severity, filename, repoSlug, teamId, confidence, commentId, reviewId) { return this.recordFeedback({ patternType, severity, filenamePattern: filename, repoSlug, teamId, confidence, feedbackType: 'rejected', commentId, reviewId }); } recordIgnored(patternType, severity, filename, repoSlug, teamId, confidence, commentId, reviewId) { return this.recordFeedback({ patternType, severity, filenamePattern: filename, repoSlug, teamId, confidence, feedbackType: 'ignored', commentId, reviewId }); } getPatternAcceptanceRate(patternType, teamId, repoSlug) { const key = `${teamId}:${repoSlug}`; const insights = this.teamInsights.get(key); if (!insights) return 0.5; const stats = insights.patternStats.get(patternType); if (!stats) return 0.5; if (stats.total < 5) return 0.5; return stats.acceptanceRate; } getFilenameAcceptanceRate(filenamePattern, teamId, repoSlug) { const feedbacks = this.feedbackStore.filter(f => f.teamId === teamId && f.repoSlug === repoSlug && f.feedbackType !== 'ignored'); if (feedbacks.length === 0) return 0.5; const matching = feedbacks.filter(f => (0, minimatch_1.minimatch)(f.filenamePattern, filenamePattern) || (0, minimatch_1.minimatch)(filenamePattern, f.filenamePattern)); if (matching.length === 0) return 0.5; const accepted = matching.filter(f => f.feedbackType === 'accepted').length; return accepted / matching.length; } getTeamInsights(teamId, repoSlug) { const key = `${teamId}:${repoSlug}`; return this.teamInsights.get(key) || null; } getAdjustedConfidence(baseConfidence, patternType, filename, teamId, repoSlug) { const patternRate = this.getPatternAcceptanceRate(patternType, teamId, repoSlug); const filenameRate = this.getFilenameAcceptanceRate(filename, teamId, repoSlug); const feedbackWeight = 0.3; const teamRate = patternRate * 0.6 + filenameRate * 0.4; const adjustedConfidence = baseConfidence * (1 - feedbackWeight) + teamRate * 100 * feedbackWeight; return Math.max(0, Math.min(100, adjustedConfidence)); } shouldSuppressPattern(patternType, teamId, repoSlug, threshold = 0.2, minSamples = 10) { const key = `${teamId}:${repoSlug}`; const insights = this.teamInsights.get(key); if (!insights) return false; const stats = insights.patternStats.get(patternType); if (!stats || stats.total < minSamples) return false; return stats.acceptanceRate < threshold; } getQuery(query) { let results = [...this.feedbackStore]; if (query.teamId) { results = results.filter(f => f.teamId === query.teamId); } if (query.repoSlug) { results = results.filter(f => f.repoSlug === query.repoSlug); } if (query.patternType) { results = results.filter(f => f.patternType === query.patternType); } if (query.filenamePattern) { results = results.filter(f => (0, minimatch_1.minimatch)(f.filenamePattern, query.filenamePattern)); } if (query.since) { results = results.filter(f => f.timestamp >= query.since); } if (query.limit) { results = results.slice(-query.limit); } return results; } getSummary(teamId, repoSlug) { let feedbacks = [...this.feedbackStore]; if (teamId) { feedbacks = feedbacks.filter(f => f.teamId === teamId); } if (repoSlug) { feedbacks = feedbacks.filter(f => f.repoSlug === repoSlug); } const patternMap = new Map(); const repoMap = new Map(); const teamMap = new Map(); for (const fb of feedbacks) { if (!patternMap.has(fb.patternType)) { patternMap.set(fb.patternType, []); } patternMap.get(fb.patternType).push(fb); repoMap.set(fb.repoSlug, (repoMap.get(fb.repoSlug) || 0) + 1); teamMap.set(fb.teamId, (teamMap.get(fb.teamId) || 0) + 1); } const byPattern = new Map(); for (const [patternType, pFeedbacks] of patternMap) { const accepted = pFeedbacks.filter(f => f.feedbackType === 'accepted').length; const rejected = pFeedbacks.filter(f => f.feedbackType === 'rejected').length; const ignored = pFeedbacks.filter(f => f.feedbackType === 'ignored').length; const total = pFeedbacks.length; const avgConfidence = pFeedbacks.reduce((sum, f) => sum + f.confidence, 0) / total; byPattern.set(patternType, { patternType, total, accepted, rejected, ignored, acceptanceRate: total > 0 ? accepted / total : 0, avgConfidence }); } const recentWindow = 30 * 24 * 60 * 60 * 1000; const recentFeedbacks = feedbacks.filter(f => f.timestamp > Date.now() - recentWindow); const olderFeedbacks = feedbacks.filter(f => f.timestamp <= Date.now() - recentWindow); const recentAcceptance = recentFeedbacks.filter(f => f.feedbackType === 'accepted').length / (recentFeedbacks.length || 1); const olderAcceptance = olderFeedbacks.filter(f => f.feedbackType === 'accepted').length / (olderFeedbacks.length || 1); let recentTrend = 'stable'; if (recentAcceptance > olderAcceptance + 0.05) { recentTrend = 'improving'; } else if (recentAcceptance < olderAcceptance - 0.05) { recentTrend = 'declining'; } return { totalFeedback: feedbacks.length, byPattern, byRepo: repoMap, byTeam: teamMap, recentTrend }; } exportForTraining() { return this.feedbackStore .filter(f => f.feedbackType !== 'ignored') .map(f => ({ patternType: f.patternType, severity: f.severity, filenamePattern: f.filenamePattern, confidence: f.confidence, accepted: f.feedbackType === 'accepted', repoSlug: f.repoSlug })); } } exports.FeedbackTracker = FeedbackTracker; exports.feedbackTracker = new FeedbackTracker(); //# sourceMappingURL=feedback-tracker.js.map