Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
14a2d43
feat(examples): add code-review skill scaffold and diff parser
DNKYr Jul 16, 2026
e5dc1e2
feat(examples): add shared secret patterns and host-side redaction
DNKYr Jul 16, 2026
7eda4e1
feat(examples): add code-review checker scripts, rule docs, and diff …
DNKYr Jul 16, 2026
c88112a
feat(examples): add finding model with dedup and confidence gating
DNKYr Jul 16, 2026
0fe2071
fix(examples): add typed signatures to finding helpers
DNKYr Jul 16, 2026
3ebd0f9
feat(examples): add code-review SQL schema and ReviewStore
DNKYr Jul 16, 2026
4d3ef68
fix(examples): tidy store imports and cover all redaction write paths…
DNKYr Jul 16, 2026
73f448c
feat(examples): add governance engine and skill_run tool filter
DNKYr Jul 16, 2026
5797a7d
feat(examples): add sandbox session with env whitelist, timeout and o…
DNKYr Jul 16, 2026
ec6a74e
fix(examples): deterministic sandbox env whitelist and reopen guard
DNKYr Jul 16, 2026
e4c02fd
feat(examples): add fake review model, agent wiring and llm review step
DNKYr Jul 16, 2026
997a5bd
fix(examples): robust fenced-JSON parsing and runner cleanup in llm r…
DNKYr Jul 16, 2026
1e855bb
feat(examples): add review report builder and markdown renderer
DNKYr Jul 16, 2026
4342da9
feat(examples): add end-to-end review pipeline with governance, dedup…
DNKYr Jul 16, 2026
6ec93f4
fix(examples): add belt-and-braces redaction to DB-stored report_json
DNKYr Jul 16, 2026
3a5d1fa
test(examples): add opt-in container runtime e2e test for code review
DNKYr Jul 16, 2026
ab8b8c6
docs(examples): add code review agent README, env template and sample…
DNKYr Jul 16, 2026
5d8bb0b
fix(examples): redact report dict before returning from pipeline
DNKYr Jul 16, 2026
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
7 changes: 7 additions & 0 deletions examples/skills_code_review_agent/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Real-model mode (optional). Leave empty to use --dry-run / fake model.
TRPC_AGENT_API_KEY=
TRPC_AGENT_BASE_URL=
TRPC_AGENT_MODEL_NAME=

# Cube runtime (optional)
CUBE_API_KEY=
135 changes: 135 additions & 0 deletions examples/skills_code_review_agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Skills Code Review Agent

Automated code review agent powered by the tRPC-Agent [Skills](https://git.woa.com/trpc-python/trpc-python-agent/trpc-agent/blob/master/docs/skills/skills_zh_CN.md) framework. It demonstrates end-to-end sandboxed execution of review checkers (security, async/resource leaks, DB lifecycle, missing tests, secret leaks), governance via tool-level Filter, SQL-based persistence of findings + metrics + reports, and host-side redaction. The example runs deterministic checker scripts inside a container/workspace sandbox and optionally enriches results with an LLM pass.

```
Unified diff (file / repo / fixture)
|
v
+-- Parse diff --+
| (parse_diff) |
+----------------+
|
v
+--- Governance Filter ----+
| script allowlist, paths, |
| network deny, budget, |
| risk → needs_human_review|
+--------------------------+
|
v
+---- Sandbox (container / local / cube) ----+
| env -i (PATH+HOME+LANG whitelist) |
| security | async_leak | db_lifecycle |
| tests_missing | secrets |
| timeout: 60s, output cap: 256KB |
+-----------------------------------------------+
|
v
+--- LLM enrichment (optional) ---+
| confidence boost, false-positive |
| suppression, prose summary |
+---------------------------------+
|
v
+--- Dedup + Gating ---+
| file:line:category |
| confidence >= 0.6 |
+-----------------------+
|
v
+---- Persist to SQLite ----+
| 6 tables, task-id indexed |
+---------------------------+
|
v
+-- Redaction + Reports --+
| review_report.json/.md |
+-------------------------+
```

## Quick Start

```bash
cd examples/skills_code_review_agent

# Dev-fallback runtime, no API key needed:
python run_agent.py review --fixture security_eval --runtime local --dry-run

# Production default (requires Docker):
python run_agent.py review --diff-file my_change.diff

# Query a stored review:
python run_agent.py show --task-id <id>
```

## CLI Reference

| Flag | Values | Default | Description |
|---|---|---|---|
| `--diff-file` | path | — | Path to a unified diff / PR patch |
| `--repo-path` | path | — | Git repo; reviews `git diff HEAD` |
| `--fixture` | name | — | Bundled fixture (e.g. `security_eval`) |
| `--runtime` | `local` / `container` / `cube` | `container` | Sandbox runtime; `local` is dev-only |
| `--dry-run` | flag | off | Use deterministic fake model, no API key |
| `--db-url` | URL | `sqlite:///code_review.db` | SQLite path or DB URL |
| `--output-dir` | path | `out` | Directory for JSON + Markdown reports |
| `--no-llm` | flag | off | Skip the LLM enrichment step |

## Rule Categories

| Category | Script | Example Patterns |
|---|---|---|
| `security` | `check_security.py` | `eval`, `exec`, `shell=True`, `pickle`, `yaml.load`, SQL injection |
| `async_resource_leak` | `check_async_leak.py` | Unreferenced asyncio tasks, unmanaged sessions/files |
| `db_lifecycle` | `check_db_lifecycle.py` | DB connection/cursor/transaction lifecycle issues |
| `missing_test` | `check_tests_missing.py` | Source files changed without corresponding test changes |
| `secret_leak` | `check_secrets.py` | Hardcoded API keys, tokens, passwords (evidence pre-redacted) |

## Database Schema

| Table | Key Columns |
|---|---|
| `cr_review_tasks` | `id`, `status`, `input_type`, `input_ref`, `runtime`, `dry_run`, `diff_summary`, `created_at`, `finished_at` |
| `cr_sandbox_runs` | `id`, `task_id`, `script`, `category`, `status`, `exit_code`, `duration_ms`, `timed_out`, `stdout_summary`, `stderr_summary`, `error_type` |
| `cr_findings` | `id`, `task_id`, `severity`, `category`, `file`, `line`, `title`, `evidence`, `recommendation`, `confidence`, `source`, `status`, `dedup_key` |
| `cr_filter_events` | `id`, `task_id`, `target`, `decision`, `rule`, `reason`, `created_at` |
| `cr_metrics` | `id`, `task_id`, `total_duration_ms`, `sandbox_duration_ms`, `tool_calls`, `intercepts`, `findings_total`, `severity_distribution`, `error_distribution` |
| `cr_reports` | `id`, `task_id`, `report_json`, `report_md`, `created_at` |

All tables are joinable via `task_id` (foreign key to `cr_review_tasks.id`).

## Security Boundaries

- **Environment isolation**: Every checker script runs under `env -i` with only `PATH`, `HOME`, and `LANG` in the environment. No host env vars leak into the sandbox.
- **Timeouts**: Each script execution has a 60-second timeout (configurable). Total sandbox budget is capped at 300 seconds / 20 runs.
- **Output caps**: stdout and stderr are truncated at 256 KB per run.
- **Redaction**: Host-side regex-based redaction (shared pattern library with checkers) is applied to all reports and DB-stored content. No plaintext secrets survive in reports or the database.
- **Filter governance**: The `GovernanceToolFilter` blocks LLM-initiated tool calls outside the script allowlist, denies network tools (`curl`, `wget`, `pip`, `git`, etc.), forbids absolute/relative path escapes, and escalates high-risk commands (`sudo`, `docker`, `rm`, etc.) to `needs_human_review`.
- **Local runtime**: `--runtime local` is a development-only shortcut. Production deployments must use `container` or `cube`.

## Testing

```bash
# Run all tests (container tests are skipped by default):
python -m pytest examples/skills_code_review_agent/tests -q

# Opt into container-based tests (requires Docker):
CR_CONTAINER_TESTS=1 python -m pytest examples/skills_code_review_agent/tests -q
```

## 方案设计说明

**Skill 设计**:采用 SKILL.md 声明的规则文档 + 脚本架构,每个 checker 脚本以 JSON 契约输出 `{"findings": [...]}`,包含 severity、category、file、line、evidence、recommendation、confidence、source 字段,确保静态分析与 LLM 富化结果统一格式。

**沙箱隔离策略**:生产默认使用 container 运行时创建独立工作空间,`env -i` 启动脚本仅注入 PATH/HOME/LANG 白名单,单次超时 60 秒,预算上限 300 秒 / 20 次运行,输出截断 256 KB。local 运行时仅用于开发调试。

**Filter 策略**:GovernanceEngine 实施脚本白名单(6 个 checker),禁止网络工具(curl、wget、pip、git 等),拦截绝对路径 / `..` 穿越,高危命令(sudo、docker、rm 等)进入 `needs_human_review` 人工复核,超出预算直接 deny。

**监控字段**:`cr_metrics` 表记录 total_duration_ms、sandbox_duration_ms、tool_calls、intercepts、findings_total、severity_distribution(JSON 分布)和 error_distribution,可按 task_id 追溯全链路性能与异常。

**数据库 schema**:六表设计 — `cr_review_tasks`(任务),`cr_sandbox_runs`(沙箱运行记录),`cr_findings`(发现项),`cr_filter_events`(拦截事件),`cr_metrics`(监控指标),`cr_reports`(报告),均通过 task_id 外键关联查询。

**去重降噪**:file + line + category 三维联合去重,相同键保留最高 severity 与 confidence,置信度 < 0.6 的发现进入 `needs_human_review` 人工确认,避免低质量误报淹没关键问题。

**安全边界**:全链路脱敏 — 报告写入前经 host 侧 redaction(与 checker 共享 secret_patterns.py 规则),数据库存储同样经过脱敏,确保报告文件与 SQLite 库中绝无明文密钥泄露。整个方案通过技能化、沙箱化、治理化和持久化四个维度实现生产级代码审查自动化。
6 changes: 6 additions & 0 deletions examples/skills_code_review_agent/agent/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 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.
"""Agent wiring — fake model, prompts, config, and LlmAgent factory."""
31 changes: 31 additions & 0 deletions examples/skills_code_review_agent/agent/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 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.
"""LlmAgent wiring for the code review example."""
from trpc_agent_sdk.agents import LlmAgent
from trpc_agent_sdk.models import OpenAIModel
from trpc_agent_sdk.skills import SkillToolSet

from .config import get_model_config
from .fake_model import FakeReviewModel
from .prompts import INSTRUCTION


def create_review_agent(repository, dry_run: bool, tool_filters: list) -> LlmAgent:
"""Create the review agent. tool_filters guard skill_run via governance."""
if dry_run:
model = FakeReviewModel()
else:
api_key, url, model_name = get_model_config()
model = OpenAIModel(model_name=model_name, api_key=api_key, base_url=url)
toolset = SkillToolSet(repository=repository, filters=list(tool_filters))
return LlmAgent(
name="code_review_agent",
description="Automated code review agent combining skill scripts and LLM analysis.",
model=model,
instruction=INSTRUCTION,
tools=[toolset],
skill_repository=repository,
)
23 changes: 23 additions & 0 deletions examples/skills_code_review_agent/agent/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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.
"""Model configuration from environment variables."""
import os


def get_model_config() -> tuple[str, str, str]:
"""Return (api_key, base_url, model_name) or raise when unset."""
api_key = os.getenv("TRPC_AGENT_API_KEY", "")
url = os.getenv("TRPC_AGENT_BASE_URL", "")
model_name = os.getenv("TRPC_AGENT_MODEL_NAME", "")
if not api_key or not url or not model_name:
raise ValueError("TRPC_AGENT_API_KEY, TRPC_AGENT_BASE_URL and "
"TRPC_AGENT_MODEL_NAME must be set for real-model mode")
return api_key, url, model_name


def is_dry_run(explicit: bool) -> bool:
"""Dry-run when requested explicitly or when no API key is configured."""
return explicit or not os.getenv("TRPC_AGENT_API_KEY", "")
34 changes: 34 additions & 0 deletions examples/skills_code_review_agent/agent/fake_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 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.
"""Deterministic fake model so the full pipeline runs without any API key."""
import json
from typing import List

from trpc_agent_sdk.models import LLMModel, LlmResponse
from trpc_agent_sdk.types import Content, Part

_FAKE_PAYLOAD = {
"summary": "Dry-run review complete. Static findings are authoritative.",
"findings": [],
}


class FakeReviewModel(LLMModel):
"""LLMModel returning one canned JSON review response."""

def __init__(self, model_name: str = "fake-review-model", **kwargs):
super().__init__(model_name=model_name, **kwargs)

@classmethod
def supported_models(cls) -> List[str]:
return [r"fake-review-.*"]

def validate_request(self, request) -> None:
return None

async def _generate_async_impl(self, request, stream=False, ctx=None):
text = json.dumps(_FAKE_PAYLOAD)
yield LlmResponse(content=Content(role="model", parts=[Part.from_text(text=text)]))
30 changes: 30 additions & 0 deletions examples/skills_code_review_agent/agent/prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 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.
"""Prompts for the code review agent."""

INSTRUCTION = """You are an automated code reviewer.

You receive a unified diff plus baseline findings produced by static rule
scripts from the code-review skill. Confirm the baseline findings and add any
extra issues you can justify from the diff alone.

Reply with a single JSON object and nothing else:
{"summary": "<one paragraph>", "findings": [{"severity": "critical|high|medium|low|info",
"category": "security|async_resource_leak|db_lifecycle|missing_test|secret_leak",
"file": "<path>", "line": <int>, "title": "<short>", "evidence": "<code excerpt>",
"recommendation": "<fix>", "confidence": <0.0-1.0>}]}

Only report issues visible in the diff. Do not repeat baseline findings.
"""

REVIEW_REQUEST_TEMPLATE = """Review this change.

Baseline static findings (JSON):
{findings_json}

Unified diff (secrets already redacted):
{diff}
"""
6 changes: 6 additions & 0 deletions examples/skills_code_review_agent/filters_cr/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 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.
"""Code review tool filters package."""
38 changes: 38 additions & 0 deletions examples/skills_code_review_agent/filters_cr/governance_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 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.
"""Tool-level filter guarding LLM-initiated skill_run calls."""
from trpc_agent_sdk.abc import FilterType
from trpc_agent_sdk.filter import BaseFilter

from review.governance import GovernanceEngine


class GovernanceToolFilter(BaseFilter):
"""Blocks skill_run commands the GovernanceEngine does not allow."""

def __init__(self, engine: GovernanceEngine, on_event=None):
super().__init__()
self._type = FilterType.TOOL
self._name = "cr_governance"
self._engine = engine
self._on_event = on_event

async def _before(self, ctx, req, rsp):
command = ""
if isinstance(req, dict):
command = str(req.get("command", "") or "")
if not command:
return
decision = self._engine.check_command(command)
if self._on_event is not None:
self._on_event(decision)
if decision.decision != "allow":
rsp.rsp = {
"error": f"blocked by governance filter ({decision.rule})",
"decision": decision.decision,
"reason": decision.reason,
}
rsp.is_continue = False
14 changes: 14 additions & 0 deletions examples/skills_code_review_agent/fixtures/async_leak.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--- a/app/worker.py
+++ b/app/worker.py
@@ -5,6 +5,9 @@ async def start():
config = load_config()
+ session = aiohttp.ClientSession()
+ asyncio.create_task(poll_updates())
+ f = open("data.txt")
return config
--- a/tests/test_worker.py
+++ b/tests/test_worker.py
@@ -1,2 +1,3 @@
def test_start():
+ assert True
pass
14 changes: 14 additions & 0 deletions examples/skills_code_review_agent/fixtures/clean.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--- a/app/util.py
+++ b/app/util.py
@@ -1,4 +1,6 @@
def add(a, b):
+ # add two numbers and return the result
+ # used by the billing module
return a + b
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -1,3 +1,4 @@
from app.util import add
+
def test_add():
assert add(1, 2) == 3
15 changes: 15 additions & 0 deletions examples/skills_code_review_agent/fixtures/db_lifecycle.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--- a/app/repository.py
+++ b/app/repository.py
@@ -8,6 +8,10 @@ def reset_balances():
logger.info("resetting balances")
+ conn = sqlite3.connect("app.db")
+ cur = conn.cursor()
+ cur.execute("UPDATE accounts SET balance = 0")
+ conn.commit()
return True
--- a/tests/test_repository.py
+++ b/tests/test_repository.py
@@ -1,2 +1,3 @@
def test_reset():
+ assert True
pass
12 changes: 12 additions & 0 deletions examples/skills_code_review_agent/fixtures/duplicate_finding.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--- a/app/dynamic.py
+++ b/app/dynamic.py
@@ -3,5 +3,6 @@ def run_snippet(snippet, use_eval):
logger.debug("running snippet")
+ result = eval(snippet) if use_eval else exec(snippet)
return result
--- a/tests/test_dynamic.py
+++ b/tests/test_dynamic.py
@@ -1,2 +1,3 @@
def test_run():
+ assert True
pass
8 changes: 8 additions & 0 deletions examples/skills_code_review_agent/fixtures/missing_test.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--- a/app/service.py
+++ b/app/service.py
@@ -5,6 +5,9 @@ def process(order):
validate(order)
+ if order.total < 0:
+ raise ValueError("negative total")
+ order.status = "processed"
return order
12 changes: 12 additions & 0 deletions examples/skills_code_review_agent/fixtures/sandbox_failure.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--- a/app/misc.py
+++ b/app/misc.py
@@ -1,3 +1,4 @@
def greet(name):
+ # trivial change used by the sandbox-failure scenario
return "hello " + name
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -1,2 +1,3 @@
def test_greet():
+ assert True
pass
Loading
Loading