- VERSION file as authoritative version source - export.mjs reads VERSION with package.json fallback - bw-ota.ps1 DryRun mode for safe testing - auto-setup.ps1 bumped to v3.2.0 (Phase 8 OTA)
110 lines
3.7 KiB
JavaScript
110 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* token-saver-session-report.js · TSE Layer 5 · 2026-04-27
|
|
* Stop · 会话效率报告
|
|
* 解析 transcript, 统计效率指标, 写入 tse-efficiency-log.jsonl
|
|
* 行为: fail-open
|
|
*/
|
|
'use strict';
|
|
|
|
var fs = require('fs');
|
|
var path = require('path');
|
|
var CLAUDE_ROOT = require('./lib/root.js');
|
|
var readStdin = require('./lib/read-stdin.js');
|
|
|
|
var STATE_DIR = path.join(CLAUDE_ROOT, 'session-state');
|
|
var LOG_PATH = path.join(STATE_DIR, 'tse-efficiency-log.jsonl');
|
|
var MAX_TRANSCRIPT = 20 * 1024 * 1024;
|
|
|
|
(async () => {
|
|
try {
|
|
var hookData = {};
|
|
try { hookData = await readStdin({ maxSize: 128 * 1024 }); } catch {}
|
|
|
|
var tp = hookData.transcript_path;
|
|
if (!tp || !fs.existsSync(tp)) process.exit(0);
|
|
|
|
var stat = fs.statSync(tp);
|
|
if (stat.size > MAX_TRANSCRIPT || stat.size < 100) process.exit(0);
|
|
|
|
var raw = fs.readFileSync(tp, 'utf8');
|
|
var lines = raw.split('\n').filter(Boolean);
|
|
|
|
var m = { rounds: 0, compacts: 0, toolCalls: 0, mcpCalls: 0, agentCalls: 0,
|
|
largeOutputs: 0, tseReadGuard: 0, tseBashLimiter: 0, tsePostGuard: 0,
|
|
reads: 0, edits: 0, bashes: 0, models: {} };
|
|
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var obj;
|
|
try { obj = JSON.parse(lines[i]); } catch { continue; }
|
|
|
|
var content = (obj && obj.message && obj.message.content) || (obj && obj.content);
|
|
var role = obj && obj.message && obj.message.role;
|
|
var model = obj && obj.model;
|
|
|
|
if (role === 'assistant') m.rounds++;
|
|
if (model) m.models[model] = (m.models[model] || 0) + 1;
|
|
|
|
if (!Array.isArray(content)) {
|
|
if (typeof content === 'string') {
|
|
if (content.indexOf('[TSE') !== -1) countTse(content, m);
|
|
if (content.indexOf('PreCompact') !== -1) m.compacts++;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
for (var j = 0; j < content.length; j++) {
|
|
var part = content[j];
|
|
if (!part) continue;
|
|
|
|
if (part.type === 'tool_use') {
|
|
m.toolCalls++;
|
|
var nm = part.name || '';
|
|
if (nm.startsWith('mcp__')) m.mcpCalls++;
|
|
if (nm === 'Agent') m.agentCalls++;
|
|
if (nm === 'Read') m.reads++;
|
|
if (nm === 'Edit' || nm === 'Write') m.edits++;
|
|
if (nm === 'Bash') m.bashes++;
|
|
}
|
|
if (part.type === 'tool_result') {
|
|
var txt = typeof part.content === 'string' ? part.content : '';
|
|
if (txt.length > 5000) m.largeOutputs++;
|
|
}
|
|
|
|
var t = part.text || (typeof part === 'string' ? part : '');
|
|
if (typeof t === 'string' && t.indexOf('[TSE') !== -1) countTse(t, m);
|
|
if (typeof t === 'string' && t.indexOf('PreCompact') !== -1) m.compacts++;
|
|
}
|
|
}
|
|
|
|
var score = 80;
|
|
score -= Math.min(m.compacts * 8, 24);
|
|
score -= Math.min(m.largeOutputs * 2, 20);
|
|
score -= Math.max(0, Math.floor((m.rounds - 40) * 0.5));
|
|
if (m.agentCalls > 0) score += 10;
|
|
if (m.tseReadGuard + m.tseBashLimiter > 0 && m.tseReadGuard + m.tseBashLimiter < 8) score += 5;
|
|
score = Math.max(0, Math.min(100, score));
|
|
|
|
var grade = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'F';
|
|
|
|
var report = {
|
|
ts: new Date().toISOString(),
|
|
sid: hookData.session_id || 'unknown',
|
|
score: score,
|
|
grade: grade,
|
|
metrics: m
|
|
};
|
|
|
|
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
fs.appendFileSync(LOG_PATH, JSON.stringify(report) + '\n', 'utf8');
|
|
|
|
process.exit(0);
|
|
} catch { process.exit(0); }
|
|
})();
|
|
|
|
function countTse(text, m) {
|
|
if (text.indexOf('READ_GUARD') !== -1) m.tseReadGuard++;
|
|
if (text.indexOf('BASH_LIMITER') !== -1) m.tseBashLimiter++;
|
|
if (text.indexOf('POST_GUARD') !== -1) m.tsePostGuard++;
|
|
}
|