- 前端存在性硬规则:detectFrontend 源码级判定,web形态无真实前端/不可访问 → 实现完整度封顶50%(CLI/插件豁免) - verify 多态:build_status 支持 done/failed/done_no_ui,前端按钮新增「无前端」 - 人工启动方案:评审环境自动启动失败 → awaiting_browse 暂停,评委手动启动后填 service_url 走 /verify-browse 恢复(SSRF 信任评委确认地址) - Gitea 稳定性:clone 自动重试×3(525抖动)+ 每队独立 token 回退(全局账号失效兜底) - A 阶段报告备份:feature_inventory.__stageA_report 防 B 阶段维度重复累积/A维度丢失 - tryTest 依赖预装:Python 项目自动 pip install(测试不再因缺依赖误判失败) - 运行验证压缩静态维度:前端缺失+测试失败 → 静态纸面维度封顶50%(防原型拿高分) - 文档-实现三级校验:claim-consistency 注入运行时证据(构建/测试/前端),识别「代码有但跑不起来」的虚报(逸飞冲天 61%→25%) - 赛事文档:中期20%+最终80%(AI=人工总分)、评审表/评审规则/赛事说明/07设计同步 - 清空 review_snapshots 测试污染数据,保留当前正式分(零号158/逸飞冲天126) - 测试 446 全通过(后端435+前端11)
275 lines
23 KiB
Markdown
275 lines
23 KiB
Markdown
# 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 # 本文件
|
||
```
|
||
|
||
## 启动方式
|
||
|
||
```bash
|
||
# 后端(端口 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% 文件清单(接口:`extractSignatures` + `renderInventoryText`,无 coreFiles 精读包——精读由 `filterFilesForDim` 维度过滤承担,2026-08-27 文档对齐) |
|
||
| `code-signals.ts` | 代码信号检测器:TODO 密度/空壳率/硬编码返回/死导入——确定性线索注入实现类维度 prompt,定位为线索而非判据 |
|
||
| `ai-log-audit.ts` | AI 日志确定性审计:占位率 / git 一致率 / 覆盖广度;占位>50%→维度≤30%、git一致<30%→≤20% 封顶 |
|
||
| `measurement-audit.ts` | 测量协议检查(P2,2026-08-27):确定性检查 `data/measurement/` 齐备性(baseline/README/timing-log),注入效果类维度 prompt |
|
||
| `accept-runner.ts` | 验收命令执行器(P2,2026-08-27):解析 README `## 验收` 命令块并真实执行,注入实现类维度;测试环境 dry-run 不实跑 |
|
||
| `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.ts` runSubAgent 严格规则):每个维度独立评分,其他维度的判定(如 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_json` status=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.ts` `runBenchmark(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 添加)
|
||
- `feature_inventory TEXT DEFAULT ''`(2026-08-27,migration 添加):全量功能发现轮(Map-Reduce)结果 JSON 数组,供详情页展示与跨快照复用;重评时覆盖
|
||
- `ai_report.dimensions[n].checks`:实现类维度子 Agent 返回的定向追踪核对表(feature/entry/depth/evidence/reason),由 `parseDimResponse` 解析透传(2026-08-27 修正),前端维度表展示,缺失回退现状不崩
|
||
- `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.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-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**(`runReview` catch 兜底,任何阶段抛错都会有 `[pipe:xxx] FAIL <msg> [耗时ms]`,不会再静默卡 status=failed)
|
||
- 实测 cobol 第 5 次评审:ANALYZE 24s(tryBuild 跑 python 构建超时)、TEST 10s(`python -m pytest` 33/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 用例)
|
||
|
||
## 已知坑
|
||
|
||
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/本地副本)而非条目名
|
||
|
||
## 测试
|
||
|
||
```bash
|
||
# 后端单元测试(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 测试
|
||
|
||
## vitest 超时陷阱(2026-08-27 实测)
|
||
|
||
- **vitest 默认测试超时 5000ms**,而触发完整评审(`/start` → A 阶段)的用例因功能发现轮(Map-Reduce,mock 环境 12 块串行 ×400ms)拖到 ~5s,**会撞默认 5s 超时**。凡 `waitA_done`/等待 A 阶段完成的用例必须显式加超时(`}, 30000)` 或更大),否则偶发 `Test timed out in 5000ms`
|
||
- 现象:单跑通过、全量偶发失败,报错是 vitest 超时而非断言失败
|
||
|
||
## 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)
|
||
```
|
||
|
||
## 数据库操作
|
||
|
||
```bash
|
||
# 手动查 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())"
|
||
```
|
||
|
||
## 评审标准格式
|
||
|
||
```markdown
|
||
## 维度名(满分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 优先不触发内置兜底。
|