106 lines
3.4 KiB
JavaScript
106 lines
3.4 KiB
JavaScript
|
|
/**
|
||
|
|
* tse-retention-extractor.js · TSE v2.0 · 2026-04-27
|
||
|
|
* 从 transcript 提取文件路径/函数名/TODO/决策点
|
||
|
|
* 用于 PreCompact 生成保留指令, 必须 <2s 完成
|
||
|
|
*/
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
var fs = require('fs');
|
||
|
|
|
||
|
|
function extract(transcriptPath) {
|
||
|
|
if (!transcriptPath || !fs.existsSync(transcriptPath)) return null;
|
||
|
|
|
||
|
|
var deadline = Date.now() + 2000;
|
||
|
|
|
||
|
|
try {
|
||
|
|
var stat = fs.statSync(transcriptPath);
|
||
|
|
if (stat.size > 30 * 1024 * 1024) return null;
|
||
|
|
|
||
|
|
var raw = fs.readFileSync(transcriptPath, 'utf8');
|
||
|
|
var lines = raw.split('\n').filter(Boolean);
|
||
|
|
var recent = lines.slice(-200);
|
||
|
|
|
||
|
|
var filePaths = {};
|
||
|
|
var functions = {};
|
||
|
|
var todos = [];
|
||
|
|
var decisions = [];
|
||
|
|
|
||
|
|
for (var i = 0; i < recent.length; i++) {
|
||
|
|
if (Date.now() > deadline) break;
|
||
|
|
|
||
|
|
var obj;
|
||
|
|
try { obj = JSON.parse(recent[i]); } catch { continue; }
|
||
|
|
|
||
|
|
var content = (obj && obj.message && obj.message.content) || (obj && obj.content);
|
||
|
|
if (!Array.isArray(content)) continue;
|
||
|
|
|
||
|
|
for (var j = 0; j < content.length; j++) {
|
||
|
|
var part = content[j];
|
||
|
|
if (!part) continue;
|
||
|
|
|
||
|
|
if (part.type === 'tool_use') {
|
||
|
|
var inp = part.input || {};
|
||
|
|
var fp = inp.file_path;
|
||
|
|
if (fp && typeof fp === 'string') {
|
||
|
|
filePaths[fp] = (filePaths[fp] || 0) + 1;
|
||
|
|
}
|
||
|
|
if (part.name === 'Grep' && inp.pattern) {
|
||
|
|
var match = inp.pattern.match(/[a-zA-Z_][a-zA-Z0-9_]{3,40}/);
|
||
|
|
if (match) functions[match[0]] = (functions[match[0]] || 0) + 1;
|
||
|
|
}
|
||
|
|
if (part.name === 'Edit' && inp.file_path) {
|
||
|
|
filePaths[inp.file_path] = (filePaths[inp.file_path] || 0) + 2;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var text = part.text || (typeof part === 'string' ? part : '');
|
||
|
|
if (typeof text === 'string' && text.length > 10) {
|
||
|
|
var todoRe = /(?:TODO|FIXME|HACK)[:\s](.{5,80})/gi;
|
||
|
|
var tm;
|
||
|
|
while ((tm = todoRe.exec(text)) !== null && todos.length < 5) {
|
||
|
|
todos.push(tm[1].trim());
|
||
|
|
}
|
||
|
|
var decRe = /(?:\u51b3\u5b9a|\u9009\u62e9|\u65b9\u6848|\u786e\u8ba4|decided|agreed|choose)[:\s](.{5,80})/gi;
|
||
|
|
var dm;
|
||
|
|
while ((dm = decRe.exec(text)) !== null && decisions.length < 5) {
|
||
|
|
decisions.push(dm[1].trim());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var out = ['[TSE\xb7RETENTION] Compact \u4fdd\u7559\u4ee5\u4e0b\u5173\u952e\u4e0a\u4e0b\u6587:'];
|
||
|
|
|
||
|
|
var sorted = Object.entries(filePaths).sort(function(a, b) { return b[1] - a[1]; }).slice(0, 10);
|
||
|
|
if (sorted.length > 0) {
|
||
|
|
out.push('## \u6d3b\u8dc3\u6587\u4ef6');
|
||
|
|
for (var si = 0; si < sorted.length; si++) {
|
||
|
|
var bn = sorted[si][0].split(/[\\/]/).pop();
|
||
|
|
out.push('- ' + bn + ' (' + sorted[si][1] + 'x) ' + sorted[si][0]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var fnArr = Object.keys(functions).slice(0, 15);
|
||
|
|
if (fnArr.length > 0) {
|
||
|
|
out.push('## \u5173\u952e\u6807\u8bc6\u7b26');
|
||
|
|
out.push(fnArr.join(', '));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (todos.length > 0) {
|
||
|
|
out.push('## \u5f85\u529e');
|
||
|
|
for (var ti = 0; ti < todos.length; ti++) out.push('- ' + todos[ti]);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (decisions.length > 0) {
|
||
|
|
out.push('## \u5173\u952e\u51b3\u7b56');
|
||
|
|
for (var di = 0; di < decisions.length; di++) out.push('- ' + decisions[di]);
|
||
|
|
}
|
||
|
|
|
||
|
|
return out.length > 1 ? out.join('\n') : null;
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = { extract: extract };
|