Files
L2keka/server/src/services/code-inventory.ts
T
hangshuo652 f8b4e9d44d 评审真实性P1落地:全量代码可见性+Mock信号+AI日志审计+定向追踪+功能发现轮
- code-inventory.ts:全量符号索引,注入实现类维度,AI 可见100%文件清单
- code-signals.ts:代码信号检测器(TODO密度/空壳率/硬编码返回/死导入),修复TODO检测顺序bug
- ai-log-audit.ts:AI日志确定性审计(占位率/git一致率/覆盖广度),占位>50%→≤30%、git一致<30%→≤20%封顶
- standard-utils.ts:isQualitativeDesignDim 定性设计维度豁免C档
- review.service.ts:注入三个模块+定向追踪模板+日志封顶+分支提醒+Map-Reduce功能发现轮
- 预算 15000→40000;新增 review-authenticity.test.ts(14用例)
- 实测:净码特攻b3 81分(118功能全量识别),六边形50~62分(Mock被识别)
2026-08-27 09:23:02 +08:00

110 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import path from 'path';
/**
* 全量代码可见性(2026-08-26 评审真实性改进方案 §3.1):
* 对仓库全部文本文件生成轻量签名索引,让评审 AI 看见 100% 文件清单,
* 替代"前60个文件+15K截断"导致的盲区。
*/
export interface FileSignature {
path: string;
lines: number;
lang: 'ts' | 'js' | 'py' | 'other';
symbols: string[];
}
const CODE_LANG: Record<string, 'ts' | 'js' | 'py'> = {
'.ts': 'ts', '.tsx': 'ts', '.mts': 'ts', '.cts': 'ts',
'.js': 'js', '.jsx': 'js', '.mjs': 'js', '.cjs': 'js',
'.py': 'py',
};
/** 可提取符号的文本扩展名(其余只记 path+lines */
const TEXT_EXTS = new Set([
...Object.keys(CODE_LANG),
'.json', '.yaml', '.yml', '.md', '.html', '.css', '.scss', '.vue',
'.sql', '.sh', '.java', '.go', '.rs', '.rb', '.php', '.cs', '.kt',
]);
// 各语言的定义行模式(保守匹配,宁缺勿滥)
const SYMBOL_PATTERNS: Record<string, RegExp[]> = {
ts: [
/^\s*(export\s+)?(default\s+)?(async\s+)?function\s+\w+/,
/^\s*(export\s+)?(abstract\s+)?class\s+\w+/,
/^\s*export\s+(const|let|var)\s+\w+\s*=\s*(\(|async|function)/,
/^\s*export\s+(interface|type|enum)\s+\w+/,
/^\s*(public|private|protected|static)?\s*(async\s+)?\w+\s*\([^)]*\)\s*[:{]/,
],
js: [
/^\s*(export\s+)?(default\s+)?(async\s+)?function\s+\w+/,
/^\s*(export\s+)?(class\s+\w+)/,
/^\s*(export\s+)?(const|let|var)\s+\w+\s*=\s*(\(|async|function|\w+\s*=>)/,
/^\s*(module\.exports|exports\.\w+)\s*=/
],
py: [
/^\s*def\s+\w+/,
/^\s*class\s+\w+/,
],
};
export function extractSignatures(files: { path: string; content?: string; size?: number }[]): FileSignature[] {
const out: FileSignature[] = [];
for (const f of files) {
const ext = path.extname(f.path).toLowerCase();
const rel = f.path.replace(/\\/g, '/');
// 行数:有内容用内容算,否则用 size 粗估
let lines = 0;
let lang: FileSignature['lang'] = 'other';
const codeLang = CODE_LANG[ext];
if (codeLang) lang = codeLang;
let content: string | undefined;
if ('content' in f && typeof (f as any).content === 'string') {
content = (f as any).content as string;
lines = content.split('\n').length;
} else if (f.size != null) {
lines = Math.max(1, Math.round(f.size / 38)); // 平均 38 字节/行 估算
} else {
lines = 1;
}
if (!TEXT_EXTS.has(ext)) {
out.push({ path: rel, lines, lang: 'other', symbols: [] });
continue;
}
const symbols: string[] = [];
if (content && codeLang) {
const patterns = SYMBOL_PATTERNS[codeLang] || [];
for (const line of content.split('\n')) {
if (symbols.length >= 5) break;
for (const re of patterns) {
if (re.test(line)) {
symbols.push(line.trim().slice(0, 100));
break;
}
}
}
}
out.push({ path: rel, lines, lang, symbols });
}
return out;
}
/** 渲染紧凑索引文本(供注入 prompt)——全量文件,每文件 1~3 行 */
export function renderInventoryText(sigs: FileSignature[], budgetChars = 8000): string {
const lines: string[] = ['', '## 仓库文件全量索引(共 ' + sigs.length + ' 个文件,此清单为完整底账,评分必须覆盖其中核心文件)'];
let used = lines.join('\n').length;
for (const s of sigs) {
const symLine = s.symbols.length ? ' | ' + s.symbols.slice(0, 2).join(' ; ') : '';
const l = `- ${s.path} (${s.lines}${s.lang !== 'other' ? ',' + s.lang : ''})${symLine}`;
if (used + l.length > budgetChars) {
lines.push(`- …(其余 ${sigs.length - sigs.indexOf(s)} 个文件见截断说明,均为外围配置/资源)`);
break;
}
lines.push(l);
used += l.length;
}
return '\n' + lines.join('\n');
}