- 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被识别)
22 KiB
22 KiB
AI-Review 评审系统
目录
ai-review/
├── server/ # 后端 Express + TypeScript + better-sqlite3
├── web/ # 前端 React + TypeScript + Vite
├── config/ # teams.json(14 支参赛队伍结构化数据,含 gittea 凭据与选题)
├── docs/ # 文档(评审标准模板、user-stories.md 用户故事)
├── docs/design/ # 设计书(01-系统 / 02-API / 03-后台 / 04-前端)
└── AGENTS.md # 本文件
启动方式
# 后端(端口 3002)
cd server
npx tsc # 必须先编译(不能直接跑 ts)
node dist/index.js
# 前端(端口 14001)
cd web
npx vite --host 0.0.0.0 --port 14001
# 后端测试(vitest,在 server/ 下运行)
cd server && npm test
关键配置
| 项 | 值 | 位置 |
|---|---|---|
| 后端端口 | 3002 | server/src/config.ts |
| 前端端口 | 14001 | web/vite.config.ts |
| 登录密码 | 620f4c96 |
server/.env |
| DeepSeek API | api.deepseek.com |
server/src/services/review.service.ts |
| MAX_CONCURRENT | 3 | review.service.ts |
| DB 文件 | server/data/ai-review.db |
server/src/db.ts |
核心架构
评审管线(3 阶段)
Phase 1: 概览(1 次 AI 调用)
Phase 2: N 子 Agent 并行评审(按标准维度数,concurrency=3)
Phase 3: 确定性校准(computeCalibration) + 硬规则引擎
核心文件
| 文件 | 职责 |
|---|---|
review.service.ts (~1500行) |
评审引擎:clone→tryBuild→tryBrowse→AI→校准(computeCalibration)→硬规则→出分 |
standards.ts |
parseDimensions 解析标准 MD 为维度列表 |
l2-topics.ts |
L2考核选题元数据(11命题题+自选题,config/l2-topics.json):难度赋分 cap、功能完整性拆分、验收要点注入 |
l2-participants.ts |
L2受验者注册表(config/l2-participants.json):员工编号↔Gitea账号↔仓库映射,自动生成拉取 URL;拉取认证走全局评审账号只读协作者权限 |
code-inventory.ts |
全量符号索引(评审真实性改进 2026-08-26):对所有文本文件提取 path/lines/symbols,注入实现类维度让 AI 看见 100% 文件清单 |
code-signals.ts |
代码信号检测器:TODO 密度/空壳率/硬编码返回/死导入——确定性线索注入实现类维度 prompt,定位为线索而非判据 |
ai-log-audit.ts |
AI 日志确定性审计:占位率 / git 一致率 / 覆盖广度;占位>50%→维度≤30%、git一致<30%→≤20% 封顶 |
entries.ts |
条目 CRUD + 触发评审 + PDF 导出 |
projects.ts |
项目 CRUD + 汇总排名 |
db.ts |
SQLite 初始化 + migrations(ALTER TABLE try/catch) |
config.ts |
端口/密码/DeepSeek key |
关键流程
cloneRepo → discoverFiles → countCodeStats → tryBuild → tryBrowse/tryStart
→ 概览(精简context) → N子Agent(content+fileBlock) → 校准(computeCalibration) → 硬规则
→ 总分(rawScore) → 迟交扣分 → finalScore → review_snapshots
关键事实(修改前必读)
编译与重启
- TS 改动后必须
cd server && npx tsc,不能只改 .ts 不编译 server/dist/编译输出和server/src/分离- 启动:必须用
node dist/index.js(不能tsx watch,那需要 dev 模式) - 服务器崩溃后启动会自动恢复卡死的 entry(状态重置为 pending)
tryBuild
- 支持 7 种构建系统:npm/pom.xml/build.gradle/makefile/cargo/go/pyproject
- Python build 命令是
python -m build --wheel --no-isolation(不是pip install -e .) buildRootMap[file] === undefined判断是否存在(不是 falsy,根目录文件 relDir="")canBuild过滤 install 步骤:!command.startsWith('npm install')和!command.startsWith('pip install')
tryBrowse
- 用
puppeteer-core+ 自动检测 Chrome/Edge 路径(不自带 Chromium) waitUntil: 'domcontentloaded'(不是networkidle0,对 API 服务太严格)service_url提供时跳过 tryStart,直接 browse 该 URL- 看门狗:整体 45s 硬超时(BROWSE_WATCHDOG_MS),Chrome 启动/加载/关闭任一环节挂住也会被强制 kill 并降级为"跳过浏览器测试",不会卡死评审管线
- 评审期二次 SSRF 校验(
revalidateHost):打开 URL 前重新 DNS 解析域名,若已变为内网地址(重绑定攻击)则拦截并跳过浏览器测试;仅config.ssrfDnsCheck开启时生效
维度评审(runSubAgent)
- 评审依据优先级:标准快照
dim.content优先且唯一;内置dimGuidelines仅当 content 为空时兜底(并标注"内置兜底,仅供参考")。不要两套规则同时注入,否则 AI 会困惑 dimGuidelines是硬编码指南(key:场景价值/架构设计/工具使用/实现完整/开发范式/Agent核心/规模/代码规范/演示与文档/AI使用日志/效果与数据等),分值小计可能≠maxScore,仅兜底用DIM_FILE_FILTERS按维度过滤文件(如代码规范只给源码,演示文档只给 .md)filterFilesForDim输出时给文档文件标注(文档/说明文件,非代码),防止 AI 把 README/CLAUDE.md 内容当作代码问题上报fileBlock截断:普通维度 15000 字,构建维度 40000 字DIM_FILE_FILTERS的'AI使用日志'正则需同时覆盖ai_log/ai_usage/usage_log(如_AI_USAGE_LOG.md),否则该文件被过滤、AI 判"未发现日志"(2026-08-18 实测修复)extraContext只对构建维度(实现完整度)附加构建步骤详情projectContext包含服务状态行:参赛者服务地址: xxx(可正常访问/无法访问)
确定性证据(方案一 / 方案二,2026-08-07 新增)
- 方案一
evidence-detect.ts:Agent核心能力 4 项硬门槛由代码模式匹配做确定性判定(LLM调用/工具选择策略/错误重试降级/状态持久化),AI 不再判门槛、只做 5 项评分要素。检测结果(文件:行号)注入 Agent核心维度 prompt,含allPassed结论 - 方案二
test-runner.ts:tryTest在构建可用的前提下真跑测试。支持多框架输出解析——pytest/jest/go test -v/surefire(Maven)/Gradle tests completed/Cargo test result;覆盖率解析含 pytest-cov/jest/istanbul/go cover/jacoco。go test 自动加-v -cover。软证据——成功才注入真实通过率/覆盖率到"效果与数据"维度;环境失败/工具缺失一律中性不扣分(不证明项目差) - tryTest 与构建完全解耦(2026-08-13):无条件跑测试,不依赖
buildResult.canBuild。构建失败 ≠ 测试不可跑(cobol-java 的 build-backend 写错打不了包,但 pytest 能真跑 33/42)。runTest完整保留输出供解析——汇总行在输出尾部,截断到 3000 字会丢 passed/coverage 汇总(曾导致 testsPassed=0、覆盖率 null) - 维度独立原则(
review.service.tsrunSubAgent 严格规则):每个维度独立评分,其他维度的判定(如 Agent核心门槛是否通过、项目是否为 Agent 应用)不构成扣分依据——"效果与数据"评估测试覆盖/覆盖率/可复现性,即使非 Agent 项目,测试真实存在且通过就应据实给分 - 确定性测试证据强绑定:tryTest 真跑成功时
testEvidenceToPrompt注入"确定性证据,评分必须据此",AI 不得忽略或低估真实通过率/覆盖率(实测生效:calc 项目 Agent核心 0/25 但效果与数据 15/20 正常给分;cobol-java 效果与数据 0/20→6/20,AI 引用"42个用例33通过") - Agent核心检测置信度:
buildAgentGateReport按命中数标注置信度——0/低(1)/中(2-3)/高(≥4);低置信度通过项注入提示"可能误匹配,可在评分要素中酌情下调,但不得推翻门槛判定" - 阈值常量在
evidence-detect.ts:MAX_HITS=5;test-runner.ts:TEST_TIMEOUT=180s
校准(Phase 3b,确定性)
computeCalibration(standard-utils.ts)由代码计算,不信任 LLM 的 delta 数值- LLM 只输出跨维度语义矛盾的方向(over/under),代码定调幅:L1 矛盾±2、L2 σ异常(偏离>2σ)±4、L3 最不稳定维度(Agent核心/规模·功能点/效果与数据)高估×0.8
- 调幅常量在
review-constants.ts:CAL_ANOMALY_STDDEV=2.0 / CAL_L2_LIMIT=4 / CAL_L1_LIMIT=2 / CAL_UNSTABLE_WEIGHT=0.8 - 每个实际改动写入
calibrationExplanation(格式:校准执行:\n- 维度: 旧分→新分(L1/L2/L3)),可审计 - 老的
applyCalibration(LLM-delta)已删除,不要引用
代码健康度(countCodeStats)
- 返回 CodeStats:fileCount/totalLines/effectiveLines/blankLines/commentLines/duplicateRatio/dirDepth/tinyFiles
duplicateRatio用 MD5 哈希文件内容检测dirDepth.avg追踪目录嵌套深度- 结果注入
projectContext供 AI 参考
硬规则(Phase 3c,校准后执行)
- 构建失败→实现完整度≤4/12,效果与数据≤3/10
- pytest 失败→效果与数据≤5/10
- 重复代码>50%→代码规范≤3/5
- 无根目录 README→演示与文档≤2/5
- 日志写入 calibrationExplanation
评分
- 校准 delta 用
Math.round()取整(避免 52.400000000000006) - 总分 =
Math.round(每个维度分数)累加 finalScore=totalScore - 迟交扣分,上限max_score_cap- 迟交判定(2026-08-15 增强):
resolveSubmitTime解析提交时间——优先 git 最后 commit 时间;commit 缺失或早于条目创建时间(空仓库/提前 clone 旧代码/无 .git)→ 用条目创建时间兜底避免逃逸;两者都无效才用评审时刻。然后computeLateDays对比project.deadline
评审可信度(2026-08-19)
- 排名用多次聚合:
aggregateScores/aggregateEntryScores(standard-utils.ts)——最近 N 次(默认3)review_snapshots.score中位数;N=2 平均、N=1 单次。聚合前提=各快照standard_snapshot一致。<3 次标"初评(未达聚合样本)",排名区分正式/初评 - 快照分数列:
review_snapshots.score存含迟交扣分的 final_score(写快照时一并存,不解析 ai_report——它只有 totalScore)。历史快照 backfill 从 totalScore best-effort - 可验证能力三档(
classifyVerifiability,效果/提效类维度,校准之前判档):A=基准证据(entries.benchmark_jsonstatus=done);B=测试通过或覆盖率非 null;C=数据缺位→封顶 maxScore*0.3 + note"数据缺位(未证明),非无效"。构建成功≠效果可验证。C 档 note 渲染到前端维度表 + PDF。纯增益维度严格制(2026-08-26):提效幅度/效果对比/效率提升类维度(isPureGainDim)测试通过不构成提效证据——无基线对比数据一律 C 档封顶(实测案例:六边形战队提效幅度无量化数据 raw=6,修复后同类场景 C≤3)。定性设计维度豁免(2026-08-26):提效设计合理性/XX清晰度(isQualitativeDesignDim)考察设计思路而非量化结果,不参与 C 档封顶——避免测试环境因素跨队伍不公平(净码特攻案例) - 基准证据按 entry 落库(
entries.benchmark_json,非 env 变量——MAX_CONCURRENT=3 并发会串数据) - 确定性 L1(
detectStructuralContradictions):仅证据性矛盾触发(有测试/基准证据但效果≈0 → under;效果高分+实现全低 → over)。效果维度 under 一律丢弃(computeCalibration 内过滤)——效果维度只降不升,诚实由三档封顶负责。禁止"实现高分+无数据"当 under - overall 中性边界(
neutralizeTestEvidence):测试 summary 含"中性"→ 标[中性证据];不含("未检测到测试框架配置"=真缺测试)→ 真实弱点。synthesizeOverall prompt 禁止把[中性证据]列为不足 - 视频 URL 弱证据:
detectDemoVideo扫根目录 README + 根级 docs/*.md 的视频链接,source='url'(外部链接未核验)vs 'file'(强证据优先) - 维度级聚合:详情/PDF 维度表用
averageDimensions跨快照聚合(entries.ts GET /:entryId 返回 dimsAgg),展示"历次分差" - 决赛圈基准框架:
benchmark.tsrunBenchmark(dir, seeds, detect),检出率与基线分开呈现。seed 库赛前生成不公开(Goodhart 已知上限) - 人机标定:
docs/design/06-人机标定方案.md——绝对准确未验证前,分数用于排名(相对序)可信,绝对解读需标定偏差基线
parseDimensions
- 正则在
standards.ts,修复后支持空内容(content: "") - 从
## 维度名(分数)解析,必须紧跟\n##或字符串结束 - 过滤分区/说明性标题:
评审维度、合格判定、成果物清单、第X部分:等不进入维度列表 - group 推断:
## [Q2] xxx(显式)或## 2-A. xxx/## 2-A:xxx(题目追加维度前缀)→ group=Q2;其余 common - 验证:标准模板解析结果应固定为——赛道一 12维/150、赛道二 8维/100、L2考核 7维/100(全 common);人才测评已整合进 L2考核(2026-08-23),不再单独验证
entries 表
UNIQUE(project_id, repo_url)约束service_url TEXT DEFAULT ''(migration 添加)build_status TEXT DEFAULT ''(2026-08-18,migration 添加):赛道二/L2考核单阶段人工构建确认,''=未确认(自动构建) / done / failed;赛道一 B 阶段不存此列,走 /verify 请求体standard_snapshot存标准快照(评审时标准被修改也不影响已评审结果)review_snapshots存每次评审历史- L2考核专属字段:
selected_topic(选题 01~11/self,决定 max_score_cap 与难度赋分)、self_registration(自选题登记 JSON:登记编号+功能清单快照) - force-review(测试端点)需
ADMIN_TEST_TOKEN=true且NODE_ENV=test双条件才开启;生产node dist/index.js即使 flag 泄漏也打不开 - 赛道必选(2026-08-13):创建/编辑项目必须传 track(赛道一/赛道二/L2考核),缺省返回 400
赛道为必选项;前端下拉首项"请选择赛道(必选)",create() 校验if (!track) setError('请选择赛道')。注意:有赛道会自动导入对应默认标准(projects.tsPOST 里loadDefaultStandard)——创建项目的测试用例必须带合法 track,否则建出的项目无赛道且后续标准解析行为不同。e2e 陷阱:依赖"自定义标准快照"的用例(如hardcode-fixes.spec.ts3.1/3.2)在带 track 创建项目后,自动标准会抢占resolveStandard匹配,导致条目快照不是测试上传的标准——需先调用apiClearStandards(request, token, pid)清掉自动标准再上传自定义标准
本地评审样本
server/data/clone/_real_cobol_java/:真实仓库(cobol-java-v3,COBOL→Java 测试生成工具)的本地副本,作真实评审冒烟对象- 用它跑过 6 次真实 DeepSeek 评审:30(旧校准)→52(确定性门槛)→58→67(效果与数据 0→6,tryTest 解耦构建后)→55(第 5 次评审,校准 anomalies 触发的自然波动)。数据在
data/e2e-usertest.db的 review_snapshots(该项目 track=赛道一,12 维标准) - 注意:评审结束会删除 clone 目录(
executeReview末尾fs.rmSync(dir)),只留 review_snapshots;若需现场调试 clone 内容,评审前复制一份出来
管线诊断日志([pipe:entryId])
pipeLog(entryId, phase, msg, durMs?)只写服务端 console(不进 progress_log,不污染前端)- 覆盖阶段:START / CLONE / ANALYZE(含 canBuild·untested)/ GATES / TEST(command·pass/fail·coverage·summary,这是"证据静默丢失"类问题的 debug 入口)/ BROWSE(serviceUrl·pageLoaded)/ OVERVIEW / SUBAGENT(12 个 DIM 各带耗时)/ OVERALL(整体评价合成,pos/hl/wk,失败打印 failed)/ DONE(score·penalty·final·总耗时)/ FAIL(
runReviewcatch 兜底,任何阶段抛错都会有[pipe:xxx] FAIL <msg> [耗时ms],不会再静默卡 status=failed) - 实测 cobol 第 5 次评审:ANALYZE 24s(tryBuild 跑 python 构建超时)、TEST 10s(
python -m pytest33/42 pass、coverage null)、DIM 每维度 4.6~24.7s、全程 114s
整体评价合成(方案A,2026-08-19)
synthesizeOverall(review.service.ts):校准+硬规则之后,+1 次 LLM 调用(约 15-30s),把 overview+维度得分评语+确定性证据合成点评式整体评价,写入ai_report.overall(JSON:highlights[{point,review}]/weaknesses[{point,review}]/verdict)- 项目总览(overview)负责中立描述,overall 只做点评:每条亮点/不足都带判断(价值/影响/严重度),不重复定位
- 输入全是真实证据,prompt 显式"禁止编造";失败非致命(overall=null,不影响评分)
- 兼容旧格式(字符串数组 → point,review 空)
- 落点:赛道一 B 尾(executeReviewB)与赛道二单阶段尾(executeReview)各一次;A 阶段不合成
- 展示:前端条目详情"整体评价"段(亮点点评绿/不足点评红/总评,ProjectView.tsx)+ PDF 报告块(pdf.service.ts)
- 纯函数
parseOverallResponse负责 JSON 解析/代码块剥离/裁剪(overall.test.ts 单测 7 用例)
已知坑
- Chrome 崩溃导致服务器卡死(已缓解) — tryBrowse 有整体 45s 看门狗(BROWSE_WATCHDOG_MS),挂住会强制 kill Chrome 并降级"跳过浏览器测试",但仍建议留意 browse 期间系统资源占用
- 多人提交排队 — MAX_CONCURRENT=3,第 4 个 entry 起入队列排队等空位(
queue[]+processQueue) - AI 从 docs 编造代码问题(已缓解) — fileBlock 中文档文件已标注
(文档/说明文件,非代码),但 AI 仍可能把标准模板内容误当代码问题,若再出现可在 prompt 强化 - tryBrowse SSRF 风险(已缓解) — service_url 保存时校验 + 评审期
revalidateHost二次 DNS 解析(重绑定拦截,仅config.ssrfDnsCheck开启时生效),仍建议沙箱 - review.service.ts 1060 行 — 单文件过大,改动前必须理解整个 pipeline
- difficulty 字段已移除 — 不要搜或引用 difficulty(前端已改为"赛道"下拉)
- 本地 file:// 仓库 URL 格式 — 必须用
file://D:/...(两斜杠);file:///D:/...(三斜杠)会被path.resolve解析成D:\D:\...导致白名单拒绝(2026-08-07 实测) - 测试数据会污染"真实数据"判断 — 库里大量
选手X/小明作品/k4-entry是 e2e 造的假条目;判断真实评审数据要看repo_url(真实 https/本地副本)而非条目名
测试
# 后端单元测试(vitest)
cd server && npm test
# 特定测试文件
cd server && npm test -- src/__tests__/api.test.ts
# 用户故事驱动验收(docs/user-stories.md,纯函数级,不调 AI,20 用例)
cd server && npm test -- src/__tests__/user-stories.test.ts
# E2E(需前后端都启动)
# web/e2e 目录有 Playwright 测试
## E2E 运行陷阱(2026-08-14 实测)
- **整套 full-e2e 需 15+ 分钟**,单个 describe 单独跑更快(评审标准 7、条目管理 10、批量/详情/汇总/异常/UI 27、完整流程 1 = 45 个测试)。整套跑超时是正常现象,不是 bug
- **孤儿 tsx server 会卡死所有 DB 请求**:多次中断 e2e 会残留多组 `tsx src/index.ts` 进程同时打开 `data/ai-review.db`,better-sqlite3 WAL 锁冲突导致 login/beforeAll 全部挂起 90s。症状:health 慢、login 超时、beforeAll timeout。排查:`Get-CimInstance Win32_Process -Filter "Name='node.exe'" | where CommandLine -like "*tsx*src/index.ts*"`,全杀后重跑
- **`apiClearStandards` 的 BASE 必须带 `/api`**(`http://localhost:3002/api`),漏掉会打到 `/projects/:id/standards`(无路由)返回 404 HTML,`list.json()` 抛 `SyntaxError: Unexpected token '<'`
- **补 track 后项目头部和表格赛道列都会渲染 `<span class="badge">赛道一</span>`**,原 `.badge` 选择器断言会多命中——用 `.entry-table .badge` 限定,且状态文案是中文(`待评审` 而非 `pending`,见 `ProjectView.tsx` statusBadge)
数据库操作
# 手动查 SQLite
cd server && node -e "const D=require('better-sqlite3');const d=new D('data/ai-review.db');console.log(d.prepare('SELECT name FROM sqlite_master WHERE type=\"table\"').all())"
评审标准格式
## 维度名(满分X分)
内容描述...
## 下一个维度(满分Y分)
内容描述...
标准 MD 必须至少有一个 ## 维度名(分数) 格式的标题。
内置标准模板(由项目管理员上传)
赛道一(Agent开发实战赛): 场景价值15、开发范式与架构设计25、工具使用10、实现完整度15、规模·功能点·技术难度10、演示与文档5、AI使用日志10、效果评估与数据10
赛道二(IDE+开发范式创新赛): 场景价值、开发范式、工具使用、实现完整度、规模·功能点、演示与文档、AI使用日志、效果评估与数据(总分100)
L2考核(AI人才育成认证,7维/100): 功能完整性30、设计文档10、测试用例与测试结果10、AI协作过程记录15、技术选型与范式运用15、代码质量+README 10、业务场景理解与需求分析10。选题难度赋分:★★+0/★★★+5/★★★★+10,封顶选题 cap(100/105/110)。合格线≥60 且 功能完整性≥15(地板线)。
当前维度 guideline 只覆盖标准维度名(如"场景价值与合理性"),赛道一变体名(如"开发范式与架构设计")需扩展匹配关键词。L2考核 7 维标准自带分档锚点与交叉验证规则,content 优先不触发内置兜底。