#!/usr/bin/env node /** * 预测性审计引擎 (Predictive Audit) * * 挖掘 evolution-log.jsonl 模式: * - 哪些标签/维度修复最频繁 * - 版本间修复密度趋势 * - 预测下次升级可能的风险点 * * 用法: * node scripts/predictive-audit.js # 文本报告 * node scripts/predictive-audit.js --json # JSON 输出 */ const fs = require('fs'); const path = require('path'); const detectClaudeRoot = () => require('./paths.config.js').PATHS.root; const CLAUDE_ROOT = detectClaudeRoot(); const JSON_MODE = process.argv.includes('--json'); // === 加载演进日志 === function loadEvolutionLog() { const candidates = [ path.join(CLAUDE_ROOT, 'evolution-log.jsonl'), path.join(CLAUDE_ROOT, 'debug', 'evolution-log.jsonl'), path.join(CLAUDE_ROOT, 'projects', 'C--Users-janson9527us', 'memory', 'evolution-log.jsonl'), ]; const allEntries = []; const seenSeqs = new Set(); for (const fp of candidates) { if (!fs.existsSync(fp)) continue; const lines = fs.readFileSync(fp, 'utf8').trim().split('\n'); for (const line of lines) { try { const entry = JSON.parse(line); if (entry.seq != null && !seenSeqs.has(entry.seq)) { seenSeqs.add(entry.seq); allEntries.push(entry); } } catch {} } } return allEntries.sort((a, b) => (a.seq || 0) - (b.seq || 0)); } // === 分析 === function analyze(entries) { if (entries.length === 0) return null; // 1. 标签频率分析 const tagCounts = {}; for (const e of entries) { for (const tag of (e.tags || [])) { tagCounts[tag] = (tagCounts[tag] || 0) + 1; } } const topTags = Object.entries(tagCounts) .sort((a, b) => b[1] - a[1]) .map(([tag, count]) => ({ tag, count, pct: Math.round(count / entries.length * 100) })); // 2. 修复密度趋势 (按版本) const byVersion = {}; for (const e of entries) { const v = e.version || 'unknown'; if (!byVersion[v]) byVersion[v] = { count: 0, totalFixes: 0, entries: [] }; byVersion[v].count++; byVersion[v].totalFixes += (e.fix_count || 0); byVersion[v].entries.push(e); } const versionTrend = Object.entries(byVersion) .sort((a, b) => { // 按版本号排序 const va = a[0].replace('v', '').split('.').map(Number); const vb = b[0].replace('v', '').split('.').map(Number); return (va[0] - vb[0]) || (va[1] - vb[1]); }) .map(([version, data]) => ({ version, entries: data.count, totalFixes: data.totalFixes, avgFixesPerEntry: data.count > 0 ? Math.round(data.totalFixes / data.count * 10) / 10 : 0, })); // 3. 触发源分析 const triggerCounts = {}; for (const e of entries) { const trigger = e.trigger || 'unknown'; triggerCounts[trigger] = (triggerCounts[trigger] || 0) + 1; } // 4. 趋势分析 - 最近 5 个版本的复杂度变化 const recentVersions = versionTrend.slice(-5); const complexityTrend = recentVersions.map(v => v.totalFixes); const isIncreasing = complexityTrend.length >= 3 && complexityTrend[complexityTrend.length - 1] < complexityTrend[complexityTrend.length - 2]; // 5. 预测风险点 const predictions = []; // 高频标签预测 if (topTags.length > 0) { const topTag = topTags[0]; if (topTag.pct > 60) { predictions.push({ risk: 'HIGH', area: topTag.tag, reason: `"${topTag.tag}" 出现在 ${topTag.pct}% 的版本变更中, 是最频繁的变更领域`, suggestion: `下次升级重点关注 ${topTag.tag} 相关组件的回归测试`, }); } } // 修复密度趋势预测 if (recentVersions.length >= 3) { const recent = recentVersions.slice(-3); const avgRecent = recent.reduce((s, v) => s + v.totalFixes, 0) / recent.length; if (avgRecent > 10) { predictions.push({ risk: 'MEDIUM', area: 'complexity', reason: `最近 3 个版本平均修复 ${Math.round(avgRecent)} 项, 系统复杂度快速增长`, suggestion: '考虑放慢新功能节奏, 增加稳定性/重构投入', }); } } // 版本/测试相关预测 const versionTag = topTags.find(t => t.tag === 'version'); const testingTag = topTags.find(t => t.tag === 'testing'); if (versionTag && testingTag) { if (versionTag.count > testingTag.count * 1.5) { predictions.push({ risk: 'MEDIUM', area: 'testing-gap', reason: `版本变更 (${versionTag.count}次) 远多于测试相关变更 (${testingTag.count}次)`, suggestion: '增加自动化测试覆盖, 确保每次升级有充分测试', }); } } // 安全标签频率 const securityTag = topTags.find(t => t.tag === 'security'); if (securityTag && securityTag.pct > 40) { predictions.push({ risk: 'INFO', area: 'security', reason: `安全相关变更占 ${securityTag.pct}%, 系统安全投入较高`, suggestion: '保持当前安全关注度, 考虑引入自动化安全扫描', }); } // 降低修复密度 = 好趋势 if (isIncreasing) { predictions.push({ risk: 'INFO', area: 'improving', reason: '最近版本修复数呈下降趋势, 系统稳定性在改善', suggestion: '继续保持, 可适当增加新功能投入', }); } // v5.1: 指数平滑预测 const fixCounts = versionTrend.map(v => v.totalFixes); const smoothed = exponentialSmoothing(fixCounts); // v5.1: 标签共现矩阵 const cooccurrence = buildCooccurrenceMatrix(entries); // v5.1: 尖峰检测 const spikes = detectSpikes(fixCounts); if (spikes.length > 0) { predictions.push({ risk: 'INFO', area: 'spike-detection', reason: `检测到 ${spikes.length} 个修复密度尖峰`, suggestion: '关注尖峰对应版本的变更质量', }); } return { totalEntries: entries.length, versionRange: `${entries[0].version} -> ${entries[entries.length - 1].version}`, dateRange: `${entries[0].ts} ~ ${entries[entries.length - 1].ts}`, topTags, versionTrend, triggers: Object.entries(triggerCounts).sort((a, b) => b[1] - a[1]), predictions, smoothedForecast: smoothed, cooccurrence, spikes, }; } // === v5.1: 指数平滑预测 === /** * 简单指数平滑, 返回下一期预测值 * @param {number[]} data - 时间序列数据 * @param {number} alpha - 平滑系数 (默认 0.4) * @returns {{ forecast: number, series: number[] }} */ function exponentialSmoothing(data, alpha = 0.4) { if (data.length === 0) return { forecast: 0, series: [] }; const series = [data[0]]; for (let i = 1; i < data.length; i++) { series.push(alpha * data[i] + (1 - alpha) * series[i - 1]); } // 下一期预测 const forecast = Math.round(series[series.length - 1] * 10) / 10; return { forecast, series: series.map(v => Math.round(v * 10) / 10) }; } // === v5.1: 标签共现矩阵 === /** * 构建标签共现矩阵 * @param {Array} entries - evolution-log 条目 * @returns {Object} { "tagA:tagB" → count } */ function buildCooccurrenceMatrix(entries) { const matrix = {}; for (const entry of entries) { const tags = entry.tags || []; for (let i = 0; i < tags.length; i++) { for (let j = i + 1; j < tags.length; j++) { const pair = [tags[i], tags[j]].sort().join(':'); matrix[pair] = (matrix[pair] || 0) + 1; } } } return matrix; } // === v5.1: 尖峰检测 === /** * 检测时间序列中的异常尖峰 * @param {number[]} timeSeries - 时间序列数据 * @param {number} threshold - Z-score 阈值 (默认 2.0) * @returns {Array<{ index: number, value: number, zScore: number }>} */ function detectSpikes(timeSeries, threshold = 2.0) { if (timeSeries.length < 3) return []; const mean = timeSeries.reduce((a, b) => a + b, 0) / timeSeries.length; const variance = timeSeries.reduce((s, v) => s + (v - mean) ** 2, 0) / timeSeries.length; const stddev = Math.sqrt(variance); if (stddev === 0) return []; const spikes = []; for (let i = 0; i < timeSeries.length; i++) { const zScore = Math.abs(timeSeries[i] - mean) / stddev; if (zScore > threshold) { spikes.push({ index: i, value: timeSeries[i], zScore: Math.round(zScore * 100) / 100 }); } } return spikes; } // === 输出 === function main() { const entries = loadEvolutionLog(); if (entries.length === 0) { console.log('无演进日志数据。'); return; } const report = analyze(entries); if (JSON_MODE) { console.log(JSON.stringify(report, null, 2)); return; } console.log('=== 预测性审计报告 ==='); console.log(`演进记录: ${report.totalEntries} 条 (${report.versionRange})`); console.log(`时间跨度: ${report.dateRange}`); console.log(''); // 标签热力图 console.log('标签频率 (Top 10):'); for (const t of report.topTags.slice(0, 10)) { const bar = '#'.repeat(Math.min(t.count, 30)); console.log(` ${t.tag.padEnd(20)} ${String(t.count).padStart(3)} (${String(t.pct).padStart(3)}%) ${bar}`); } console.log(''); // 版本趋势 console.log('版本修复密度:'); for (const v of report.versionTrend) { const bar = '*'.repeat(Math.min(v.totalFixes, 40)); console.log(` ${v.version.padEnd(8)} ${String(v.totalFixes).padStart(3)} fixes (${v.entries} entries) ${bar}`); } console.log(''); // 触发源 console.log('触发源分布:'); for (const [trigger, count] of report.triggers) { console.log(` ${trigger.padEnd(20)} ${count}`); } console.log(''); // 预测 if (report.predictions.length > 0) { console.log('风险预测:'); for (const p of report.predictions) { console.log(` [${p.risk}] ${p.area}: ${p.reason}`); console.log(` -> ${p.suggestion}`); } } else { console.log('风险预测: 暂无显著风险信号'); } } // 模块导出 (供测试使用) if (typeof module !== 'undefined') { module.exports = { loadEvolutionLog, analyze, main, exponentialSmoothing, buildCooccurrenceMatrix, detectSpikes, }; } if (require.main === module) { main(); }