Skip to content

Commit 14a2d43

Browse files
committed
feat(examples): add code-review skill scaffold and diff parser
1 parent e113610 commit 14a2d43

6 files changed

Lines changed: 214 additions & 0 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--- a/app/util.py
2+
+++ b/app/util.py
3+
@@ -1,4 +1,6 @@
4+
def add(a, b):
5+
+ # add two numbers and return the result
6+
+ # used by the billing module
7+
return a + b
8+
--- a/tests/test_util.py
9+
+++ b/tests/test_util.py
10+
@@ -1,3 +1,4 @@
11+
from app.util import add
12+
+
13+
def test_add():
14+
assert add(1, 2) == 3
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
name: code-review
3+
description: Automated code review rules and scripts. Parses unified diffs and detects security risks, async/resource leaks, DB lifecycle problems, missing tests, and secret leaks.
4+
---
5+
Overview
6+
7+
This skill reviews a unified diff. Stage the diff at work/inputs/changes.diff, then run
8+
the scripts below from the workspace root. Every script prints a JSON object to stdout.
9+
10+
Scripts
11+
12+
- scripts/parse_diff.py <diff> : prints {"summary": ..., "files": [...]}
13+
- scripts/check_security.py <diff> : security findings (eval/exec, shell=True, pickle, yaml.load, SQL injection)
14+
- scripts/check_async_leak.py <diff> : async/resource leak findings (unreferenced tasks, unmanaged sessions/files)
15+
- scripts/check_db_lifecycle.py <diff> : DB connection/cursor/transaction lifecycle findings
16+
- scripts/check_tests_missing.py <diff> : source changed without test changes
17+
- scripts/check_secrets.py <diff> : hardcoded secrets (evidence pre-redacted)
18+
19+
Rules documentation lives under references/rules/.
20+
21+
Examples
22+
23+
1) python3 skills/code-review/scripts/parse_diff.py work/inputs/changes.diff
24+
2) python3 skills/code-review/scripts/check_security.py work/inputs/changes.diff
25+
26+
Output contract
27+
28+
Checker scripts print: {"findings": [{"severity", "category", "file", "line",
29+
"title", "evidence", "recommendation", "confidence", "source"}]}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
"""Unified diff parser shared by all code-review checker scripts (stdlib only)."""
7+
import re
8+
9+
_HUNK_RE = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@")
10+
11+
12+
def _strip_prefix(path):
13+
"""Strip git's a/ b/ prefixes."""
14+
if path.startswith(("a/", "b/")):
15+
return path[2:]
16+
return path
17+
18+
19+
def parse_unified_diff(text):
20+
"""Parse a unified diff into a list of per-file dicts with new-file line numbers."""
21+
files = []
22+
current = None
23+
old_no = new_no = 0
24+
for line in text.splitlines():
25+
if line.startswith("diff --git") or line.startswith("Index: "):
26+
current = None
27+
continue
28+
if line.startswith("--- "):
29+
raw_old = line[4:].split("\t")[0].strip()
30+
current = {
31+
"path": "",
32+
"old_path": "" if raw_old == "/dev/null" else _strip_prefix(raw_old),
33+
"is_new": raw_old == "/dev/null",
34+
"is_deleted": False,
35+
"hunks": [],
36+
"added_lines": [],
37+
"removed_lines": [],
38+
}
39+
files.append(current)
40+
continue
41+
if line.startswith("+++ "):
42+
if current is None:
43+
continue
44+
raw_new = line[4:].split("\t")[0].strip()
45+
if raw_new == "/dev/null":
46+
current["is_deleted"] = True
47+
current["path"] = current["old_path"]
48+
else:
49+
current["path"] = _strip_prefix(raw_new)
50+
continue
51+
m = _HUNK_RE.match(line)
52+
if m and current is not None:
53+
old_no, new_no = int(m.group(1)), int(m.group(3))
54+
current["hunks"].append({"old_start": old_no, "new_start": new_no, "lines": []})
55+
continue
56+
if current is None or not current["hunks"]:
57+
continue
58+
hunk = current["hunks"][-1]
59+
if line.startswith("+"):
60+
hunk["lines"].append({"tag": "+", "new_lineno": new_no, "old_lineno": None, "text": line[1:]})
61+
current["added_lines"].append({"line": new_no, "text": line[1:]})
62+
new_no += 1
63+
elif line.startswith("-"):
64+
hunk["lines"].append({"tag": "-", "new_lineno": None, "old_lineno": old_no, "text": line[1:]})
65+
current["removed_lines"].append({"line": old_no, "text": line[1:]})
66+
old_no += 1
67+
elif line.startswith(" ") or line == "":
68+
hunk["lines"].append({"tag": " ", "new_lineno": new_no, "old_lineno": old_no, "text": line[1:]})
69+
old_no += 1
70+
new_no += 1
71+
# "\ No newline at end of file" markers are intentionally ignored.
72+
return files
73+
74+
75+
def summarize(files):
76+
"""Return a compact summary dict for a parsed diff."""
77+
return {
78+
"files_changed": len(files),
79+
"additions": sum(len(f["added_lines"]) for f in files),
80+
"deletions": sum(len(f["removed_lines"]) for f in files),
81+
"files": [f["path"] or f["old_path"] for f in files],
82+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
"""CLI: parse a unified diff file and print a JSON summary to stdout."""
7+
import json
8+
import sys
9+
10+
from diffparse import parse_unified_diff, summarize
11+
12+
13+
def main():
14+
if len(sys.argv) < 2:
15+
print(json.dumps({"error": "usage: parse_diff.py <diff-file>"}))
16+
return 2
17+
with open(sys.argv[1], encoding="utf-8", errors="replace") as fh:
18+
files = parse_unified_diff(fh.read())
19+
print(json.dumps({"summary": summarize(files), "files": files}))
20+
return 0
21+
22+
23+
if __name__ == "__main__":
24+
sys.exit(main())
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
"""Test path setup for the skills_code_review_agent example."""
7+
import sys
8+
from pathlib import Path
9+
10+
EXAMPLE_ROOT = Path(__file__).resolve().parents[1]
11+
SCRIPTS_DIR = EXAMPLE_ROOT / "skills" / "code-review" / "scripts"
12+
13+
for p in (str(EXAMPLE_ROOT), str(SCRIPTS_DIR)):
14+
if p not in sys.path:
15+
sys.path.insert(0, p)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
"""Tests for the unified diff parser."""
7+
import json
8+
import subprocess
9+
import sys
10+
from pathlib import Path
11+
12+
EXAMPLE_ROOT = Path(__file__).resolve().parents[1]
13+
FIXTURES = EXAMPLE_ROOT / "fixtures"
14+
SCRIPTS = EXAMPLE_ROOT / "skills" / "code-review" / "scripts"
15+
16+
17+
def _clean_text():
18+
return (FIXTURES / "clean.diff").read_text(encoding="utf-8")
19+
20+
21+
def test_parse_files_and_paths():
22+
from diffparse import parse_unified_diff
23+
files = parse_unified_diff(_clean_text())
24+
assert [f["path"] for f in files] == ["app/util.py", "tests/test_util.py"]
25+
26+
27+
def test_added_line_numbers():
28+
from diffparse import parse_unified_diff
29+
files = parse_unified_diff(_clean_text())
30+
added = files[0]["added_lines"]
31+
assert added[0]["line"] == 2
32+
assert "add two numbers" in added[0]["text"]
33+
assert added[1]["line"] == 3
34+
35+
36+
def test_summarize():
37+
from diffparse import parse_unified_diff, summarize
38+
s = summarize(parse_unified_diff(_clean_text()))
39+
assert s["files_changed"] == 2
40+
assert s["additions"] == 3
41+
assert s["deletions"] == 0
42+
43+
44+
def test_parse_diff_cli():
45+
out = subprocess.run(
46+
[sys.executable, str(SCRIPTS / "parse_diff.py"), str(FIXTURES / "clean.diff")],
47+
capture_output=True, text=True, check=True)
48+
payload = json.loads(out.stdout)
49+
assert payload["summary"]["files_changed"] == 2
50+
assert payload["files"][0]["path"] == "app/util.py"

0 commit comments

Comments
 (0)