reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,384 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
function parseArgs(argv) {
const args = {
caseDir: '',
traces: [],
maxLines: 200000,
json: false,
markdown: false,
};
for (let i = 2; i < argv.length; i++) {
const arg = argv[i];
if (arg === '--case-dir' || arg === '--case' || arg === '-d') args.caseDir = argv[++i] || '';
else if (arg === '--trace' || arg === '--input' || arg === '-i') args.traces.push(argv[++i] || '');
else if (arg === '--max-lines') args.maxLines = Number(argv[++i] || args.maxLines);
else if (arg === '--json') args.json = true;
else if (arg === '--markdown') args.markdown = true;
else if (arg === '--help' || arg === '-h') args.help = true;
else throw new Error(`未知参数:${arg}`);
}
if (!args.json && !args.markdown) args.markdown = true;
return args;
}
function usage() {
return `用法:
node scripts/analyze_trace_complexity.js --case-dir case --markdown
node scripts/analyze_trace_complexity.js --trace case/ruyi-trace/logs/trace.ndjson --json
node scripts/analyze_trace_complexity.js --trace case/tmp/env-trace.jsonl --markdown
说明:基于 RuyiTrace / Node trace 日志评估补环境复杂度、风险点和优先级。复杂度评估只用于理解项目,不自动决定是否使用 isolated-vm、vm 或 jsEnv。`;
}
function exists(p) {
try {
fs.accessSync(p);
return true;
} catch {
return false;
}
}
function stat(p) {
try {
return fs.statSync(p);
} catch {
return null;
}
}
function walk(p, out = []) {
if (!exists(p)) return out;
const st = stat(p);
if (!st) return out;
if (st.isDirectory()) {
let names = [];
try {
names = fs.readdirSync(p);
} catch {
names = [];
}
for (const name of names) walk(path.join(p, name), out);
} else if (st.isFile()) {
out.push(p);
}
return out;
}
function rel(root, p) {
return (path.relative(root, p) || '.').replace(/\\/g, '/');
}
function uniq(arr) {
return [...new Set(arr)].filter(Boolean).sort();
}
const CATEGORY_PATTERNS = [
['navigator', /\bnavigator\b|Navigator\.prototype|userAgent|webdriver|plugins|mimeTypes|hardwareConcurrency|deviceMemory/ig],
['document', /\bdocument\b|Document\.prototype|document\.cookie|querySelector|createElement|getElementById|documentElement|document\.all/ig],
['iframe-realm', /iframe|contentWindow|contentDocument|defaultView|srcdoc|document\.write|document\.close/ig],
['location', /\blocation\b|Location\.prototype|href|origin|hostname|pathname|search|hash/ig],
['screen', /\bscreen\b|Screen\.prototype|availWidth|availHeight|colorDepth|pixelDepth|orientation/ig],
['storage', /localStorage|sessionStorage|Storage\.prototype|getItem|setItem|removeItem/ig],
['cookie', /document\.cookie|Set-Cookie|\bcookie\b/ig],
['crypto', /\bcrypto\b|getRandomValues|randomUUID|SubtleCrypto|crypto\.subtle/ig],
['performance-time', /performance|performance\.now|timeOrigin|Date\.now|new Date|setTimeout|requestAnimationFrame/ig],
['canvas', /HTMLCanvasElement|CanvasRenderingContext2D|toDataURL|toBlob|getImageData|measureText|canvas/ig],
['webgl', /WebGLRenderingContext|WebGL2RenderingContext|getParameter|readPixels|getSupportedExtensions|getShaderPrecisionFormat|webgl/ig],
['webgpu', /navigator\.gpu|GPUAdapter|requestAdapter|webgpu/ig],
['audio', /AudioContext|OfflineAudioContext|AnalyserNode|startRendering|getChannelData/ig],
['dom-geometry', /getBoundingClientRect|DOMRect|offsetWidth|offsetHeight|clientWidth|clientHeight|getClientRects/ig],
['dom-cssom', /DOMParser|innerHTML|HTMLUnknownElement|HTMLTextAreaElement|HTMLOptionElement|HTMLSelectElement|HTMLCollection|ShadowRoot|CSSStyleSheet|CSSStyleRule|CSSRuleList|getComputedStyle/ig],
['worker', /\bWorker\b|importScripts|postMessage|MessageChannel|MessagePort|BroadcastChannel/ig],
['wasm', /WebAssembly|instantiateStreaming|compileStreaming|\.wasm/ig],
['network', /\bfetch\b|XMLHttpRequest|Request\.prototype|Response\.prototype|Headers\.prototype|sendBeacon/ig],
['xhr-fetch-session-bridge', /live-session-bridge|offline-fixture|XMLHttpRequest|fetch|sendBeacon|curl_cffi|curl-cffi|CycleTLS|impers/ig],
['object-shape', /Object\.keys|Object\.getOwnPropertyNames|Object\.getOwnPropertyDescriptor|Object\.getOwnPropertySymbols|Reflect\.ownKeys|hasOwnProperty|propertyIsEnumerable|brand check|prototype walk/ig],
['private-state-leakage', /__readyState|__headers|__children|__parentNode|__responseHeaders|Object\.defineProperty\s*\([^)]*['"]_{1,2}[A-Za-z]|\bthis\s*\.\s*_{1,2}[A-Za-z]/ig],
['indexedDB', /indexedDB|IDBFactory|IDBRequest|IDBOpenDBRequest|IDBKeyRange|IDBDatabase/ig],
['history', /\bhistory\b|pushState|replaceState|popstate|referrer/ig],
['event-clone-error', /EventTarget|structuredClone|DataCloneError|ownKeys|ownNames|Reflect\.ownKeys|getOwnPropertyNames/ig],
['clock-timer', /Date\.now|performance\.now|setTimeout|clearTimeout|queueMicrotask|requestAnimationFrame/ig],
['writer-branch', /reload writer|form writer|final writer|HTMLFormElement\.submit|Location\.reload|cf-chl-gen|cf-chl-out|generatedForm/ig],
['performance-timeline', /PerformanceObserver|PerformanceResourceTiming|PerformanceEntry|PerformanceObserverEntryList|getEntries|PerformancePaintTiming|performance\.mark/ig],
];
const REALISM_PATTERNS = [
['Function.prototype.toString', /Function\.prototype\.toString|\.toString\s*\(\)/ig],
['Object.prototype.toString', /Object\.prototype\.toString|\[object\s+[A-Za-z]+]/ig],
['descriptor', /Object\.getOwnPropertyDescriptor|Object\.getOwnPropertyDescriptors|defineProperty|getOwnPropertyDescriptor/ig],
['prototype', /Object\.getPrototypeOf|setPrototypeOf|__proto__|prototype/ig],
['ownKeys', /Reflect\.ownKeys|Object\.keys|getOwnPropertyNames|getOwnPropertySymbols/ig],
['instanceof', /\binstanceof\b/ig],
['constructor.name', /constructor\.name|\.constructor\b/ig],
['Symbol.toStringTag', /Symbol\.toStringTag|@@toStringTag/ig],
];
const ASYNC_PATTERNS = [
['Promise', /\bPromise\b|queueMicrotask|then\(/ig],
['timer', /setTimeout|setInterval|requestAnimationFrame|cancelAnimationFrame/ig],
['message', /postMessage|MessageChannel|BroadcastChannel|message event/ig],
['observer', /MutationObserver|IntersectionObserver|ResizeObserver|PerformanceObserver/ig],
['xhr-fetch', /\bfetch\b|XMLHttpRequest|readystatechange|loadend/ig],
['indexedDB-async', /IDBRequest|onsuccess|onerror|transaction|openCursor/ig],
];
function countMatches(text, pattern) {
pattern.lastIndex = 0;
let count = 0;
while (pattern.exec(text)) count += 1;
return count;
}
function emptySignalMap(items) {
return Object.fromEntries(items.map(([name]) => [name, 0]));
}
function discoverTraceFiles(args) {
const files = [];
for (const item of args.traces) {
const resolved = path.resolve(item);
if (exists(resolved)) files.push(resolved);
}
if (args.caseDir) {
const caseDir = path.resolve(args.caseDir);
const candidates = [
path.join(caseDir, 'ruyi-trace', 'logs'),
path.join(caseDir, 'tmp'),
path.join(caseDir, 'notes'),
];
for (const candidate of candidates) {
for (const file of walk(candidate)) {
if (/\.(ndjson|jsonl|json)$/i.test(file) && /trace|missing-env|ruyi|env/i.test(path.basename(file))) {
files.push(file);
}
}
}
}
return uniq(files);
}
function inspectLine(text, summary) {
for (const [name, pattern] of CATEGORY_PATTERNS) {
summary.categories[name] += countMatches(text, pattern);
}
for (const [name, pattern] of REALISM_PATTERNS) {
summary.realism[name] += countMatches(text, pattern);
}
for (const [name, pattern] of ASYNC_PATTERNS) {
summary.async[name] += countMatches(text, pattern);
}
const stackMatches = text.match(/(?:stack\.file|filename|file|url)["']?\s*[:=]\s*["']?([^"',\s)]+)/ig);
if (stackMatches) {
for (const raw of stackMatches.slice(0, 20)) summary.stackSignals.add(raw.slice(0, 160));
}
}
function analyzeFile(file, args) {
const result = {
file,
lines: 0,
bytes: 0,
truncatedByMaxLines: false,
parseErrors: 0,
};
const text = fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '');
result.bytes = Buffer.byteLength(text);
const lines = text.split(/\r?\n/);
for (const line of lines) {
if (!line.trim()) continue;
if (result.lines >= args.maxLines) {
result.truncatedByMaxLines = true;
break;
}
result.lines += 1;
}
return { result, lines: lines.slice(0, args.maxLines) };
}
function scoreSummary(summary) {
const hitCategories = Object.entries(summary.categories).filter(([, count]) => count > 0).map(([name]) => name);
const hitRealism = Object.entries(summary.realism).filter(([, count]) => count > 0).map(([name]) => name);
const hitAsync = Object.entries(summary.async).filter(([, count]) => count > 0).map(([name]) => name);
const fingerprintHits = ['canvas', 'webgl', 'webgpu', 'audio', 'dom-geometry', 'dom-cssom'].filter(name => summary.categories[name] > 0);
const stateHits = ['storage', 'cookie', 'indexedDB', 'history', 'performance-time', 'performance-timeline', 'crypto', 'clock-timer'].filter(name => summary.categories[name] > 0);
const envMatrixHitMap = [
['iframe-realm', 'iframe-realm'],
['worker', 'worker-task'],
['performance-timeline', 'performance-timeline'],
['dom-cssom', 'dom-cssom'],
['event-clone-error', 'event-clone-error'],
['xhr-fetch-session-bridge', 'xhr-fetch-session-bridge'],
['object-shape', 'object-shape'],
['private-state-leakage', 'private-state-leakage'],
['clock-timer', 'clock-timer'],
['writer-branch', 'writer-branch'],
];
const envMatrixHits = envMatrixHitMap
.filter(([source]) => summary.categories[source] > 0)
.map(([, id]) => id);
let score = 0;
score += hitCategories.length * 2;
score += hitRealism.length * 3;
score += fingerprintHits.length * 4;
score += hitAsync.length * 2;
score += stateHits.length * 2;
score += envMatrixHits.length * 3;
if (summary.stackSignals.size >= 10) score += 6;
else if (summary.stackSignals.size >= 3) score += 3;
let level = '未知';
if (summary.files.length === 0 || summary.totalLines === 0) level = '未知';
else if (score >= 45 || fingerprintHits.length >= 3 || hitRealism.length >= 5) level = '高';
else if (score >= 18 || fingerprintHits.length >= 1 || hitRealism.length >= 2) level = '中';
else level = '低';
return {
score,
level,
hitCategories,
hitRealism,
hitAsync,
fingerprintHits,
stateHits,
envMatrixHits,
stackSignalCount: summary.stackSignals.size,
};
}
function analyze(args) {
const files = discoverTraceFiles(args);
const root = args.caseDir ? path.resolve(args.caseDir) : process.cwd();
const summary = {
files: files.map(file => rel(root, file)),
totalLines: 0,
totalBytes: 0,
categories: emptySignalMap(CATEGORY_PATTERNS),
realism: emptySignalMap(REALISM_PATTERNS),
async: emptySignalMap(ASYNC_PATTERNS),
fileSummaries: [],
stackSignals: new Set(),
parseErrors: 0,
};
for (const file of files) {
const { result, lines } = analyzeFile(file, args);
summary.fileSummaries.push({
...result,
file: rel(root, file),
});
summary.totalLines += result.lines;
summary.totalBytes += result.bytes;
for (const line of lines) {
if (!line.trim()) continue;
inspectLine(line, summary);
}
}
const score = scoreSummary(summary);
return {
passed: true,
complexity: score.level,
score: score.score,
source: {
files: summary.files,
totalLines: summary.totalLines,
totalBytes: summary.totalBytes,
fileSummaries: summary.fileSummaries,
},
signals: {
categories: summary.categories,
realism: summary.realism,
async: summary.async,
hitCategories: score.hitCategories,
hitRealism: score.hitRealism,
hitAsync: score.hitAsync,
fingerprintHits: score.fingerprintHits,
stateHits: score.stateHits,
envMatrixHits: score.envMatrixHits,
stackSignalCount: score.stackSignalCount,
},
conclusion: {
frameworkBinding: false,
note: 'Trace 复杂度评估只用于补环境范围、风险点和优先级,不自动决定是否使用 isolated-vm、vm 或 jsEnv。',
},
};
}
function renderMarkdown(result) {
const lines = [];
lines.push('# Trace 复杂度评估报告');
lines.push('');
lines.push(`- 复杂度等级:${result.complexity}`);
lines.push(`- 分数:${result.score}`);
lines.push(`- Trace 文件数:${result.source.files.length}`);
lines.push(`- Trace 总行数:${result.source.totalLines}`);
lines.push('- 与补环境框架关系:复杂度评估不决定框架选择,是否使用 isolated-vm / vm / jsEnv 仍以用户确认为准。');
lines.push('');
if (!result.source.files.length) {
lines.push('未发现可用 Trace 日志,无法基于日志评估复杂度。');
lines.push('');
return lines.join('\n');
}
lines.push('## 命中的 WebAPI 类别');
lines.push('');
lines.push(result.signals.hitCategories.length ? result.signals.hitCategories.map(x => `- ${x}:${result.signals.categories[x]}`).join('\n') : '- 无明显命中');
lines.push('');
lines.push('## 真实性检测信号');
lines.push('');
lines.push(result.signals.hitRealism.length ? result.signals.hitRealism.map(x => `- ${x}:${result.signals.realism[x]}`).join('\n') : '- 无明显命中');
lines.push('');
lines.push('## 指纹、异步和状态风险');
lines.push('');
lines.push(`- 指纹 API:${result.signals.fingerprintHits.join('、') || '无明显命中'}`);
lines.push(`- 异步链路:${result.signals.hitAsync.join('、') || '无明显命中'}`);
lines.push(`- 状态依赖:${result.signals.stateHits.join('、') || '无明显命中'}`);
lines.push(`- WebAPI 环境检测矩阵触发类:${result.signals.envMatrixHits.join('、') || '无明显命中'}`);
lines.push(`- 调用栈分散信号数:${result.signals.stackSignalCount}`);
lines.push('');
lines.push('## 文件摘要');
lines.push('');
lines.push('| 文件 | 行数 | 字节数 | 达到最大行限制 |');
lines.push('|---|---:|---:|---|');
for (const item of result.source.fileSummaries) {
lines.push(`| ${item.file} | ${item.lines} | ${item.bytes} | ${item.truncatedByMaxLines ? '是' : '否'} |`);
}
lines.push('');
lines.push('## 使用说明');
lines.push('');
lines.push('- 该报告用于补环境范围、优先级和阶段报告。');
lines.push('- 不要把复杂度等级写成自动选择补环境框架的依据。');
lines.push('- 补环境框架选择仍然必须由用户确认;用户未选择时默认不使用框架。');
return lines.join('\n');
}
function main() {
const args = parseArgs(process.argv);
if (args.help) {
console.log(usage());
return;
}
const result = analyze(args);
if (args.json) console.log(JSON.stringify(result, null, 2));
else console.log(renderMarkdown(result));
}
try {
main();
} catch (error) {
console.error(error && error.message ? error.message : String(error));
process.exitCode = 2;
}