Files
L2keka/AGENTS.md
T
hangshuo652 6a2419d4a4 L2考核落地:人才测评整合为L2考核track,新增查重初筛与e2e验证
- 新增 L2考核 track(7维标准/100分):选题难度赋分cap、功能完整性地板线、合格判定
- 人才测评整合进 L2考核:移除 L2/L3 两级认定与 question_id 机制
- 新增 l2-topics 选题元数据服务与 config/l2-topics.json(11命题题+自选题)
- 新增查重初筛 plagiarism-detect(MD5精确比对+归一化相似度+提交行为,仅告警)
- 修复 L2 维度 DIM_FILE_FILTERS 缺失导致 AI 协作记录证据漏喂
- e2e:修复 Windows spawn、DB 隔离,L2 用例 27 项全绿
2026-08-23 20:58:56 +08:00

20 KiB
Raw Blame History

AI-Review 评审系统

目录

ai-review/
├── server/          # 后端 Express + TypeScript + better-sqlite3
├── web/             # 前端 React + TypeScript + Vite
├── config/          # teams.json14 支参赛队伍结构化数据,含 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、功能完整性拆分、验收要点注入
entries.ts 条目 CRUD + 触发评审 + PDF 导出
projects.ts 项目 CRUD + 汇总排名
db.ts SQLite 初始化 + migrationsALTER 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.tstryTest 在构建可用的前提下真跑测试。支持多框架输出解析——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.ts runSubAgent 严格规则):每个维度独立评分,其他维度的判定(如 Agent核心门槛是否通过、项目是否为 Agent 应用)不构成扣分依据——"效果与数据"评估测试覆盖/覆盖率/可复现性,即使非 Agent 项目,测试真实存在且通过就应据实给分
  • 确定性测试证据强绑定tryTest 真跑成功时 testEvidenceToPrompt 注入"确定性证据,评分必须据此",AI 不得忽略或低估真实通过率/覆盖率(实测生效:calc 项目 Agent核心 0/25 但效果与数据 15/20 正常给分;cobol-java 效果与数据 0/20→6/20AI 引用"42个用例33通过"
  • Agent核心检测置信度buildAgentGateReport 按命中数标注置信度——0/低(1)/中(2-3)/高(≥4);低置信度通过项注入提示"可能误匹配,可在评分要素中酌情下调,但不得推翻门槛判定"
  • 阈值常量在 evidence-detect.tsMAX_HITS=5test-runner.tsTEST_TIMEOUT=180s

校准(Phase 3b,确定性)

  • computeCalibrationstandard-utils.ts)由代码计算,不信任 LLM 的 delta 数值
  • LLM 只输出跨维度语义矛盾的方向(over/under),代码定调幅:L1 矛盾±2、L2 σ异常(偏离>2σ)±4、L3 最不稳定维度(Agent核心/规模·功能点/效果与数据)高估×0.8
  • 调幅常量在 review-constants.tsCAL_ANOMALY_STDDEV=2.0 / CAL_L2_LIMIT=4 / CAL_L1_LIMIT=2 / CAL_UNSTABLE_WEIGHT=0.8
  • 每个实际改动写入 calibrationExplanation(格式:校准执行:\n- 维度: 旧分→新分(L1/L2/L3),可审计
  • 老的 applyCalibrationLLM-delta)已删除,不要引用

代码健康度(countCodeStats

  • 返回 CodeStatsfileCount/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/aggregateEntryScoresstandard-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_json status=done);B=测试通过或覆盖率非 null;C=数据缺位→封顶 maxScore*0.3 + note"数据缺位(未证明),非无效"。构建成功≠效果可验证。C 档 note 渲染到前端维度表 + PDF
  • 基准证据按 entry 落库entries.benchmark_json,非 env 变量——MAX_CONCURRENT=3 并发会串数据)
  • 确定性 L1detectStructuralContradictions):仅证据性矛盾触发(有测试/基准证据但效果≈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.ts runBenchmark(dir, seeds, detect),检出率与基线分开呈现。seed 库赛前生成不公开(Goodhart 已知上限)
  • 人机标定docs/design/06-人机标定方案.md——绝对准确未验证前,分数用于排名(相对序)可信,绝对解读需标定偏差基线

parseDimensions

  • 正则在 standards.ts,修复后支持空内容(content: ""
  • ## 维度名(分数) 解析,必须紧跟 \n## 或字符串结束
  • 过滤分区/说明性标题:评审维度合格判定成果物清单第X部分: 等不进入维度列表
  • group 推断:## [Q2] xxx(显式)或 ## 2-A. xxx / ## 2-Axxx(题目追加维度前缀)→ 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-18migration 添加):赛道二/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.ts POST 里 loadDefaultStandard)——创建项目的测试用例必须带合法 track,否则建出的项目无赛道且后续标准解析行为不同。e2e 陷阱:依赖"自定义标准快照"的用例(如 hardcode-fixes.spec.ts 3.1/3.2)在带 track 创建项目后,自动标准会抢占 resolveStandard 匹配,导致条目快照不是测试上传的标准——需先调用 apiClearStandards(request, token, pid) 清掉自动标准再上传自定义标准

本地评审样本

  • server/data/clone/_real_cobol_java/:真实仓库(cobol-java-v3COBOL→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 / TESTcommand·pass/fail·coverage·summary这是"证据静默丢失"类问题的 debug 入口/ BROWSEserviceUrl·pageLoaded/ OVERVIEW / SUBAGENT12 个 DIM 各带耗时)/ OVERALL(整体评价合成,pos/hl/wk,失败打印 failed/ DONEscore·penalty·final·总耗时)/ FAILrunReview catch 兜底,任何阶段抛错都会有 [pipe:xxx] FAIL <msg> [耗时ms],不会再静默卡 status=failed
  • 实测 cobol 第 5 次评审:ANALYZE 24stryBuild 跑 python 构建超时)、TEST 10spython -m pytest 33/42 pass、coverage null)、DIM 每维度 4.6~24.7s、全程 114s

整体评价合成(方案A,2026-08-19

  • synthesizeOverallreview.service.ts):校准+硬规则之后,+1 次 LLM 调用(约 15-30s),把 overview+维度得分评语+确定性证据合成点评式整体评价,写入 ai_report.overallJSONhighlights[{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 用例)

已知坑

  1. Chrome 崩溃导致服务器卡死(已缓解) — tryBrowse 有整体 45s 看门狗(BROWSE_WATCHDOG_MS),挂住会强制 kill Chrome 并降级"跳过浏览器测试",但仍建议留意 browse 期间系统资源占用
  2. 多人提交排队 — MAX_CONCURRENT=3,第 4 个 entry 起入队列排队等空位(queue[] + processQueue
  3. AI 从 docs 编造代码问题(已缓解) — fileBlock 中文档文件已标注 (文档/说明文件,非代码),但 AI 仍可能把标准模板内容误当代码问题,若再出现可在 prompt 强化
  4. tryBrowse SSRF 风险(已缓解) — service_url 保存时校验 + 评审期 revalidateHost 二次 DNS 解析(重绑定拦截,仅 config.ssrfDnsCheck 开启时生效),仍建议沙箱
  5. review.service.ts 1060 行 — 单文件过大,改动前必须理解整个 pipeline
  6. difficulty 字段已移除 — 不要搜或引用 difficulty(前端已改为"赛道"下拉)
  7. 本地 file:// 仓库 URL 格式 — 必须用 file://D:/...(两斜杠);file:///D:/...(三斜杠)会被 path.resolve 解析成 D:\D:\... 导致白名单拒绝(2026-08-07 实测)
  8. 测试数据会污染"真实数据"判断 — 库里大量 选手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,纯函数级,不调 AI20 用例)
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,封顶选题 cap100/105/110)。合格线≥60 且 功能完整性≥15(地板线)。

当前维度 guideline 只覆盖标准维度名(如"场景价值与合理性"),赛道一变体名(如"开发范式与架构设计")需扩展匹配关键词。L2考核 7 维标准自带分档锚点与交叉验证规则,content 优先不触发内置兜底。