bookworm-smart-assistant/scripts/predictive-audit.js

333 lines
10 KiB
JavaScript
Raw Permalink Normal View History

#!/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();
}