Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions examples/skills_code_review_agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# All variables are OPTIONAL — the example runs fully offline by default
# (--dry-run / --model-mode fake needs no key at all).
#
# NOTE: this file is NOT loaded automatically (no dotenv dependency). Export
# the variables into your shell yourself, e.g.:
# set -a; source .env; set +a

# Only used with --model-mode real (LLM summary stage):
TRPC_AGENT_API_KEY=your_api_key
TRPC_AGENT_BASE_URL=https://api.openai.com/v1
TRPC_AGENT_MODEL_NAME=gpt-4o-mini

# Only used with --sandbox cube (Cube/E2B workspace runtime):
E2B_API_KEY=your_e2b_key
15 changes: 15 additions & 0 deletions examples/skills_code_review_agent/DESIGN.zh_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# 方案设计说明

**Skill 设计**:code-review Skill 以 SKILL.md 声明用法,规则文档与执行脚本分离;diff 解析器、规则引擎与密钥正则表统一放在 scripts/lib(纯标准库),沙箱内直接执行,宿主经 importlib 加载同一份代码,检测与脱敏单源不漂移。规则覆盖安全风险、异步错误、资源泄漏、测试缺失、敏感信息泄漏与数据库生命周期六类。

**沙箱隔离**:生产默认 Container 运行时,亦支持 Cube/E2B;本地运行时仅作开发回退,并重写环境构造逻辑,仅放行白名单变量。每次执行受超时与输出字节上限约束;失败、超时、异常均记录为 sandbox_run 行并触发宿主内规则回退,评审任务绝不中断。

**Filter 策略**:沙箱执行前经 BaseFilter 链决策:高风险脚本内容、非白名单命令、禁止路径、网络访问、超预算运行分别产生 deny 或 needs_human_review;被拦截的执行不会进入沙箱,原因同时写入报告与数据库。

**监控字段**:总耗时、沙箱耗时与次数、工具调用数、拦截次数与决策分布、finding 数、severity 分布、异常类型分布,随报告入库可查,并辅以 tracer span。

**数据库 schema**:cr_review_task、cr_sandbox_run、cr_filter_event、cr_finding、cr_report 五表,经 ReviewStore 抽象接口访问;SQLite 为默认实现,更换连接 URL 即切换 MySQL/PostgreSQL。

**去重降噪**:同文件同行同类问题仅报一条,保留最高严重级别并合并命中的规则号;置信度低于阈值的启发式结果进入人工复核桶,不混入高置信 findings。

**安全边界**:证据在沙箱内先行脱敏,宿主对报告与全部入库字段递归二次脱敏;diff 摘要不含代码内容,明文密钥不落任何持久化面。
84 changes: 84 additions & 0 deletions examples/skills_code_review_agent/README.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Automatic Code-Review Agent (Skills + Sandbox + Database)

> 中文文档: [README.md](README.md) · Design note (Chinese): [DESIGN.zh_CN.md](DESIGN.zh_CN.md)

A verifiable automatic code-review (CR) agent prototype built on the
tRPC-Agent SDK: it reads a git diff / PR patch / local changes, loads rules
and scripts through the **code-review Skill**, executes static checks in a
**sandbox** after the **Filter policy** approves, persists structured
findings, block records, sandbox logs and monitoring metrics to a **SQL
database**, and renders `review_report.json` + `review_report.md`.

## Highlights

- **CR Skill** (`skills/code-review/`): SKILL.md + rule docs + sandbox
scripts covering six categories — security risk, async errors, resource
leaks, missing tests, secret leakage, DB transaction/connection lifecycle
(the issue requires four).
- **One implementation, both sides**: the diff parser, rule engine and secret
pattern table live in `skills/code-review/scripts/lib/` (stdlib-only). The
sandbox executes it directly; the host loads the very same files via
importlib — detection and redaction can never drift apart.
- **Sandbox execution**: `--sandbox` defaults to **auto** — **container**
(Docker, native isolation) when Docker is available, otherwise a clearly
logged fallback to **local**; **cube** (Cube/E2B) is supported. **local**
is a development fallback only (tests/dry-run pin it) and is hardened with
`EnvWhitelistLocalProgramRunner` so host secrets never enter the sandbox.
- **Safety boundary**: per-run timeout, stdout/stderr size cap, env
whitelist, in-sandbox evidence redaction; timeouts/failures/exceptions are
recorded as data (`cr_sandbox_run` rows) and the pipeline falls back to the
in-process rule engine — **a sandbox crash never kills a review**.
- **Filter governance**: `SandboxGovernanceFilter` (real SDK `BaseFilter` +
`run_filters` chain) pre-blocks risky scripts, non-whitelisted commands,
forbidden paths, network access and over-budget runs. On
`deny`/`needs_human_review` the terminal handler is never invoked; reasons
land in the report and `cr_filter_event`.
- **Dedup & noise control**: one report per `(file, line, category)` (highest
severity wins, merged rule ids preserved); findings with confidence < 0.7
go to `needs_human_review` and never mix into high-confidence findings.
- **Storage**: five tables behind the `ReviewStore` ABC; switching to
MySQL/PostgreSQL is just a different SQLAlchemy URL.
- **Monitoring**: total/sandbox duration, tool calls, filter decisions,
finding counts, severity distribution, exception types — all DB-queryable,
plus OpenTelemetry tracer spans per phase.
- **Offline by default**: `--dry-run` (fake model + local sandbox) needs no
API key and finishes in seconds (acceptance limit: 2 minutes).

## Usage

```bash
cd examples/skills_code_review_agent

python run_agent.py review --fixture security_issue --dry-run # no API key
python run_agent.py review --diff-file my.patch
python run_agent.py review --repo-path /path/to/repo
python run_agent.py review --files a.py b.py

python run_agent.py show --task-id <ID> # full DB bundle for one task
python run_agent.py list
python run_agent.py init-db # idempotent schema init/migration

# production shape: container sandbox + real model. Export the variables
# listed in .env.example first — .env is NOT loaded automatically:
# set -a; source .env; set +a
python run_agent.py review --diff-file my.patch --sandbox container --model-mode real
```

## Tests

```bash
python -m pytest examples/skills_code_review_agent/tests -q # 71 passed, all offline
```

## Database schema

`cr_review_task` (task + status machine), `cr_sandbox_run` (every sandbox
attempt incl. blocked/failed, redacted output excerpts), `cr_filter_event`
(every governance decision with reasons), `cr_finding` (structured findings,
bucketed into `finding` / `needs_human_review`), `cr_report` (final document
+ metrics). Everything is queryable by task id via
`ReviewStore.get_task_bundle()` / `run_agent.py show`.

See README.md (Chinese) for the acceptance-criteria mapping table and the
architecture diagram, and DESIGN.zh_CN.md for the 300–500-character design
note required by the issue.
140 changes: 140 additions & 0 deletions examples/skills_code_review_agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# 自动代码评审 Agent(Skills + 沙箱 + 数据库)

> English version: [README.en.md](README.en.md) · 方案设计说明: [DESIGN.zh_CN.md](DESIGN.zh_CN.md)

本示例基于 tRPC-Agent SDK 构建一个可验证的自动代码评审(CR)Agent 原型:
读取 git diff / PR patch / 本地变更,通过 **code-review Skill** 加载规则与脚本,
经 **Filter 策略**放行后在**沙箱**中执行静态检查,把结构化 findings、拦截记录、
沙箱日志和监控指标全部落入 **SQL 数据库**,最终输出 `review_report.json` 与
`review_report.md`。

## 关键特性

- **CR Skill**(`skills/code-review/`):SKILL.md + 6 类规则文档 + 沙箱脚本。
规则覆盖安全风险、异步错误、资源泄漏、测试缺失、敏感信息泄漏、数据库事务/连接
生命周期(超出题目要求的 4 类)。
- **单一实现,双端复用**:diff 解析器、规则引擎、密钥正则表全部放在
`skills/code-review/scripts/lib/`(纯标准库)。沙箱内直接执行,宿主端通过
importlib 加载同一份代码 —— 检测与脱敏永不漂移。
- **沙箱执行**:`--sandbox` 默认 **auto**——检测到 Docker 即用 **container**
(原生隔离,生产方案),否则记录告警并回退 **local**;另支持 **cube**
(Cube/E2B 云沙箱)。**local** 仅作为开发 fallback(测试与 `--dry-run` 显式
使用),并通过 `EnvWhitelistLocalProgramRunner` 强制环境变量白名单,
宿主密钥不进沙箱。
- **安全边界**:每次运行有超时、stdout/stderr 大小上限、环境白名单;证据文本在
沙箱内即完成脱敏;沙箱超时/失败/异常一律记录为数据(`cr_sandbox_run` 行),
自动回退到宿主内规则引擎,**评审任务永不崩溃**。
- **Filter 治理**:`SandboxGovernanceFilter`(真实 `BaseFilter` + `run_filters`
链)对高风险脚本、非白名单命令、禁止路径、网络访问、超预算执行做前置拦截;
`deny` / `needs_human_review` 时终端 handler 不会被调用,拦截原因写入报告与
`cr_filter_event` 表。
- **去重与降噪**:同一 `(file, line, category)` 只报一次(保最高严重级别,合并
规则 ID);置信度 < 0.7 的启发式结果进入 `needs_human_review`,绝不混入高置信
findings。
- **数据库存储**:5 张表(task / sandbox_run / filter_event / finding / report),
接口为 `ReviewStore` ABC —— 换 MySQL/PostgreSQL 只需换 SQLAlchemy URL。
- **监控审计**:总耗时、沙箱耗时、工具调用次数、拦截次数与分布、finding 数量、
severity 分布、异常类型分布,全部入库可查,另附 OpenTelemetry tracer span。
- **离线可测**:`--dry-run`(fake model + local 沙箱)不需要任何 API Key,
完整链路秒级完成(验收要求 ≤ 2 分钟)。

## 架构与数据流

```
--diff-file | --repo-path | --files | --fixture
│ inputs.py → RawChangeSet(unified diff + 可选全文件内容)
ReviewPipeline.run() [span code_review.total]
1 建任务 cr_review_task(status=running)
2 宿主解析 diff → 摘要(无内容, 安全入库) [span code_review.parse]
3 Filter 治理门 (run_filters)
allow → 4 deny/needs_human_review → 记录拦截, 走宿主回退
4 沙箱执行 run_checks.py [span code_review.sandbox]
stage skill → 注入 diff.json → 白名单环境运行(超时/输出上限)
失败/超时 → cr_sandbox_run 记录 + 宿主回退, 任务继续
5 后处理: 去重 → 降噪分桶 → 二次脱敏 [span code_review.postprocess]
6 LLM 摘要 (fake|real|off) [span code_review.llm]
7 落库 findings/filter_events/report + 渲染 json/md 报告
```

## 关键文件

| 路径 | 说明 |
|---|---|
| `run_agent.py` | CLI 入口(review / show / list / init-db) |
| `skills/code-review/SKILL.md` | Skill 使用说明 |
| `skills/code-review/rules/*.md` | 6 类规则文档 |
| `skills/code-review/scripts/` | 沙箱入口脚本 + 纯标准库规则引擎 `lib/` |
| `codereview/pipeline.py` | 评审编排(任务生命周期、回退、落库) |
| `codereview/governance.py` | Filter 策略(BaseFilter + run_filters) |
| `codereview/sandbox.py` | 沙箱运行时工厂 + 环境白名单 + SandboxExecutor |
| `codereview/findings.py` | Finding 模型、去重、降噪 |
| `codereview/redaction.py` | 宿主端脱敏(复用 Skill 的正则表) |
| `codereview/store/` | ReviewStore ABC + SqlReviewStore + schema + init 脚本 |
| `fixtures/*.diff` | 8 条可运行测试样例 |
| `sample_output/` | 提交的示例报告(json + md) |
| `tests/` | 71 个 pytest 用例(全部离线) |

## 运行方式

```bash
cd examples/skills_code_review_agent

# 1. 离线体检(无需任何 API Key)
python run_agent.py review --fixture security_issue --dry-run

# 2. 评审一个 diff / 一个 git 工作区 / 一组文件
python run_agent.py review --diff-file my.patch
python run_agent.py review --repo-path /path/to/repo
python run_agent.py review --files a.py b.py

# 3. 查询数据库(按 task id 取回全部记录)
python run_agent.py show --task-id <ID>
python run_agent.py list
python run_agent.py init-db # 初始化/迁移 schema(幂等)

# 4. 生产形态:容器沙箱 + 真实模型
python run_agent.py review --diff-file my.patch --sandbox container --model-mode real
```

- `--sandbox` 默认 **auto**:检测到 Docker 时使用 container
(`python:3.12-slim` 镜像),否则告警并回退 local;`--sandbox cube` 使用
Cube/E2B(需要 E2B Key)。**local 只是开发 fallback,不是生产方案**——
生产请使用 container。
- `--model-mode real` 需要环境变量 `TRPC_AGENT_API_KEY` / `TRPC_AGENT_BASE_URL`
/ `TRPC_AGENT_MODEL_NAME`(键名参考 `.env.example`;本示例**不会**自动加载
`.env`,请先 `set -a; source .env; set +a` 或逐个 `export`)。
- `--inject-sandbox-failure` 演示沙箱失败被安全吸收(任务状态
`completed_with_errors`,报告照常生成)。

## 运行测试

```bash
python -m pytest examples/skills_code_review_agent/tests -q # 71 passed
```

## 数据库 Schema(SQLite 默认,URL 可换 MySQL/PostgreSQL)

| 表 | 内容 | 关键字段 |
|---|---|---|
| `cr_review_task` | 任务与状态机 | id, status, input_type/ref, diff_summary(JSON), config(JSON), error_* |
| `cr_sandbox_run` | 每次沙箱尝试(含被拦截/失败) | task_id, status(ok/failed/timeout/blocked/error), exit_code, timed_out, filter_action, stdout/stderr 摘录(已脱敏+截断), error_type |
| `cr_filter_event` | 每个治理决策 | task_id, stage, target, action, rule, reasons(JSON) |
| `cr_finding` | 结构化 finding | task_id, severity, category, file, line, title, evidence(已脱敏), recommendation, confidence, source, rule_id, bucket, dedup_key |
| `cr_report` | 最终报告 + 监控摘要 | task_id(unique), summary, severity_stats, filter_summary, sandbox_summary, metrics, report(完整 JSON) |

初始化/迁移:`python run_agent.py init-db`(`SqlStorage.create_sql_engine` 执行
`create_all` + 前向列迁移,幂等可重复执行)。

## 验收标准对照

| # | 验收标准 | 实现/验证 |
|---|---|---|
| 1 | 8 条 diff 样本全部可运行并生成报告 | `fixtures/` 8 条;`tests/test_fixtures_e2e.py` 参数化全跑 |
| 2 | 高危检出 ≥80%,误报 ≤15% | `tests/test_rules.py`:19 条带标注正样本全检出(公开集召回 100%);10 条干净样本高置信 FP 为 0。隐藏集指标以此带标注语料为代理 |
| 3 | DB 完整记录并按 task id 查询 | 5 张表 + `get_task_bundle(task_id)`;`tests/test_store.py` |
| 4 | 沙箱超时/输出上限;失败不崩溃 | `tests/test_sandbox_safety.py`(超时、截断、强制失败、异常包裹) |
| 5 | 脱敏检出 ≥95%,报告与 DB 无明文 | `tests/test_redaction.py`(48 条样本 ≥95%);e2e 断言报告与 sqlite 文件字节中无种子明文 |
| 6 | dry-run ≤2 分钟 | `test_dry_run_speed_and_no_api_key`(实测 < 5 秒,且删除全部 Key 环境变量) |
| 7 | 高风险脚本先经 Filter,deny/needs_human_review 不进沙箱 | `tests/test_governance_filter.py`(handler 哨兵证明未执行) |
| 8 | 报告含 7 个规定部分 | `tests/test_cli_and_report.py::test_report_sections_complete` |
11 changes: 11 additions & 0 deletions examples/skills_code_review_agent/codereview/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
#
# Copyright (C) 2026 Tencent. All rights reserved.
#
# tRPC-Agent-Python is licensed under Apache-2.0.
"""Host-side package of the skills_code_review_agent example.

Orchestrates the automatic code-review pipeline on top of trpc_agent_sdk:
skill-driven rules, governed sandbox execution (Filter), SQL persistence
(storage), telemetry spans and an optional (fake/real) LLM summary stage.
"""
110 changes: 110 additions & 0 deletions examples/skills_code_review_agent/codereview/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
#
# Copyright (C) 2026 Tencent. All rights reserved.
#
# tRPC-Agent-Python is licensed under Apache-2.0.
"""Configuration dataclasses for the code-review pipeline."""

from __future__ import annotations

import logging
import os
import shutil
from dataclasses import asdict
from dataclasses import dataclass
from dataclasses import field
from typing import FrozenSet
from typing import Tuple

EXAMPLE_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SKILLS_ROOT = os.path.join(EXAMPLE_ROOT, "skills")
SKILL_NAME = "code-review"
FIXTURES_DIR = os.path.join(EXAMPLE_ROOT, "fixtures")

DEFAULT_DB_FILENAME = "review.db"

#: Environment variables the sandbox is allowed to inherit from the host.
DEFAULT_ENV_WHITELIST: FrozenSet[str] = frozenset({
"PATH",
"LANG",
"LC_ALL",
"HOME",
"TMPDIR",
"PYTHONIOENCODING",
})


@dataclass
class SandboxConfig:
"""Sandbox execution limits and runtime selection.

``runtime_kind='container'`` is the default (Docker image, native
isolation); ``'local'`` is the development fallback used by tests and
dry-run on hosts without Docker, hardened with an env whitelist — the CLI
resolves ``--sandbox auto`` via :func:`resolve_sandbox_kind`. ``'cube'``
targets Cube/E2B cloud sandboxes.
"""

runtime_kind: str = "container" # container | local | cube
timeout_sec: float = 30.0
max_output_bytes: int = 64_000
env_whitelist: FrozenSet[str] = DEFAULT_ENV_WHITELIST
container_image: str = "python:3.12-slim"
work_root: str = ""
force_fail: bool = False # test injection: sandbox check raises deterministically


@dataclass
class PolicyConfig:
"""Filter governance policy for sandbox runs."""

allowed_cmds: Tuple[str, ...] = ("python3", "python")
forbidden_paths: Tuple[str, ...] = ("/etc", "/root", "/var/run/docker.sock", ".ssh", "..", "~")
allow_network: bool = False
max_sandbox_runs: int = 8
max_total_sandbox_seconds: float = 90.0


@dataclass
class NoiseConfig:
"""Dedup / noise-control thresholds."""

min_confidence: float = 0.7 # findings below go to needs_human_review


@dataclass
class ReviewConfig:
"""Top-level pipeline configuration."""

db_url: str = ""
out_dir: str = "out"
model_mode: str = "fake" # fake | real | off
model_name: str = "fake-review-v1"
sandbox: SandboxConfig = field(default_factory=SandboxConfig)
policy: PolicyConfig = field(default_factory=PolicyConfig)
noise: NoiseConfig = field(default_factory=NoiseConfig)

def to_dict(self) -> dict:
data = asdict(self)
data["sandbox"]["env_whitelist"] = sorted(self.sandbox.env_whitelist)
return data


def resolve_sandbox_kind(kind: str = "auto") -> str:
"""Resolve the ``--sandbox`` choice: ``'auto'`` → ``'container'`` when
Docker is available, otherwise fall back — clearly logged — to the LOCAL
dev runtime. Explicit choices pass through unchanged."""
if kind != "auto":
return kind
if shutil.which("docker"):
return "container"
logging.getLogger(__name__).warning(
"sandbox 'auto': docker not found on PATH — falling back to the local "
"dev runtime (env-whitelisted, dev fallback only). Install Docker or "
"pass --sandbox container for production isolation.")
return "local"


def default_db_url(base_dir: str) -> str:
"""SQLite (aiosqlite) URL under ``base_dir`` — the swappable default backend."""
return f"sqlite+aiosqlite:///{os.path.join(os.path.abspath(base_dir), DEFAULT_DB_FILENAME)}"
Loading
Loading