Skip to content

feat: 基于 Skills + 沙箱 + 数据库的自动代码评审 Agent (#92)#200

Closed
wzxsdf wants to merge 3 commits into
trpc-group:mainfrom
wzxsdf:feat/code-review-agent-92
Closed

feat: 基于 Skills + 沙箱 + 数据库的自动代码评审 Agent (#92)#200
wzxsdf wants to merge 3 commits into
trpc-group:mainfrom
wzxsdf:feat/code-review-agent-92

Conversation

@wzxsdf

@wzxsdf wzxsdf commented Jul 16, 2026

Copy link
Copy Markdown

概述

实现一个端到端、可验证的自动代码评审 Agent 原型。输入 git diff / 文件列表 / git 工作区变更,Agent 通过 code-review Skill 加载规则与脚本,经 Filter 治理后进入沙箱执行必要检查,把发现的问题按严重级别 / 文件 / 行号 / 证据 / 修复建议结构化输出,并将审查任务、拦截记录、监控摘要与结果写入 SQLite。

核心设计取舍:确定性规则保检出下限 + LLM 提准确率上限。规则层(正则 + AST/taint + 外部扫描器)保证 dry-run 可跑、可测;LLM 层做降噪(剔除误报)与补召回(补语义问题),可降级、可关闭。

架构

输入(diff/文件列表/git工作区)
 → 解析(解析期脱敏,规则脚本零明文)
 → 规则召回(逐行正则+LITERAL_ARGUMENT防误报 / hunk跨行 / Python AST taint / semgrep·bandit)
 → LLM增强(降噪二分类剔除误报 + 低置信补召回;无Key走预录制)
 → 四元组去重(file,line,category,rule_id) + 三桶路由(findings/warnings/needs_review)
 → Filter前置治理(CommandPolicy + SDK BaseFilter;deny/review不进沙箱)
 → 沙箱执行(Fake默认 / Local(dev) / Container / Cube;bounded_int单向收紧)
 → 脱敏闭环(检/脱共享正则 + Shannon熵;解析/finding/stdout/落库/LLM输入)
 → SQLite七表落库(schema_migrations真迁移)
 → 报告(JSON / Markdown八段式 / SARIF v2.1.0)

关键设计 / 创新点

  1. LLM 规则增强层:对规则召回的 finding 做结构化二分类(true/false positive)剔除误报 + 对低置信语义项补召回;双模式(真实 LLM / 预录制 fixture),无 API Key 也能跑通全链路。
  2. 隐藏样本量化评测:公开 8 + 隐藏 12 fixture,实例级 P/R/F1(非集合级,避免夸大召回)。
  3. 四元组去重(file, line, category, rule_id) 保留同类不同子问题的证据(优于三元组)。
  4. 检 / 脱同步:检测规则与脱敏共享同一正则集(SECRET_KV_KEYS 单一真相源),杜绝“检出未脱 / 脱了未检”。
  5. 沙箱永不抛:超时 / 失败捕获为 partial result,不中断静态评审。

验收对照(issue #92 八条)

# 验收 状态 说明
1 公开 8 样本可运行出报告 evaluate.py 跑全部 fixture,生成 JSON/MD/SARIF
2 隐藏样本检出≥80% 误报≤15% ✅* 召回 0.895 / 脱敏 0.983 / findings 桶误报 0.0(见实测)
3 DB 完整记录 + 按 task 查 七表 + get_task_details 聚合
4 沙箱超时/输出限制/失败不崩 timeout + bounded output + 异常捕获
5 脱敏≥95% 无明文 0.983,报告/DB/LLM 输入均脱敏
6 dry-run ≤2min 无 Key 走预录制
7 Filter 前置 deny 不进沙箱 BaseFilter._before 短路 + pipeline allow 才进沙箱
8 报告八段式 + 可执行修复 findings/warnings/needs_review/filter/sandbox/monitoring/conclusion + recommendation

使用方法

cd examples/skills_code_review_agent

# 默认 fake 沙箱(无 Docker 也能跑)
PYTHONUTF8=1 python run_review.py --diff-file fixtures/diffs/security.diff

# 真实 LLM 模式(需配置 .env 的 TRPC_AGENT_API_KEY/BASE_URL/MODEL_NAME)
PYTHONUTF8=1 python evaluate.py --llm

测试

  • pytest tests/ -v:187 项全通过
  • python evaluate.py:量化评测
  • yapf + flake8(max-line-length=120)全绿

文件结构

  • examples/skills_code_review_agent/:agent(pipeline/rule_engine/ast_analyzer/redaction/dedup/llm_layer/report/telemetry)/ storage(七表+迁移)/ sandbox(四后端)/ filters / fixtures(8公开+12隐藏)/ tests / CLI + evaluate + README
  • skills/code-review/:SKILL.md + 6 类规则 + 沙箱脚本

Closes #92

wzxsdf added 3 commits July 13, 2026 21:28
- replay harness: 同一组轨迹驱动 InMemory/SQLite/Redis,比较事件/state/memory/summary
- 归一化(占位符,保留字段存在性)+ JSONPath 精确 allowed_diff + 覆盖率治理
- summary: SDK 确定性模型 + 三分比较 + loss/overwrite/affiliation 检测
- 检出验证: 快照层注入 + 端到端后端注入(改 SQL 行 / Redis key 重读)
- 实测发现 SQLite summary 持久化 drift,标 KNOWN_DRIFT 只报告不改
- 29 tests pass, 1 skipped (Redis 需 TRPC_REPLAY_REDIS_URL)
构建「评测→失败归因→prompt优化→验证集回归→接受决策→审计落盘」可复现闭环:
- 分层失败归因(规则快通道 + 反事实深归因,本地 metric 零成本)
- 过拟合三重检测(显式公式 + 泛化缺口 + 趋势背离)
- 可配置 gate(三态 accept/reject/needs_review)
- sha256 审计 + 三态成本 fail-closed
- fake/trace/online 三模式,fake 无 API key 可跑通(<1s),9 pytest 全过
端到端 CR Agent: 输入解析 → 规则(正则 + AST/taint) → LLM 增强(降噪 + 补召回)
→ 四元组去重 → Filter 前置 → 沙箱(fake/local/container/cube) → 脱敏 → SQLite 七表落库 → JSON/MD/SARIF 报告

- examples/skills_code_review_agent/: 完整 example(agent/storage/sandbox/filters/fixtures/tests + CLI + evaluate)
- skills/code-review/: Skill 包(SKILL.md + 6 类规则 + 沙箱脚本)
- 创新 1 LLM 增强: 降噪(findings 桶误报 0.0) + 补召回(召回 0.895)
- 量化评测: 公开 8 + 隐藏 12 fixture, 实例级 P/R/F1
- .gitignore: 加 .env 防御(trpc-group#90 教训)

Closes trpc-group#92
@wzxsdf wzxsdf closed this Jul 16, 2026
@CongkeChen

Copy link
Copy Markdown
Contributor

AI Code Review

现在我已经获取了足够的信息,让我来撰写评审意见。

发现的问题

🚨 Critical

  • examples/skills_code_review_agent/agent/llm_layer.py:29-416:真模式 LLM 降噪判定值不一致,导致误报无法被剔除

    • 提示词 DENOISING_PROMPT 要求 LLM 返回 "verdict":"TP|FP",但 _apply_verdicts 只在 verdict == "false_positive" 时剔除(第 416 行)。真 LLM 模式返回 "FP" 时不会被识别,所有误报都会原样保留并提升置信度,降噪逻辑形同虚设。dry_run 预录制用的是 true_positive/false_positive 才巧合可用,但真实 LLM 调用路径下功能失效。建议统一判定值:要么提示词改为要求 false_positive,要么 _apply_verdicts 同时识别 "FP"
    ...
    if verdict.get("verdict") == "false_positive":   # 真 LLM 返回的是 "FP"
        continue
    ...
  • examples/skills_code_review_agent/sandbox/cube.py:108:Cube 沙箱超时分支永远捕获不到,超时被误判为 failed

    • subprocess.run(..., timeout=...) 超时时抛出的是 subprocess.TimeoutExpired(属 SubprocessError,不是 TimeoutError 的子类),会被第 124 行的通用 except Exception 接住,返回 status="failed"error_type="TimeoutExpired",而非预期的 status="timeout"LocalSandbox 正确捕获了 subprocess.TimeoutExpired,Cube 应一致。对应测试 test_cube_timeout_with_mock 仅断言 result is not None,掩盖了该缺陷。建议 except subprocess.TimeoutExpired as e: 放在通用 except Exception 之前。

⚠️ Warning

  • examples/skills_code_review_agent/sandbox/container.py:118-129:Container 沙箱同样把 subprocess.TimeoutExpired 之外的真实超时归到通用分支,且 client.exec_runTimeoutError 的假设依赖 SDK 行为

    • 虽然此处通过 CommandArgs(timeout=...) 交给 SDK,但第 118 行仅捕获 TimeoutError;若 SDK 抛 subprocess.TimeoutExpired 或其他异常,会被通用分支转为 failed 而非 timeout。建议显式捕获 subprocess.TimeoutExpired 并与 TimeoutError 合并处理,避免超时状态被误报为失败影响 _conclusioncompleted_with_warnings 判定。
  • examples/skills_code_review_agent/agent/pipeline.py:128-143:沙箱脚本在空临时目录执行,static_review.py/diff_summary.py 实际不存在

    • SKILL_SCRIPTS = ["static_review", "diff_summary"],构造命令 python static_review,被 Filter allow 后在全新 TemporaryDirectoryruntime.run(script="static_review.py", ...)。脚本文件从未复制进 workspace,真后端(local/container/cube)下必然 FileNotFoundError,除 fake 外的任何后端都会返回 failed run。当前仅 fake 路径可跑通,真后端路径存在隐含缺陷。建议在执行前将 skills 目录下的脚本复制到 workspace,或直接以绝对路径调用。
  • examples/skills_code_review_agent/storage/store.py:102-108saveUPDATE review_tasks 更新状态,但 pipeline 未先 start_task 插入任务行

    • pipeline.run_review 直接 ReviewStore().save(report)(第 193 行),未调用 start_task;schema 中 review_tasks.task_id 是主键,saveUPDATEINSERT。若任务行不存在,UPDATE 影响 0 行,review_tasks 表无记录,而子表(input_diffs/findings 等)通过外键 REFERENCES review_tasks(task_id) 写入——由于 SQLite 默认不开启 PRAGMA foreign_keys=ON,写入会“成功”但产生孤儿数据,get_task_details 会因查不到 review_tasks 直接返回 None。建议 save 内对 review_tasksINSERT OR REPLACE,或在 pipeline 中先调用 start_task
  • examples/skills_code_review_agent/filters/policy.py:79:沙箱调用预算判断使用 > 而非 >=,且默认值为 12 时第 13 次才拦截

    • ctx.get("call_index", 0) > self.p["max_sandbox_runs"]:当 call_index 从 0 开始递增时,索引 12(第 13 次调用)才触发 deny,实际允许了 13 次而非配置的 12 次。若 call_index 表达“已调用次数”则应 >=。属边界条件 off-by-one,建议改为 >= 并明确 call_index 语义。
  • examples/skills_code_review_agent/filters/policy.py:55-72:高危命令子串匹配过于宽松,curl/&&/; 会误拦合法脚本路径

    • if hc in command 用纯子串匹配,命令字符串 python static_review.py 中虽不含这些,但任何含 ;(如路径 a;b.py)或 curl 子串的合法输入都会被判 needs_human_review;同时 forbidden_paths".." 也会误匹配路径中的 ..。建议对命令做 token 化后再匹配,或收紧匹配规则,避免误拦与漏拦。
  • examples/skills_code_review_agent/tests/test_storage.py(整体):测试均依赖 start_task 先建任务行,未覆盖 pipeline 实际“未 start_task 直接 save”的真实路径

    • 所有 test_save_* fixture 都先 store.start_task(...)save,掩盖了前述 pipeline 未建任务行导致 get_task_details 返回 None / 孤儿子表数据的问题。建议补一个“不调用 start_task 直接 save 后 get_task_details”的测试覆盖真实调用路径。

💡 Suggestion

  • examples/skills_code_review_agent/agent/pipeline.py:201run_review.py 输出路径硬编码 "outputs/"
    • write_reports(report, "outputs/") 使用相对路径,依赖进程当前工作目录;CLI 在 _PROJECT_ROOT / "outputs" 查找却写入 cwd 下的 outputs/,两者可能不一致。建议统一基于 _PROJECT_ROOT 或可配置输出目录。

总结

存在两个必须修复的 Critical:LLM 真模式降噪判定值与提示词不一致导致误报剔除失效、Cube 沙箱超时分支捕获错误异常类型导致超时被误判为失败(并被宽松测试掩盖)。另有沙箱脚本未落盘、存储 savestart_task 断链、Filter 预算 off-by-one 与子串误匹配等稳定性/兼容性隐患需修复。建议优先修复 Critical 后再合入。

测试建议

  • 补充真模式 LLM 降噪测试:mock LLM 返回 verdict="FP",断言对应 finding 被剔除。
  • 补充 Cube/Container 沙箱 subprocess.TimeoutExpired 场景测试,断言 status=="timeout"(而非仅 is not None)。
  • 补充“pipeline 未 start_task 直接 saveget_task_details”的集成测试,验证任务行与子表一致性。


if verdict:
# 如果被标记为 false_positive,跳过(剔除误报)
if verdict.get("verdict") == "false_positive":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LLM 真模式降噪判定值与提示词不一致,误报无法剔除

DENOISING_PROMPT 要求 LLM 返回 "verdict":"TP|FP",但 _apply_verdicts 仅在 verdict=="false_positive" 时剔除。真 LLM 返回 "FP" 不会被识别,所有误报原样保留并提升置信度,降噪形同虚设。建议统一判定值,让 _apply_verdicts 同时识别 "FP"。

duration_ms=duration_ms,
)

except Exception as e:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cube 沙箱超时分支永远捕获不到,超时被误判为 failed

subprocess.run 超时抛 subprocess.TimeoutExpired(非 TimeoutError 子类),会被第 124 行通用 except Exception 接住返回 status=failed 而非 timeout。建议将 except subprocess.TimeoutExpired 放在通用 except Exception 之前,与 LocalSandbox 一致。

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@e113610). Learn more about missing BASE report.

Additional details and impacted files
@@            Coverage Diff             @@
##             main        #200   +/-   ##
==========================================
  Coverage        ?   87.55867%           
==========================================
  Files           ?         467           
  Lines           ?       44103           
  Branches        ?           0           
==========================================
  Hits            ?       38616           
  Misses          ?        5487           
  Partials        ?           0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

wzxsdf added a commit to wzxsdf/trpc-agent-python that referenced this pull request Jul 16, 2026
…trpc-group#92)

修复 PR trpc-group#200 reviewer(CongkeChen)发现的 Critical bug:
- DENOISING_PROMPT 要求 LLM 返回 "verdict":"TP|FP"(TP=真问题, FP=误报)
- 原 _apply_verdicts 只在 verdict == "false_positive" 时剔除
- 真 LLM 返回 "FP" 不匹配 "false_positive" → 降噪失效
- 现在兼容多种格式:FP, false_positive, false positive, False Positive, 误报

新增测试:test_real_mode_fp_verdict_compatibility() 验证真 LLM 返回 "FP" 格式时正确剔除误报
wzxsdf added a commit to wzxsdf/trpc-agent-python that referenced this pull request Jul 16, 2026
…c-group#200 review)

- Critical 2: 修复 Cube 沙箱超时异常捕获,优先捕获 subprocess.TimeoutExpired
- 隐患1: 修复 Filter 预算 off-by-one,call_index >= max_sandbox_runs 时 deny
- 隐患2: 修复 Filter 子串误匹配,路径使用边界匹配避免 .environment 命中 .env
- 隐患3: 修复沙箱脚本未落盘,pipeline 在执行前把脚本复制到 workspace
- 隐患4: 修复存储 save 与 start_task 断链,pipeline 在开始时调用 start_task
- 补充严格测试:Cube 超时断言 status==timeout、Filter off-by-one 和边界匹配测试
@wzxsdf

wzxsdf commented Jul 16, 2026

Copy link
Copy Markdown
Author

@CongkeChen 感谢详细精准的 review!两个 Critical 与稳定性隐患均已修复。

另说明:原 PR #200 的 head 分支误含了其他 issue 的提交,已基于 main 重建干净分支 feat/code-review-agent-92-clean(仅含本 issue 改动,基于 main,测试独立通过)。新 PR 将用此分支。

Critical 修复

LLM 降噪 verdict 不一致(llm_layer.py _apply_verdicts):normalize 后兼容 false_positive/FP/false/误报(c547448)。补充了真模式返回 "FP" 的测试——此前只测 false_positive 才漏掉,正是您指出的关键。
Cube 超时异常类型(cube.py):except TimeoutError → except (subprocess.TimeoutExpired, TimeoutError),超时正确返回 status="timeout"(非 failed)(e43b0de)。补严格断言 assert status == "timeout"。
稳定性隐患修复(e43b0de)

Filter 预算 off-by-one:> → >=
Filter 子串误匹配:路径/命令边界精确匹配(.environment 不再命中 .env)
沙箱脚本未落盘:pipeline 执行前 shutil.copy 脚本到 workspace
存储 save/start_task 断链:pipeline 先 start_task 再 save

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

基于 Skills + 沙箱 + 数据库存储构建自动代码评审 Agent

2 participants