/** * 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 };