UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

402 lines • 17.5 kB
/** * AI Feedback Privacy Aggregator * * Privacy-preserving data aggregation for AI feedback collection that enables * valuable analytics while protecting user privacy through differential privacy, * k-anonymity, and data minimization techniques. */ import * as crypto from 'crypto'; /** * Privacy-Preserving AI Feedback Aggregator */ export class AIFeedbackPrivacyAggregator { config; privacyBudgetUsed = 0; aggregationHistory = []; constructor(config = {}) { this.config = { enableDifferentialPrivacy: true, epsilonValue: 1.0, // Standard privacy budget kAnonymityThreshold: 5, // Minimum 5 users per group enableDataMinimization: true, retentionPeriodDays: 90, allowedAggregations: ['count', 'average', 'distribution', 'trends'], ...config }; } /** * Aggregate feedback data with privacy preservation */ async aggregateWithPrivacy(feedbackData, aggregationType = 'weekly') { // Apply privacy filters first const privacyFilteredData = this.applyPrivacyFilters(feedbackData); // Check k-anonymity requirements const kAnonymityMet = this.checkKAnonymity(privacyFilteredData); // Apply data minimization const minimizedData = this.config.enableDataMinimization ? this.applyDataMinimization(privacyFilteredData) : privacyFilteredData; // Generate base aggregations const baseMetrics = this.calculateBaseMetrics(minimizedData); // Apply differential privacy noise if enabled const noisyMetrics = this.config.enableDifferentialPrivacy ? this.addDifferentialPrivacyNoise(baseMetrics) : baseMetrics; const aggregatedMetrics = { timestamp: Date.now(), aggregationType, privacyLevel: this.determinePrivacyLevel(), metrics: noisyMetrics, privacyMetadata: { noiseAdded: this.config.enableDifferentialPrivacy, kAnonymityMet, dataMinimized: this.config.enableDataMinimization, originalEntryCount: feedbackData.length, aggregatedEntryCount: minimizedData.length } }; // Store aggregation for budget tracking this.aggregationHistory.push(aggregatedMetrics); this.updatePrivacyBudget(); return aggregatedMetrics; } /** * Generate privacy compliance report */ generatePrivacyReport(feedbackData) { const risks = []; const recommendations = []; // Check data retention compliance const now = Date.now(); const retentionThreshold = now - (this.config.retentionPeriodDays * 24 * 60 * 60 * 1000); const expiredEntries = feedbackData.filter(entry => entry.timestamp < retentionThreshold); if (expiredEntries.length > 0) { risks.push(`${expiredEntries.length} entries exceed retention period`); recommendations.push('Implement automated data purging for expired entries'); } // Check for potential re-identification risks const identificationRisks = this.assessReidentificationRisk(feedbackData); risks.push(...identificationRisks); // Privacy budget assessment if (this.privacyBudgetUsed > 0.8) { risks.push('Privacy budget nearly exhausted (>80% used)'); recommendations.push('Consider reducing query frequency or increasing epsilon value'); } // Anonymization effectiveness const anonymizationScore = this.calculateAnonymizationScore(feedbackData); return { complianceLevel: risks.length === 0 ? 'compliant' : risks.length <= 2 ? 'warning' : 'violation', privacyRisks: risks, recommendedActions: recommendations, dataRetentionStatus: { totalEntries: feedbackData.length, expiredEntries: expiredEntries.length, retainedEntries: feedbackData.length - expiredEntries.length }, anonymizationEffectiveness: { score: anonymizationScore, vulnerabilities: anonymizationScore < 70 ? [ 'Insufficient k-anonymity', 'Potential quasi-identifier leakage' ] : [], improvements: [ 'Implement stronger hashing algorithms', 'Increase k-anonymity threshold', 'Add more differential privacy noise' ] } }; } /** * Export privacy-compliant dataset */ exportPrivacyCompliantDataset(feedbackData, exportLevel = 'research') { let processedData = [...feedbackData]; const guarantees = []; const limitations = []; // Apply privacy level specific transformations switch (exportLevel) { case 'public': processedData = this.applyMaximalPrivacy(processedData); guarantees.push('k-anonymity with k ≥ 10'); guarantees.push('Differential privacy with ε = 0.5'); guarantees.push('All identifiers removed or hashed'); limitations.push('Reduced data granularity'); limitations.push('Some metrics may have statistical noise'); break; case 'research': processedData = this.applyResearchPrivacy(processedData); guarantees.push('k-anonymity with k ≥ 5'); guarantees.push('Differential privacy with ε = 1.0'); guarantees.push('Session IDs hashed'); limitations.push('Timestamp granularity reduced to days'); break; case 'internal': processedData = this.applyInternalPrivacy(processedData); guarantees.push('Data minimization applied'); guarantees.push('Retention policy enforced'); break; } return { dataset: processedData, privacyGuarantees: guarantees, limitations }; } // Private methods applyPrivacyFilters(data) { // Remove entries that don't meet privacy requirements return data.filter(entry => { // Ensure minimum data quality if (!entry.sessionId || !entry.agentType) return false; // Apply retention policy const retentionThreshold = Date.now() - (this.config.retentionPeriodDays * 24 * 60 * 60 * 1000); if (entry.timestamp < retentionThreshold) return false; return true; }); } checkKAnonymity(data) { // Group by quasi-identifiers (framework + complexity + agent type) const groups = new Map(); data.forEach(entry => { const key = `${entry.contextualData.framework}_${entry.contextualData.projectComplexity}_${entry.agentType}`; groups.set(key, (groups.get(key) || 0) + 1); }); // Check if all groups meet k-anonymity threshold for (const count of groups.values()) { if (count < this.config.kAnonymityThreshold) { return false; } } return true; } applyDataMinimization(data) { return data.map(entry => ({ ...entry, // Hash session ID for privacy sessionId: this.hashValue(entry.sessionId), // Remove or generalize detailed technical metrics technicalMetrics: { ...entry.technicalMetrics, errorsEncountered: [], // Remove specific error messages recoveryActions: [] // Remove specific recovery details }, // Generalize feedback text feedback: { ...entry.feedback, strengths: this.generalizeTextArray(entry.feedback.strengths), weaknesses: this.generalizeTextArray(entry.feedback.weaknesses), suggestions: this.generalizeTextArray(entry.feedback.suggestions) } })); // Type assertion since we're maintaining the structure } calculateBaseMetrics(data) { const totalSessions = data.length; // Calculate average satisfaction const satisfactionValues = data.map(d => d.userExperience.satisfaction); const averageSatisfaction = satisfactionValues.reduce((sum, val) => sum + val, 0) / satisfactionValues.length; // Satisfaction distribution const satisfactionDistribution = {}; satisfactionValues.forEach(val => { const bucket = this.getSatisfactionBucket(val); satisfactionDistribution[bucket] = (satisfactionDistribution[bucket] || 0) + 1; }); // Agent performance metrics const agentPerformance = {}; const agentGroups = this.groupByAgent(data); Object.entries(agentGroups).forEach(([agent, entries]) => { const avgRating = entries.reduce((sum, e) => sum + e.userExperience.satisfaction, 0) / entries.length; agentPerformance[agent] = { usageCount: entries.length, averageRating: Math.round(avgRating * 10) / 10, anonymizedFeedback: this.extractAnonymizedFeedback(entries) }; }); // Framework metrics const frameworkMetrics = {}; const frameworkGroups = this.groupByFramework(data); Object.entries(frameworkGroups).forEach(([framework, entries]) => { const successCount = entries.filter(e => e.outcome === 'success').length; frameworkMetrics[framework] = { sessionCount: entries.length, averageComplexity: this.getMostCommonComplexity(entries), successRate: Math.round((successCount / entries.length) * 100) / 100 }; }); // Trend indicators (simplified) const trendIndicators = { satisfactionTrend: averageSatisfaction > 7 ? 'improving' : averageSatisfaction > 5 ? 'stable' : 'declining', usageTrend: totalSessions > 10 ? 'increasing' : 'stable', qualityTrend: averageSatisfaction > 7 ? 'improving' : 'stable' }; return { totalSessions, averageSatisfaction: Math.round(averageSatisfaction * 10) / 10, satisfactionDistribution, agentPerformance, frameworkMetrics, trendIndicators }; } addDifferentialPrivacyNoise(metrics) { // Add Laplacian noise to numerical values const epsilon = this.config.epsilonValue; return { ...metrics, totalSessions: Math.max(0, metrics.totalSessions + this.generateLaplaceNoise(1 / epsilon)), averageSatisfaction: Math.max(0, Math.min(10, metrics.averageSatisfaction + this.generateLaplaceNoise(1 / epsilon))), // Add noise to counts in distributions satisfactionDistribution: Object.fromEntries(Object.entries(metrics.satisfactionDistribution).map(([key, count]) => [ key, Math.max(0, count + this.generateLaplaceNoise(1 / epsilon)) ])) }; } generateLaplaceNoise(scale) { // Generate Laplacian noise for differential privacy const u = Math.random() - 0.5; return -scale * Math.sign(u) * Math.log(1 - 2 * Math.abs(u)); } hashValue(value) { return crypto.createHash('sha256').update(value + 'privacy-salt').digest('hex').substring(0, 16); } generalizeTextArray(texts) { // Remove potentially identifying information and generalize return texts.map(text => { return text .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '[email]') .replace(/\b(?:https?:\/\/)?(?:www\.)?[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(?:\/[^\s]*)?\b/g, '[url]') .replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '[ip]') .replace(/\b\d{4,}\b/g, '[number]') .substring(0, 100); // Limit length }).slice(0, 3); // Limit count } getSatisfactionBucket(satisfaction) { if (satisfaction >= 8) return 'high'; if (satisfaction >= 6) return 'medium'; return 'low'; } groupByAgent(data) { const groups = {}; data.forEach(entry => { groups[entry.agentType] = groups[entry.agentType] || []; groups[entry.agentType].push(entry); }); return groups; } groupByFramework(data) { const groups = {}; data.forEach(entry => { const framework = entry.contextualData.framework; groups[framework] = groups[framework] || []; groups[framework].push(entry); }); return groups; } getMostCommonComplexity(entries) { const counts = {}; entries.forEach(e => { counts[e.contextualData.projectComplexity] = (counts[e.contextualData.projectComplexity] || 0) + 1; }); return Object.entries(counts).reduce((a, b) => counts[a[0]] > counts[b[0]] ? a : b)[0]; } extractAnonymizedFeedback(entries) { const allFeedback = entries.flatMap(e => [ ...e.feedback.strengths, ...e.feedback.weaknesses, ...e.feedback.suggestions ]); return this.generalizeTextArray(allFeedback); } determinePrivacyLevel() { if (this.config.enableDifferentialPrivacy && this.config.kAnonymityThreshold >= 10) { return 'strict'; } if (this.config.enableDifferentialPrivacy || this.config.kAnonymityThreshold >= 5) { return 'standard'; } return 'minimal'; } updatePrivacyBudget() { // Simple privacy budget tracking (in real implementation, this would be more sophisticated) this.privacyBudgetUsed += this.config.epsilonValue / 10; } assessReidentificationRisk(data) { const risks = []; // Check for unique combinations of quasi-identifiers const combinations = new Map(); data.forEach(entry => { const key = `${entry.contextualData.framework}_${entry.contextualData.projectComplexity}_${entry.agentType}`; combinations.set(key, (combinations.get(key) || 0) + 1); }); const uniqueEntries = Array.from(combinations.values()).filter(count => count === 1).length; const uniqueRatio = uniqueEntries / data.length; if (uniqueRatio > 0.1) { risks.push(`High re-identification risk: ${Math.round(uniqueRatio * 100)}% unique entries`); } return risks; } calculateAnonymizationScore(data) { let score = 100; // Deduct points for privacy risks const kAnonymityMet = this.checkKAnonymity(data); if (!kAnonymityMet) score -= 30; if (!this.config.enableDifferentialPrivacy) score -= 20; if (!this.config.enableDataMinimization) score -= 15; const risks = this.assessReidentificationRisk(data); score -= risks.length * 10; return Math.max(0, score); } applyMaximalPrivacy(data) { return data .filter((_, index) => index % 2 === 0) // Sample 50% for extreme privacy .map(entry => ({ timestamp: Math.floor(entry.timestamp / (24 * 60 * 60 * 1000)) * (24 * 60 * 60 * 1000), // Round to day agentType: entry.agentType, outcome: entry.outcome, satisfaction: this.getSatisfactionBucket(entry.userExperience.satisfaction), framework: entry.contextualData.framework === 'unknown' ? 'unknown' : 'known', complexity: entry.contextualData.projectComplexity })); } applyResearchPrivacy(data) { return data.map(entry => ({ sessionId: this.hashValue(entry.sessionId), timestamp: Math.floor(entry.timestamp / (24 * 60 * 60 * 1000)) * (24 * 60 * 60 * 1000), agentType: entry.agentType, outcome: entry.outcome, userExperience: { satisfaction: Math.round(entry.userExperience.satisfaction), efficiency: Math.round(entry.userExperience.efficiency), clarity: Math.round(entry.userExperience.clarity), usefulness: Math.round(entry.userExperience.usefulness) }, contextualData: { framework: entry.contextualData.framework, projectComplexity: entry.contextualData.projectComplexity, userType: entry.contextualData.userType } })); } applyInternalPrivacy(data) { return data.map(entry => ({ ...entry, sessionId: this.hashValue(entry.sessionId), feedback: { ...entry.feedback, strengths: this.generalizeTextArray(entry.feedback.strengths), weaknesses: this.generalizeTextArray(entry.feedback.weaknesses), suggestions: this.generalizeTextArray(entry.feedback.suggestions) } })); } } //# sourceMappingURL=ai-feedback-privacy-aggregator.js.map