
Code Reviewer
- 1k installs
- 23.5k repo stars
- Updated July 17, 2026
- alirezarezvani/claude-skills
code-reviewer is a Claude agent skill that systematically reviews code for security, correctness, and maintainability issues for developers who need structured pre-merge review against universal and language-specific rul
About
code-reviewer is a Claude skill that systematically reviews code for security, correctness, and maintainability before merging or shipping. It applies rules from universal.md plus language-specific guides such as languages/c.md, flagging unsafe patterns like unbounded strcpy and promoting bounds-aware alternatives such as fgets, strncpy, strncat, and snprintf. Sample refactors demonstrate eliminating detector hits while preserving behavior. Developers reach for code-reviewer when they want agent-driven review coverage across security smells, SOLID and DRY concerns, and language idioms without waiting for human reviewer availability. Output is actionable review feedback tied to concrete rule violations and suggested fixes.
- Applies 70+ prioritized rules from universal.md and language-specific guides
- Refactors unsafe patterns while preserving original surface area and behavior
- Detects buffer overflows, unchecked allocations, format-string risks, and command injection
- Produces clean, standards-compliant samples that pass all rule checks
- Hard-gate: review must pass before invoking merge or deploy steps
Code Reviewer by the numbers
- 1,005 all-time installs (skills.sh)
- +10 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #123 of 1,356 Code Review & Quality skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
npx skills add https://github.com/alirezarezvani/claude-skills --skill code-reviewerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1k |
|---|---|
| repo stars | ★ 23.5k |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 17, 2026 |
| Repository | alirezarezvani/claude-skills ↗ |
How do you automate pre-merge code review for security issues?
Have an agent systematically review code for security, correctness, and maintainability issues before merging or shipping.
Who is it for?
Developers preparing pull requests who want agent-assisted review across security, correctness, and maintainability with language-specific rule packs.
Skip if: Teams needing formal penetration testing, compliance certification, or performance benchmarking that requires dedicated profiling tools instead of static review.
When should I use this skill?
A developer requests code review, pre-merge checks, or security and maintainability analysis on changed files before shipping.
What you get
Structured review findings, rule violation reports, and refactored code examples addressing security and maintainability smells.
- Review findings report
- Suggested security and maintainability fixes
Files
Code Reviewer
Automated code review tools for analyzing pull requests, detecting code quality issues, and generating review reports.
---
How This Skill Is Organized
code-reviewer/
SKILL.md ← you are here (tools + dispatch table)
rules/
universal.md ← security, async, resources, exceptions, performance — all languages
languages/
python.md ← Python-specific rules + idioms
typescript.md ← TypeScript / JavaScript-specific rules + idioms
go.md ← Go-specific rules + idioms
swift.md ← Swift-specific rules + idioms
kotlin.md ← Kotlin-specific rules + idioms
csharp.md ← C# / .NET-specific rules + idioms
java.md ← Java-specific rules + idioms
c.md ← C -specific rules + idioms
cpp.md ← C++ -specific rules + idioms
rust.md ← Rust -specific rules + idioms
ruby.md ← Ruby -specific rules + idioms
php.md ← PHP-specific rules + idioms
dart.md ← Dart / Flutter-specific rules + idiomsLoading order for every review
1. This file (SKILL.md) — tools and thresholds 2. rules/universal.md — always, for every language 3. The matching languages/*.md — one file based on the extension table below
That is always exactly 2 additional files, regardless of scope.
| Extension(s) | Load |
|---|---|
.py | languages/python.md |
.ts, .tsx, .js, .jsx, .mjs | languages/typescript.md |
.go | languages/go.md |
.swift | languages/swift.md |
.kt, .kts | languages/kotlin.md |
.cs, .csx, .razor, .cshtml | languages/csharp.md |
.java | languages/java.md |
.c, .h | languages/c.md |
.cpp, .cc, .cxx, .hpp, .hh, .hxx | languages/cpp.md |
.rs | languages/rust.md |
.rb, .rake, .gemspec, .ru | languages/ruby.md |
.php, .phtml | languages/php.md |
.dart | languages/dart.md |
---
Tools
PR Analyzer
Analyzes git diff between branches to assess review complexity and identify risks.
# Analyze current branch against main
python scripts/pr_analyzer.py /path/to/repo
# Compare specific branches
python scripts/pr_analyzer.py . --base main --head feature-branch
# JSON output for integration
python scripts/pr_analyzer.py /path/to/repo --jsonWhat it detects (universal — see also language file for language-specific signals):
- Hardcoded secrets (passwords, API keys, tokens, connection strings)
- SQL / query injection patterns
- Debug statements left in production code
- Lint / analyzer suppression annotations
- TODO/FIXME comments
Language-specific detections are defined in each languages/*.md file.
Output includes:
- Complexity score (1-10)
- Risk categorization (critical, high, medium, low)
- File prioritization for review order
- Commit message validation
---
Code Quality Checker
Analyzes source code for structural issues, code smells, and SOLID violations.
# Analyze a directory
python scripts/code_quality_checker.py /path/to/code
# Analyze specific language
# Valid values: python, typescript, javascript, go, swift, kotlin, csharp, java, c, cpp, rust, ruby, php, dart
python scripts/code_quality_checker.py . --language java
# JSON output
python scripts/code_quality_checker.py /path/to/code --jsonUniversal thresholds:
| Issue | Threshold |
|---|---|
| Long function | >50 lines |
| Large file | >500 lines |
| God class | >20 methods |
| Too many params | >5 |
| Deep nesting | >4 levels |
| High complexity | >10 branches |
Language-specific checks are defined in each languages/*.md file.
---
Review Report Generator
Combines PR analysis and code quality findings into structured review reports.
# Generate report for current repo
python scripts/review_report_generator.py /path/to/repo
# Markdown output
python scripts/review_report_generator.py . --format markdown --output review.md
# Use pre-computed analyses
python scripts/review_report_generator.py . \
--pr-analysis pr_results.json \
--quality-analysis quality_results.jsonVerdicts:
| Score | Verdict |
|---|---|
| 90+ with no high issues | Approve |
| 75+ with ≤2 high issues | Approve with suggestions |
| 50-74 | Request changes |
| <50 or critical issues | Block |
---
Adding a New Language
Reviewer guidance (required):
1. Create languages/<name>.md using any existing language file as a template — it must have sections: PR Analyzer Signals, Code Quality Checks, Security, Async, Resource Management, Exception Handling, Performance, Idioms. 2. Add the extension row to the dispatch table above.
That is all the agent-driven review needs.
Deterministic analyzer support (optional, recommended): the bundled scripts only flag a language they explicitly know. To make code_quality_checker.py score the new language:
3. Add the extensions to LANGUAGE_EXTENSIONS in scripts/code_quality_checker.py (this also adds the --language choice). 4. Add function / class / method regex entries for the language in the same file; otherwise it falls back to the Python patterns. 5. Optionally add a check_<name>_specific_smells(...) detector (see the C#, Java, and C ones) and call it from analyze_file. 6. Add assets/sample_<name>_smells.<ext> + _clean fixtures and commit the expected --json output under expected_outputs/ as a regression guard.
---
Regression Fixtures
Labelled fixtures live in assets/ with their committed --json output in expected_outputs/ (C#, Java, and C). Drift from the committed JSON signals a behaviour change in the analyzer:
python scripts/code_quality_checker.py assets/sample_java_smells.java --json \
| diff - expected_outputs/sample_java_smells_quality.json/*
* sample_c_clean.c — sample_c_smells.c refactored per
* rules/universal.md + languages/c.md. Same surface area, zero
* detector hits.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void safe_input(void) {
char buf[64];
/* fgets is bounds-aware */
if (fgets(buf, sizeof(buf), stdin) == NULL) {
return;
}
char dest[10];
/* strncpy with explicit bound + manual null-terminate */
strncpy(dest, buf, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
/* strncat with remaining-space bound */
size_t room = sizeof(dest) - strlen(dest) - 1;
strncat(dest, "world", room);
char msg[100];
/* snprintf is bounds-aware */
snprintf(msg, sizeof(msg), "%s says hello", buf);
/* Format string is a literal; buf is an argument */
printf("%s\n", buf);
char name[32];
/* %s with explicit width prevents overflow */
scanf("%31s", name);
}
void checked_alloc(int n) {
/* malloc result is NULL-checked before any dereference */
char *buf = malloc(n);
if (buf == NULL) {
return;
}
buf[0] = 'x';
buf[1] = 'y';
strncpy(buf, "ok", n - 1);
free(buf);
buf = NULL;
printf("done\n");
}
void run_safe_cmd(void) {
/* system() with a string literal — no command-injection surface */
system("ls -la");
}
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
safe_input();
checked_alloc(100);
run_safe_cmd();
return 0;
}
/*
* sample_c_smells.c — labelled instances of every C-specific pattern
* the code-reviewer skill flags. Every smell is annotated inline with
* its CWE and the rule from languages/c.md.
*
* Refactored counterpart: sample_c_clean.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void unsafe_input(void) {
char buf[64];
/* RULE: banned function gets() — CWE-242, no bounds check */
gets(buf);
char dest[10];
/* RULE: banned function strcpy() — no bounds check */
strcpy(dest, buf);
/* RULE: banned function strcat() — no bounds check */
strcat(dest, "world");
char msg[100];
/* RULE: banned function sprintf() — no bounds check */
sprintf(msg, "%s says hello", buf);
/* RULE: format-string vulnerability — CWE-134, buf controls format */
printf(buf);
char name[32];
/* RULE: unbounded scanf — %s without width, CWE-120 */
scanf("%s", name);
}
void leaky_alloc(int n) {
/* RULE: malloc result not NULL-checked within 5 lines — CWE-690 */
char *buf = malloc(n);
buf[0] = 'x';
buf[1] = 'y';
strcpy(buf, "leak");
/* RULE: free without zeroing pointer — CWE-416 dangling */
free(buf);
printf("done\n");
}
void run_user_cmd(const char *cmd_from_user) {
/* RULE: system() with non-literal argument — CWE-78 command injection */
system(cmd_from_user);
}
int main(int argc, char *argv[]) {
if (argc > 1) {
unsafe_input();
leaky_alloc(100);
run_user_cmd(argv[1]);
}
return 0;
}
// Sample C# file showing the fixed version of sample_csharp_smells.cs.
// Same shape, but every smell has been resolved per the patterns documented
// in rules/universal.md and languages/csharp.md.
//
// Run:
// python scripts/code_quality_checker.py assets/sample_csharp_clean.cs
//
// Expected: no HIGH C#-specific smells flagged.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Data.SqlClient;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Sample
{
public class DbOptions
{
// FIX: connection string from configuration, never inlined.
public string ConnectionString { get; init; } = "";
}
public class UserService
{
private readonly string _connectionString;
private readonly HttpClient _httpClient;
private readonly ILogger<UserService> _logger;
// FIX: IHttpClientFactory + IOptions, no hardcoded secrets, no `new HttpClient()`.
public UserService(
IHttpClientFactory httpClientFactory,
IOptions<DbOptions> dbOptions,
ILogger<UserService> logger)
{
_httpClient = httpClientFactory.CreateClient("api");
_connectionString = dbOptions.Value.ConnectionString;
_logger = logger;
}
// FIX: async Task (not async void) so callers can await and observe exceptions.
public async Task HandleClickAsync()
{
// FIX: await the Task instead of blocking on it.
var data = await FetchAsync().ConfigureAwait(false);
_logger.LogInformation("Fetched {Length} bytes", data.Length);
}
public async Task<string> FetchAsync()
{
try
{
// FIX: await the async call — Task is no longer discarded.
await FireAndForgetAsync().ConfigureAwait(false);
// FIX: real null check, no `!`.
var user = await GetCurrentUserAsync().ConfigureAwait(false);
if (user is null)
{
throw new InvalidOperationException("No current user");
}
_ = user.Name;
return await _httpClient
.GetStringAsync("https://api.example/data")
.ConfigureAwait(false);
}
catch (HttpRequestException ex)
{
// FIX: catch specific exception, log with context, rethrow.
_logger.LogError(ex, "Upstream fetch failed");
throw;
}
}
// FIX: real type, not `dynamic`.
public User? CurrentUser { get; private set; }
// FIX: `unsafe` removed — none of the logic actually needed pointers.
public int FirstValue(int[] values) => values.Length > 0 ? values[0] : 0;
// FIX: no #pragma / [SuppressMessage] — root cause fixed instead.
public string GetName(int id)
{
// FIX: `using var` disposes connection + command deterministically.
using var conn = new SqlConnection(_connectionString);
// FIX: parameterized query, no string concatenation.
using var cmd = new SqlCommand("SELECT name FROM users WHERE id = @id", conn);
cmd.Parameters.AddWithValue("@id", id);
conn.Open();
return (string)cmd.ExecuteScalar();
}
private Task<User?> GetCurrentUserAsync() => Task.FromResult<User?>(null);
private Task FireAndForgetAsync() => Task.CompletedTask;
}
public record User(int Id, string Name);
}
// Sample C# file demonstrating every C#-specific pattern the code-reviewer
// skill detects. Each smell is labelled inline. This file is NOT meant to
// compile cleanly — it is a fixture for code_quality_checker.py and
// pr_analyzer.py.
//
// Run:
// python scripts/code_quality_checker.py assets/sample_csharp_smells.cs
//
// Expected output: see expected_outputs/sample_csharp_smells_quality.json
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Data.SqlClient;
using System.Diagnostics.CodeAnalysis;
namespace Sample
{
public class UserService
{
// [hardcoded_secrets] hardcoded connection string with password
public string ConnectionString = "Server=prod;Database=app;Password=hunter2;";
// [csharp_async_void] async void on a non-event-handler signature
public async void HandleClick(object sender, EventArgs e)
{
// [csharp_blocking_async] .Result blocks on Task in a sync context
var data = FetchAsync().Result;
// [console_log] Debug.WriteLine output statement
Debug.WriteLine(data);
}
public async Task<string> FetchAsync()
{
// [csharp_new_httpclient] new HttpClient() in method body
// [csharp_undisposed_idisposable] HttpClient not in `using`
var client = new HttpClient();
try
{
// [csharp_missing_await] FireAndForgetAsync() returns Task, never awaited
FireAndForgetAsync();
// [csharp_null_forgiving] `user!.Name` forces null-forgiving
var name = user!.Name;
return await client.GetStringAsync("https://api.example/data");
}
catch (Exception)
{
// [csharp_swallowed_exception] empty catch (Exception)
}
return null!;
}
// [loose_type] C# `dynamic` overuse
public dynamic Untyped = null;
// [csharp_unsafe_block] `unsafe` modifier on a method
public unsafe void Pointers()
{
int x = 0;
int* p = &x;
}
// [analyzer_disable] #pragma warning disable
#pragma warning disable CS0168
// [analyzer_disable] [SuppressMessage] attribute
[SuppressMessage("Style", "IDE0060")]
public string GetName(SqlConnection conn, int id)
{
// [csharp_undisposed_idisposable] SqlCommand without `using`
// [sql_concatenation] string concatenation builds SQL with user input
var cmd = new SqlCommand("SELECT name FROM users WHERE id = " + id, conn);
return cmd.ExecuteScalar().ToString();
}
}
}
// Sample Java file showing the fixed version of sample_java_smells.java.
// Same shape, but every smell has been resolved per the patterns documented
// in rules/universal.md and languages/java.md.
//
// Run:
// python scripts/code_quality_checker.py assets/sample_java_clean.java
//
// Expected: no HIGH Java-specific smells flagged.
package sample;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import com.fasterxml.jackson.databind.ObjectMapper;
public class UserService {
// FIX: heavy object shared as a singleton instead of constructed per call.
private static final ObjectMapper MAPPER = new ObjectMapper();
// FIX: connection string injected from configuration, never inlined.
private final String connectionString;
public UserService(String connectionString) {
this.connectionString = connectionString;
}
public String getName(Connection conn, int id) {
// FIX: try-with-resources guarantees the stream and statement close.
try (InputStream config = new FileInputStream("/etc/config");
// FIX: parameterized query, no string concatenation.
PreparedStatement stmt =
conn.prepareStatement("SELECT name FROM users WHERE id = ?")) {
stmt.setInt(1, id);
try (ResultSet rs = stmt.executeQuery()) {
return rs.next() ? rs.getString("name") : null;
}
} catch (Exception e) {
// FIX: rethrow with context instead of swallowing.
throw new IllegalStateException("Failed to load user " + id, e);
}
}
public void process() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// FIX: restore the interrupt flag so cancellation still propagates.
Thread.currentThread().interrupt();
}
}
}
// Sample Java file demonstrating the Java-specific patterns the code-reviewer
// skill detects. Each smell is labelled inline. This file is NOT meant to
// compile cleanly — it is a fixture for code_quality_checker.py and
// pr_analyzer.py.
//
// Run:
// python scripts/code_quality_checker.py assets/sample_java_smells.java
//
// Expected output: see expected_outputs/sample_java_smells_quality.json
package sample;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.Statement;
import com.fasterxml.jackson.databind.ObjectMapper;
public class UserService {
// [hardcoded_secrets] hardcoded JDBC URL with password
public String connectionString = "jdbc:postgresql://prod/app?user=app&password=hunter2";
// [analyzer_disable] @SuppressWarnings without justification
@SuppressWarnings("unchecked")
public String getName(Connection conn, int id) throws Exception {
// [java_unclosed_resource] FileInputStream not in try-with-resources
FileInputStream fis = new FileInputStream("/etc/config");
// [java_per_use_heavy_object] new ObjectMapper() constructed per call
ObjectMapper mapper = new ObjectMapper();
try {
Statement stmt = conn.createStatement();
// [sql_concatenation] string concatenation builds SQL with user input
return stmt.executeQuery("SELECT name FROM users WHERE id = " + id).toString();
} catch (Exception e) {
// [java_empty_catch] empty catch swallows the exception
}
return null;
}
public void process() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// [java_swallowed_interrupt] interrupt flag not restored
// [console_log] printStackTrace used as error handling
e.printStackTrace();
}
}
public void log(String message) {
// [console_log] System.out.println left in production code
System.out.println(message);
}
}
{
"file": "/home/user/claude-skills/engineering-team/skills/code-reviewer/assets/sample_c_clean.c",
"language": "c",
"metrics": {
"lines": {
"total": 72,
"code": 43,
"blank": 17,
"comment": 12
},
"functions": 4,
"classes": 0,
"avg_complexity": 1.8
},
"quality_score": 100,
"grade": "A",
"smells": [
{
"type": "long_function",
"severity": "medium",
"message": "Function 'safe_input' has 61 lines (max: 50)",
"location": "safe_input"
},
{
"type": "magic_number",
"severity": "low",
"message": "Magic number 100 should be a named constant",
"location": "line 29"
},
{
"type": "magic_number",
"severity": "low",
"message": "Magic number 100 should be a named constant",
"location": "line 68"
}
],
"solid_violations": [],
"function_details": [
{
"name": "safe_input",
"parameters": 1,
"lines": 61,
"complexity": 3
},
{
"name": "checked_alloc",
"parameters": 1,
"lines": 31,
"complexity": 2
},
{
"name": "run_safe_cmd",
"parameters": 1,
"lines": 15,
"complexity": 1
},
{
"name": "main",
"parameters": 2,
"lines": 9,
"complexity": 1
}
],
"class_details": []
}
{
"file": "/home/user/claude-skills/engineering-team/skills/code-reviewer/assets/sample_c_smells.c",
"language": "c",
"metrics": {
"lines": {
"total": 67,
"code": 37,
"blank": 17,
"comment": 13
},
"functions": 4,
"classes": 0,
"avg_complexity": 2.0
},
"quality_score": 4,
"grade": "F",
"smells": [
{
"type": "long_function",
"severity": "medium",
"message": "Function 'unsafe_input' has 54 lines (max: 50)",
"location": "unsafe_input"
},
{
"type": "magic_number",
"severity": "low",
"message": "Magic number 242 should be a named constant",
"location": "line 17"
},
{
"type": "magic_number",
"severity": "low",
"message": "Magic number 100 should be a named constant",
"location": "line 27"
},
{
"type": "magic_number",
"severity": "low",
"message": "Magic number 134 should be a named constant",
"location": "line 31"
},
{
"type": "magic_number",
"severity": "low",
"message": "Magic number 120 should be a named constant",
"location": "line 35"
},
{
"type": "magic_number",
"severity": "low",
"message": "Magic number 690 should be a named constant",
"location": "line 41"
},
{
"type": "magic_number",
"severity": "low",
"message": "Magic number 416 should be a named constant",
"location": "line 47"
},
{
"type": "magic_number",
"severity": "low",
"message": "Magic number 100 should be a named constant",
"location": "line 62"
},
{
"type": "c_banned_gets",
"severity": "high",
"message": "'gets()' is unsafe: no bounds check, removed from C11 (CWE-242)",
"location": "offset 117"
},
{
"type": "c_banned_strcpy",
"severity": "high",
"message": "'strcpy()' is unsafe: no bounds check \u2014 prefer strncpy or strlcpy",
"location": "offset 157"
},
{
"type": "c_banned_strcpy",
"severity": "high",
"message": "'strcpy()' is unsafe: no bounds check \u2014 prefer strncpy or strlcpy",
"location": "offset 447"
},
{
"type": "c_banned_strcat",
"severity": "high",
"message": "'strcat()' is unsafe: no bounds check \u2014 prefer strncat or strlcat",
"location": "offset 186"
},
{
"type": "c_banned_sprintf",
"severity": "high",
"message": "'sprintf()' is unsafe: no bounds check \u2014 prefer snprintf",
"location": "offset 238"
},
{
"type": "c_format_string",
"severity": "high",
"message": "'printf(buf)' uses a non-literal format string \u2014 CWE-134 format string vulnerability",
"location": "offset 284"
},
{
"type": "c_unbounded_scanf",
"severity": "high",
"message": "scanf '%s' without a width specifier \u2014 unbounded read can overflow the destination buffer",
"location": "offset 326"
},
{
"type": "c_malloc_unchecked",
"severity": "medium",
"message": "'buf' from malloc/calloc/realloc is not NULL-checked within 5 lines \u2014 dereferencing NULL is UB (CWE-690)",
"location": "line 36"
},
{
"type": "c_free_without_null",
"severity": "low",
"message": "'free(buf)' not followed by 'buf = NULL;' \u2014 dangling pointer can be reused (CWE-416)",
"location": "line 42"
},
{
"type": "c_system_non_literal",
"severity": "high",
"message": "'system(cmd_from_user)' with a non-literal argument \u2014 command injection (CWE-78); use execve with validated args",
"location": "offset 571"
}
],
"solid_violations": [],
"function_details": [
{
"name": "unsafe_input",
"parameters": 1,
"lines": 54,
"complexity": 2
},
{
"name": "leaky_alloc",
"parameters": 1,
"lines": 28,
"complexity": 2
},
{
"name": "run_user_cmd",
"parameters": 1,
"lines": 15,
"complexity": 2
},
{
"name": "main",
"parameters": 2,
"lines": 9,
"complexity": 2
}
],
"class_details": []
}
{
"file": "/home/user/claude-skills/engineering-team/skills/code-reviewer/assets/sample_csharp_clean.cs",
"language": "csharp",
"metrics": {
"lines": {
"total": 101,
"code": 67,
"blank": 14,
"comment": 20
},
"functions": 13,
"classes": 3,
"avg_complexity": 1.3
},
"quality_score": 98,
"grade": "A",
"smells": [
{
"type": "csharp_unused_using",
"severity": "low",
"message": "'using System;' appears unused",
"location": "System"
},
{
"type": "csharp_unused_using",
"severity": "low",
"message": "'using System.Net.Http;' appears unused",
"location": "System.Net.Http"
},
{
"type": "csharp_unused_using",
"severity": "low",
"message": "'using System.Threading.Tasks;' appears unused",
"location": "System.Threading.Tasks"
},
{
"type": "csharp_unused_using",
"severity": "low",
"message": "'using System.Data.SqlClient;' appears unused",
"location": "System.Data.SqlClient"
},
{
"type": "csharp_unused_using",
"severity": "low",
"message": "'using Microsoft.Extensions.Logging;' appears unused",
"location": "Microsoft.Extensions.Logging"
},
{
"type": "csharp_unused_using",
"severity": "low",
"message": "'using Microsoft.Extensions.Options;' appears unused",
"location": "Microsoft.Extensions.Options"
}
],
"solid_violations": [],
"function_details": [
{
"name": "HttpClient",
"parameters": 0,
"lines": 2,
"complexity": 1
},
{
"name": "UserService",
"parameters": 3,
"lines": 8,
"complexity": 1
},
{
"name": "Task",
"parameters": 1,
"lines": 2,
"complexity": 2
},
{
"name": "HandleClickAsync",
"parameters": 0,
"lines": 8,
"complexity": 1
},
{
"name": "FetchAsync",
"parameters": 0,
"lines": 12,
"complexity": 2
},
{
"name": "InvalidOperationException",
"parameters": 1,
"lines": 21,
"complexity": 3
},
{
"name": "FirstValue",
"parameters": 1,
"lines": 4,
"complexity": 1
},
{
"name": "GetName",
"parameters": 1,
"lines": 4,
"complexity": 1
},
{
"name": "SqlConnection",
"parameters": 1,
"lines": 3,
"complexity": 1
},
{
"name": "SqlCommand",
"parameters": 2,
"lines": 7,
"complexity": 1
}
],
"class_details": [
{
"name": "DbOptions",
"methods": 0,
"lines": 7
},
{
"name": "UserService",
"methods": 8,
"lines": 75
},
{
"name": "User",
"methods": 0,
"lines": 3
}
]
}
{
"file": "/home/user/claude-skills/engineering-team/skills/code-reviewer/assets/sample_csharp_smells.cs",
"language": "csharp",
"metrics": {
"lines": {
"total": 79,
"code": 43,
"blank": 11,
"comment": 25
},
"functions": 7,
"classes": 1,
"avg_complexity": 1.3
},
"quality_score": 45,
"grade": "F",
"smells": [
{
"type": "csharp_async_void",
"severity": "high",
"message": "'async void HandleClick' \u2014 only safe for event handlers; prefer 'async Task'",
"location": "HandleClick"
},
{
"type": "csharp_blocking_async",
"severity": "high",
"message": "Blocking call on async operation ('.Result' / '.Wait()' / '.GetAwaiter().GetResult()') \u2014 can deadlock in ASP.NET contexts",
"location": "offset 430"
},
{
"type": "csharp_swallowed_exception",
"severity": "high",
"message": "Empty catch block swallows exceptions silently",
"location": "offset 853"
},
{
"type": "csharp_undisposed_idisposable",
"severity": "medium",
"message": "'HttpClient' looks like IDisposable but is not wrapped in 'using' / 'using var'",
"location": "offset 555"
},
{
"type": "csharp_undisposed_idisposable",
"severity": "medium",
"message": "'SqlCommand' looks like IDisposable but is not wrapped in 'using' / 'using var'",
"location": "offset 1289"
},
{
"type": "csharp_new_httpclient",
"severity": "medium",
"message": "'new HttpClient()' \u2014 prefer IHttpClientFactory or a long-lived static instance to avoid socket exhaustion",
"location": "offset 606"
},
{
"type": "csharp_missing_await",
"severity": "medium",
"message": "Async method called without 'await' \u2014 Task is discarded",
"location": "line 42"
},
{
"type": "csharp_unused_using",
"severity": "low",
"message": "'using System;' appears unused",
"location": "System"
},
{
"type": "csharp_unused_using",
"severity": "low",
"message": "'using System.Net.Http;' appears unused",
"location": "System.Net.Http"
},
{
"type": "csharp_unused_using",
"severity": "low",
"message": "'using System.Threading.Tasks;' appears unused",
"location": "System.Threading.Tasks"
},
{
"type": "csharp_unused_using",
"severity": "low",
"message": "'using System.Data.SqlClient;' appears unused",
"location": "System.Data.SqlClient"
},
{
"type": "csharp_unused_using",
"severity": "low",
"message": "'using System.Diagnostics.CodeAnalysis;' appears unused",
"location": "System.Diagnostics.CodeAnalysis"
}
],
"solid_violations": [],
"function_details": [
{
"name": "HandleClick",
"parameters": 2,
"lines": 9,
"complexity": 1
},
{
"name": "FetchAsync",
"parameters": 0,
"lines": 3,
"complexity": 1
},
{
"name": "HttpClient",
"parameters": 0,
"lines": 3,
"complexity": 1
},
{
"name": "HttpClient",
"parameters": 0,
"lines": 24,
"complexity": 3
},
{
"name": "Pointers",
"parameters": 0,
"lines": 11,
"complexity": 1
},
{
"name": "GetName",
"parameters": 2,
"lines": 5,
"complexity": 1
},
{
"name": "SqlCommand",
"parameters": 2,
"lines": 6,
"complexity": 1
}
],
"class_details": [
{
"name": "UserService",
"methods": 4,
"lines": 61
}
]
}
{
"file": "/home/user/claude-skills/engineering-team/skills/code-reviewer/assets/sample_java_clean.java",
"language": "java",
"metrics": {
"lines": {
"total": 56,
"code": 33,
"blank": 9,
"comment": 14
},
"functions": 3,
"classes": 1,
"avg_complexity": 2.0
},
"quality_score": 100,
"grade": "A",
"smells": [
{
"type": "magic_number",
"severity": "low",
"message": "Magic number 1000 should be a named constant",
"location": "line 49"
}
],
"solid_violations": [],
"function_details": [
{
"name": "UserService",
"parameters": 1,
"lines": 5,
"complexity": 1
},
{
"name": "getName",
"parameters": 2,
"lines": 17,
"complexity": 3
},
{
"name": "process",
"parameters": 0,
"lines": 10,
"complexity": 2
}
],
"class_details": [
{
"name": "UserService",
"methods": 3,
"lines": 38
}
]
}
{
"file": "/home/user/claude-skills/engineering-team/skills/code-reviewer/assets/sample_java_smells.java",
"language": "java",
"metrics": {
"lines": {
"total": 57,
"code": 29,
"blank": 10,
"comment": 18
},
"functions": 3,
"classes": 1,
"avg_complexity": 2.0
},
"quality_score": 68,
"grade": "D",
"smells": [
{
"type": "magic_number",
"severity": "low",
"message": "Magic number 1000 should be a named constant",
"location": "line 44"
},
{
"type": "java_empty_catch",
"severity": "high",
"message": "Empty catch block swallows exceptions silently",
"location": "offset 684"
},
{
"type": "java_print_stack_trace",
"severity": "medium",
"message": "'printStackTrace()' is not real error handling \u2014 log via a proper logger or rethrow with context",
"location": "offset 913"
},
{
"type": "java_swallowed_interrupt",
"severity": "high",
"message": "InterruptedException caught without 'Thread.currentThread().interrupt()' \u2014 breaks cooperative cancellation",
"location": "offset 841"
},
{
"type": "java_unclosed_resource",
"severity": "medium",
"message": "'FileInputStream' looks like an AutoCloseable but is not in a try-with-resources statement",
"location": "offset 366"
},
{
"type": "java_per_use_heavy_object",
"severity": "medium",
"message": "'new ObjectMapper()' is expensive \u2014 share a singleton instance instead of constructing per call",
"location": "offset 451"
}
],
"solid_violations": [],
"function_details": [
{
"name": "getName",
"parameters": 2,
"lines": 18,
"complexity": 3
},
{
"name": "process",
"parameters": 0,
"lines": 11,
"complexity": 2
},
{
"name": "log",
"parameters": 1,
"lines": 6,
"complexity": 1
}
],
"class_details": [
{
"name": "UserService",
"methods": 3,
"lines": 40
}
]
}
C — Language-Specific Review Notes
Load this file alongside rules/universal.md. Universal rules are not repeated here — only C-specific rules and idioms.
---
PR Analyzer — C Risk Signals
printf/ debugfprintf(stderr, ...)statements left in production code// TODO/// FIXMEcomments near memory management code — high risk- Disabled compiler warnings (
#pragma GCC diagnostic ignore,-wflags in Makefile) - Hardcoded credentials or keys in source
- Use of banned functions:
gets,strcpy,strcat,sprintf,scanfwithout width limits
---
Code Quality — C Checks
- Functions longer than 50 lines — C functions tend to grow organically and become hard to reason about
- Missing
NULLcheck aftermalloc/calloc/realloc - Return value of functions ignored without explicit
(void)cast - Global mutable state used across translation units without clear ownership
- Magic numbers without
#defineorconst— especially sizes and offsets - Mixed
malloc/freeownership — unclear which caller is responsible for freeing
---
Security
- Flag
gets()— no bounds checking, always a buffer overflow; replace withfgets() - Flag
strcpy()/strcat()— usestrncpy()/strncat()with explicit size, orstrlcpy()/strlcat() - Flag
sprintf()— usesnprintf()with explicit buffer size - Flag
scanf("%s", buf)without a width specifier — unbounded read - Flag
strlen()result used as a signed integer — potential truncation on 64-bit - Flag user-controlled data used as a format string (
printf(user_input)) — format string attack - Flag integer arithmetic used as array index without bounds check
- Flag signed integer overflow — undefined behavior in C
---
Async / Concurrency
- Flag shared global or
staticvariables accessed from multiple threads without a mutex or_Atomic - Flag
pthread_mutex_t/sem_tnot initialized before use - Flag signal handlers that call non-async-signal-safe functions (
malloc,printf, etc.) - Flag
volatileused as a substitute for proper synchronization — it is not sufficient - Flag lock acquisition order inconsistency across call sites — deadlock risk
---
Resource Management
- Flag every
malloc/calloc/reallocpath — verify a matchingfreeexists on all exit paths - Flag
fopenwithout a matchingfcloseon all paths including error paths - Flag
dup/socket/openfile descriptors not closed on all paths - Flag stack-allocated VLAs (variable-length arrays) of unbounded size — stack overflow risk
- Flag
reallocreturn value assigned directly to the source pointer — leaks on failure
---
Exception Handling
- Flag ignored return values from
malloc,fopen,read,write,close— all can fail - Flag
errnochecked after a function that doesn't set it, or not checked immediately after one that does - Flag
perror/strerroras the sole error handling in library code — propagate errors to callers - Flag functions that return
-1on error without documenting whicherrnovalues are possible - Flag
assert()used for runtime error handling — disabled byNDEBUGin production builds
---
Performance
- Flag
strlen()called repeatedly on the same string in a loop — cache the result - Flag unnecessary copies of large structs passed by value — pass by pointer
- Flag
memcpy/memseton overlapping regions — usememmovefor overlapping - Flag repeated heap allocations in a tight loop — consider a pool or stack allocation
- Flag
volatileon variables not accessed by hardware or signal handlers — prevents optimization
---
Idioms and Best Practices
Memory Safety
- Every pointer must have a clear owner responsible for freeing it — document ownership in comments
- Set pointers to
NULLimmediately afterfreeto catch use-after-free early - Prefer
callocovermalloc+memsetfor zero-initialized allocations - Use
conston pointer parameters that the function does not modify
Defensive Coding
- Always check
NULLreturns from allocation functions - Use
size_tfor sizes and counts — neverint - Prefer
snprintfandfgetsover any unbounded string function - Compile with
-Wall -Wextra -Werrorand treat warnings as errors
Portability
- Do not assume pointer size equals
intsize — useintptr_t/uintptr_t - Do not rely on undefined behavior for performance — use compiler intrinsics instead
- Use
stdint.htypes (uint32_t,int64_t) for fixed-width requirements
C++ — Language-Specific Review Notes
Load this file alongside rules/universal.md. Universal rules are not repeated here — only C++-specific rules and idioms.
---
PR Analyzer — C++ Risk Signals
- Raw
new/deleteoutside of smart pointer wrappers reinterpret_cast— almost always a red flag; require justification- Disabled compiler warnings (
#pragma warning(disable:...),-w) // TODO/// FIXMEnear ownership or lifetime code- Hardcoded credentials or keys in source
- Use of deprecated C-style functions:
strcpy,sprintf,gets
---
Code Quality — C++ Checks
- Raw owning pointers (
T*) used whereunique_ptr/shared_ptrwould express ownership shared_ptroverused whereunique_ptrsuffices — implies shared ownership unnecessarilystd::endlused in hot paths — flushes the buffer every call; prefer'\n'- Implicit conversions between signed and unsigned integers
- Virtual destructor missing on base classes with virtual methods
catch (...)swallowing all exceptions without logging or re-throwing
---
Security
- Flag
reinterpret_caston user-controlled data — potential type confusion - Flag raw array indexing without bounds check — use
.at()or assert bounds - Flag
std::stringdata passed to C APIs without null-termination guarantee — use.c_str() - Flag hardcoded buffer sizes — derive from
sizeofor usestd::array<T, N> - Flag
sscanf/sprintf— usestd::istringstreamorstd::format(C++20) - Flag user-controlled data used as a format string
---
Async / Concurrency
- Flag
std::shared_ptraccessed from multiple threads — the pointer itself is not thread-safe for write; usestd::atomic<std::shared_ptr<T>>(C++20) or external locking - Flag
std::vector/std::mapmutated from multiple threads without a mutex - Flag
std::mutexlocked twice in the same thread withoutstd::recursive_mutex— deadlock - Flag detached threads (
std::thread::detach) with no lifetime coordination - Flag
volatileused instead ofstd::atomicfor inter-thread communication
---
Resource Management
- Flag raw
newreturning an owning pointer — wrap immediately instd::make_uniqueorstd::make_shared - Flag
deletecalled manually outside of a destructor or smart pointer — ownership confusion - Flag RAII violations — resources acquired in constructor but not released via destructor
- Flag
std::ifstream/std::ofstreamnot checked for open failure before use - Flag exceptions thrown from destructors — causes
std::terminateif thrown during stack unwinding
---
Exception Handling
- Flag
catch (...)that swallows exceptions without logging or re-throwing - Flag exceptions thrown from destructors — wrap in
try/catchinside the destructor - Flag
noexcepton functions that can actually throw — causesstd::terminate - Flag exception specifications (
throw(...)) — deprecated since C++11, removed in C++17 - Flag using exceptions for control flow in performance-critical paths
---
Performance
- Flag pass-by-value for non-trivial types where pass-by-const-reference suffices
- Flag
std::vector::push_backin a loop withoutreservewhen size is known — repeated reallocations - Flag
std::mapused wherestd::unordered_mapwould give O(1) lookup - Flag
std::endlin loops — prefer'\n'to avoid repeated buffer flushes - Flag unnecessary copies from missing
std::moveon local temporaries being returned or passed
---
Idioms and Best Practices
Ownership and Lifetime
- Prefer
std::unique_ptrfor sole ownership,std::shared_ptronly for shared ownership - Prefer
std::make_unique/std::make_sharedovernew— exception-safe - Use
std::weak_ptrto breakshared_ptrcycles - Never use raw owning pointers in new code — they are for non-owning observation only
Modern C++ (17/20)
- Prefer
std::optional<T>over sentinel values or nullable pointers for optional returns - Prefer
std::variantover tagged unions - Prefer
std::string_viewoverconst std::string&for read-only string parameters - Prefer range-based
forloops over index loops where the index isn't needed - Prefer
if constexprover#ifdeffor compile-time branching
Type Safety
- Prefer
static_castover C-style casts — explicit and auditable - Avoid
reinterpret_castexcept in low-level I/O or FFI code with a comment - Use
enum classover plainenumto avoid implicit integer conversions
C# / .NET — Language-Specific Review Notes
Load this file alongside rules/universal.md. Universal rules are not repeated here — only C#-specific rules and idioms.
---
PR Analyzer — C# Risk Signals
#pragma warning disableand[SuppressMessage]— verify they are justifiedunsafe { }blocks — require explicit sign-off- Null-forgiving operator (
!) used broadly without justification dynamicused outside of interop scenarios- Hardcoded connection strings in source files
---
Code Quality — C# Checks
async voidmethods (except event handlers)Taskreturned but not awaitedIDisposableobjects not inusing/using var- Bare
catch { }orcatch (Exception e) { }swallowing silently - Nullable reference types feature disabled at project level
---
Security
- Flag raw string interpolation in SQL queries — require parameterized queries (
SqlCommand) or EF Core - Flag missing
[ValidateAntiForgeryToken]on state-changing controller actions - Flag user-controlled data passed to
Process.Start()orFileAPIs without validation - Flag hardcoded connection strings — require
appsettings.json+ secrets management - Flag
[AllowAnonymous]on endpoints that should be protected
---
Async / Await
- Flag
async voidmethods outside of event handlers — cannot be awaited and swallow exceptions - Flag
.Result,.Wait(), or.GetAwaiter().GetResult()onTask— causes deadlocks in ASP.NET contexts - Flag missing
ConfigureAwait(false)in library (non-application) code - Flag
Task.Run()wrapping synchronous code inside ASP.NET request handlers unnecessarily - Flag
CancellationTokennot threaded through to downstream async calls
---
Resource Management
- Flag
IDisposableobjects (SqlConnection,HttpClient,FileStream, etc.) not wrapped inusing/using var - Flag
HttpClientinstantiated withnewinside a method — useIHttpClientFactoryor a shared static instance to avoid socket exhaustion - Flag
DbContextregistered as a singleton in DI — it must be scoped - Flag
MemoryStream/MemoryCachegrowing unboundedly without eviction policy
---
Exception Handling
- Flag
catch { }orcatch (Exception) { }with no logging or re-throw — silent swallow - Flag
catch (Exception e) { throw e; }— resets the stack trace; usethrow;instead - Flag catching
Exceptionwhen a specific type (IOException,HttpRequestException) is appropriate - Flag exception filters (
when) used for side effects that suppress the exception - Flag exceptions used for control flow in hot paths — use
Try*pattern methods instead
---
Performance
- Flag
.ToList()/.ToArray()onIQueryablebefore filtering — forces all rows into memory; filter server-side first - Flag
stringconcatenation in loops — useStringBuilder - Flag
Enumerable.Count()onIQueryablewhen only an existence check is needed — useAny() - Flag
awaitin a loop whereTask.WhenAll()would parallelize the work - Flag synchronous file or network I/O in an
asyncmethod — use the async overload
---
Idioms and Best Practices
Null Safety
- Ensure
<Nullable>enable</Nullable>is set in the project file - Flag excessive use of
!(null-forgiving) without a comment explaining why - Prefer
is null/is not nullover== nullfor null checks
LINQ
- Flag
First()whereFirstOrDefault()is safer - Flag complex LINQ chains that would be clearer as explicit loops
Modern C# (10+)
- Prefer
recordtypes for immutable data carriers - Prefer
switchexpressions overswitchstatements where a value is returned - Prefer primary constructors (C# 12) for simple dependency injection
- Prefer file-scoped namespaces (
namespace Foo;) over block-scoped - Prefer
ispattern matching over explicit casts
Dart / Flutter — Language-Specific Review Notes
Load this file alongside rules/universal.md. Universal rules are not repeated here — only Dart and Flutter-specific rules and idioms.
---
PR Analyzer — Dart / Flutter Risk Signals
print()statements left in production code — use a logging package// ignore:lint suppression comments — verify they are justified!null assertion operator used broadly without justification- Hardcoded API keys, tokens, or URLs in Dart source — use environment variables or a secrets package
TODO/FIXMEnear widget lifecycle or state management code
---
Code Quality — Dart Checks
dynamicused where a concrete type is known — defeats static analysis!(null assertion) used broadly — prefer null-safe patternsStatefulWidgetused whereStatelessWidgetsuffices — prefer statelesssetStatecalled with heavy computation inside — offload before callingBuildContextused across async gaps without checkingmounted- Missing
constconstructor on widgets that could be constant
---
Security
- Flag API keys or secrets hardcoded in Dart source or
pubspec.yaml— use--dart-defineor a secrets manager - Flag
httppackage used without certificate validation disabled intentionally - Flag
SharedPreferencesused to store sensitive data — useflutter_secure_storage - Flag user-controlled input used in
dart:iofile path operations without sanitization - Flag
WebViewloading arbitrary user-supplied URLs without validation - Flag deep link / URL scheme handlers that don't validate the incoming URL before acting on it
---
Async / Concurrency
- Flag
BuildContextused after anawaitwithout checkingif (!mounted) return— context may be invalid - Flag
Futurereturned but notawait-ed and without.catchError()orunawaited()— floating future - Flag
Isolate.spawnwithout a clear message-passing protocol - Flag heavy computation on the main isolate — offload with
compute()orIsolate.run() - Flag
StreamControllernot closed when the owning widget is disposed — memory leak - Flag
async*/yield*generators with no error handling on the stream consumer side
---
Resource Management
- Flag
StreamControllernot closed indispose() - Flag
AnimationControllernot disposed indispose() - Flag
TextEditingController/FocusNode/ScrollControllernot disposed indispose() - Flag
Timernot cancelled indispose() - Flag listeners added to
ChangeNotifier/ValueNotifierwithout a correspondingremoveListener
---
Exception Handling
- Flag empty
catchblocks — swallowed errors - Flag
catchErrorwith no handler body — silent failure - Flag
Future.errornot surfaced to the UI — show an error state - Flag
FlutterError.onErroroverridden without calling the original handler - Prefer typed
on ExceptionType catch (e)over genericcatch (e)where the exception type is known
---
Performance
- Flag
setStatecalled for changes that only affect a small subtree — useValueNotifier/provider/Riverpodto scope rebuilds - Flag expensive computation inside
build()— move toinitState, a controller, or aFutureBuilder - Flag
ListViewwithoutListView.builderfor long or infinite lists — builds all children at once - Flag missing
conston widgets that never change — prevents unnecessary rebuilds - Flag
Image.networkwithout a caching package in a list — re-downloads on every scroll - Flag
RepaintBoundarymissing around frequently-repainted widgets (animations, counters)
---
Idioms and Best Practices
Null Safety
- Prefer
?.safe navigation and??null coalescing over!assertions - Use
lateonly when initialization is guaranteed before first access — document why - Prefer early returns over deeply nested null checks
Flutter Widget Patterns
- Prefer
StatelessWidget+ external state management overStatefulWidgetfor business logic - Keep
build()methods pure — no side effects, no heavy computation - Extract repeated widget subtrees into named widget classes, not just methods, for better rebuild granularity
- Use
constconstructors wherever possible — compile-time constant widgets skip rebuilds entirely
State Management
- Do not mix multiple state management approaches in the same feature
- Flag business logic inside
build()— it belongs in a ViewModel, Notifier, or BLoC - Prefer
Riverpod/provider/BLoCover rawsetStatefor anything beyond local UI state
Modern Dart (3.x)
- Prefer
sealedclasses for exhaustive pattern matching on domain types - Use records (
(int, String)) for lightweight multi-value returns instead of ad hoc classes - Use
switchexpressions with pattern matching instead of longif/elsechains - Prefer
finalfor local variables — immutability by default
Go — Language-Specific Review Notes
Load this file alongside rules/universal.md. Universal rules are not repeated here — only Go-specific rules and idioms.
---
PR Analyzer — Go Risk Signals
fmt.Println/log.Printlndebug statements left in production code//nolintcomments — verify they are justifiedunsafepackage imports — require explicit sign-off- Hardcoded credentials or tokens in source
---
Code Quality — Go Checks
- Errors returned but not checked (
_ = someFunc()) panic()used outside of package initialization- Goroutines started without a clear lifetime or cancellation path
interface{}/anyused where a concrete type or typed interface would work- Missing context propagation (
context.Contextnot threaded through call chains)
---
Security
- Flag
database/sqlqueries built withfmt.Sprintf— require?/$Nplaceholders - Flag
os/execcalls with user-controlled arguments without sanitization - Flag
html/templatebypassed in favor oftext/templatefor HTML output - Flag
http.ListenAndServeTLSwithInsecureSkipVerify: true
---
Async / Concurrency
- Flag goroutines started with no clear lifetime or cancellation path — always pass
context.Context - Flag goroutines that write to a channel with no receiver and no
selectdefault — causes a leak - Flag
time.Sleep()used inside a goroutine as a synchronization mechanism - Flag
sync.WaitGroup.Add()called inside the goroutine it tracks — race condition - Flag
sync.Mutexcopied by value — must always be used as a pointer or embedded in a struct
---
Resource Management
- Flag
http.Response.Bodynot closed after reading — even on error paths (defer resp.Body.Close()) - Flag
os.Filenot closed — usedefer f.Close()immediately after opening - Flag
rows.Close()missing aftersql.Query()— leaks the DB connection - Flag
context.WithCancel/context.WithTimeoutcancel function not called — context and resources leak
---
Exception Handling
- Flag errors assigned to
_without a comment explaining why it is safe to ignore - Flag errors not wrapped with
fmt.Errorf("...: %w", err)— loses stack context - Flag
errors.New/fmt.Errorfstrings starting with a capital letter or ending in punctuation — violates Go conventions - Flag
panic()used for expected runtime errors — reserve for programming errors and unrecoverable states - Flag
recover()used to silently swallow panics without logging
---
Performance
- Flag
fmt.Sprintfused for simple string concatenation — usestrings.Builderor+for small cases - Flag
append()in a tight loop without pre-allocating slice capacity — usemake([]T, 0, n) - Flag
json.Marshal/json.Unmarshalon large structs in hot paths — considerjson.Encoder/ streaming - Flag goroutines spawned per-request without a worker pool for CPU-bound tasks
---
Idioms and Best Practices
Error Handling
- All returned errors must be checked — never assign to
_without a comment - Prefer wrapping with
fmt.Errorf("...: %w", err)for stack context - Use
errors.Is/errors.Asfor error inspection — never string comparison
Concurrency
- Every goroutine must have an owner responsible for its lifetime
- Always pass
context.Contextas the first argument to functions that do I/O or block - Prefer
sync.WaitGrouporerrgroupover ad-hoc channel coordination
Modern Go (1.18+)
- Prefer generics over
interface{}for container types and utility functions - Use
any(alias forinterface{}) in new code for readability
Java — Language-Specific Review Notes
Load this file alongside rules/universal.md. Universal rules are not repeated here — only Java-specific rules and idioms.
---
PR Analyzer — Java Risk Signals
System.out.println/e.printStackTrace()left in production code@SuppressWarningsannotations — verify they are justified- Hardcoded JDBC URLs or credentials in source
- Raw type usage (
List,Mapwithout generics)
---
Code Quality — Java Checks
- Empty
catchblocks swallowing exceptions silently - Checked exceptions caught and not re-thrown with context
Closeable/AutoCloseableresources not in try-with-resources- Raw type usage — defeats generics type safety
- Missing
@Overrideon overriding methods InterruptedExceptioncaught without callingThread.currentThread().interrupt()
---
Security
- Flag JPQL / HQL or native SQL string concatenation — require named parameters or
CriteriaBuilder - Flag
@RequestMappingwithout explicit HTTP method restriction on state-changing endpoints - Flag user-controlled input passed to
Runtime.exec()orProcessBuilderwithout validation - Flag
ObjectInputStream.readObject()on untrusted data — unsafe deserialization - Flag hardcoded JDBC URLs or credentials — require environment variables or a vault
---
Async / Concurrency
- Flag
ExecutorService.submit()return value ignored — exceptions are swallowed - Flag
Thread.sleep()used as a synchronization mechanism — useCountDownLatch,CompletableFuture, orawait() - Flag
CompletableFuturechains with no.exceptionally()or.handle()terminal handler - Flag
InterruptedExceptioncaught without callingThread.currentThread().interrupt() - Flag
synchronizedon a non-final field — the lock object can be replaced - Flag
HashMapused in multi-threaded context — useConcurrentHashMap
---
Resource Management
- Flag
InputStream,OutputStream,Connection,ResultSet,PreparedStatementnot wrapped in try-with-resources - Flag manual
finally { resource.close() }— replace with try-with-resources - Flag
HttpURLConnectionnot disconnected after use - Flag JDBC
Connectionobtained from a pool and not returned (missingclose()) on all paths - Flag
staticHttpClientorConnectionfields shared across threads without connection pool management
---
Exception Handling
- Flag empty
catchblocks —catch (Exception e) {} - Flag
InterruptedExceptioncaught withoutThread.currentThread().interrupt()— breaks cooperative cancellation - Flag checked exceptions swallowed in a
catchand not re-thrown or logged with context - Flag
throw new RuntimeException(e)without a descriptive message — loses context - Flag
printStackTrace()as the sole error handling — use a proper logger
---
Performance
- Flag
Stringconcatenation in loops — useStringBuilder - Flag
List.contains()/Map.get()in a loop on large collections — review data structure choice - Flag N+1 JPA / Hibernate queries — use
JOIN FETCHor@BatchSize - Flag
new ObjectMapper()/new Gson()instantiated per-request — share a singleton - Flag
ResultSetfully iterated when only the first result is needed — useLIMIT 1in the query
---
Idioms and Best Practices
Null Safety
- Prefer returning
Optional<T>overnullfrom methods - Flag unchecked dereferences without a prior null guard
- Do not catch
NullPointerException— fix the root cause instead
Collections and Streams
- Flag
==used to compareStringor boxed types — use.equals() - Flag
.collect(Collectors.toList())where.toList()(Java 16+) suffices - Flag premature
.stream().collect()round-trips that could be a single-pass operation
Generics
- Flag raw types in any new code — always parameterize (
List<String>, notList) - Flag unchecked cast warnings suppressed without explanation
Modern Java (11+)
- Prefer
varfor local variables where the type is obvious from the right-hand side - Prefer records for pure data carriers over manual POJOs with getters/setters
- Prefer
instanceofpattern matching (if (obj instanceof String s)) over explicit casts - Prefer
switchexpressions overswitchstatements where a value is returned
Kotlin — Language-Specific Review Notes
Load this file alongside rules/universal.md. Universal rules are not repeated here — only Kotlin-specific rules and idioms.
---
PR Analyzer — Kotlin Risk Signals
println()statements left in production code@Suppressannotations — verify they are justified!!(not-null assertion) used broadly without justification- Hardcoded credentials or API keys in source
---
Code Quality — Kotlin Checks
!!used broadly — prefer?.let,?:, orrequireNotNull()lateinit varaccessed before initialization- Coroutines launched with
GlobalScope— prefer scoped coroutines runBlockingused outside of tests or top-level entry points
---
Security
- Flag Room / SQLite queries built with string concatenation — require parameterized queries
- Flag
WebView.loadUrl()with user-controlled input without validation - Flag credentials stored in
SharedPreferences— requireEncryptedSharedPreferencesor Keychain
---
Async / Coroutines
- Flag
GlobalScope.launch/GlobalScope.asyncin production code — use a structured scope - Flag
runBlockingoutside of tests or top-level main functions - Flag
launch/asyncwithout aCoroutineExceptionHandlerorsupervisorScopewhere individual failures should not cancel siblings - Flag
Dispatchers.Mainused for CPU-bound work — useDispatchers.Default - Flag coroutine cancellation not respected — long loops should check
isActiveor callyield()
---
Resource Management
- Flag
Closeable/AutoCloseablenot wrapped in.use { }(Kotlin's try-with-resources equivalent) - Flag
OkHttpClient/Retrofitinstantiated per-request — share a singleton - Flag
BroadcastReceiverregistered without a correspondingunregisterReceiver— memory / battery leak - Flag coroutines that hold a resource across a
suspendpoint without structured cleanup infinally
---
Exception Handling
- Flag
runCatching { }.getOrNull()used broadly — silently swallows all exceptions - Flag
catch (e: Exception)in coroutines without re-throwingCancellationException— breaks structured concurrency - Flag empty
catchblocks - Flag
throw RuntimeException(e)without a descriptive message - Prefer typed
sealed classerror hierarchies over raw exceptions for domain errors in coroutine flows
---
Performance
- Flag
buildString/StringBuildernot used for multi-step string construction in loops - Flag
Listused for frequentcontainschecks — preferSet - Flag
flow.collect {}re-subscribing on every recomposition in Jetpack Compose — usecollectAsStateWithLifecycle - Flag
Dispatchers.IOused for CPU-bound work — useDispatchers.Default - Flag
suspendfunctions calling non-suspend blocking APIs directly — wrap withwithContext(Dispatchers.IO)
---
Idioms and Best Practices
Null Safety
- Prefer safe call (
?.) and Elvis operator (?:) over!! - Use
requireNotNull()/checkNotNull()with a descriptive message when null means a programming error - Prefer
valovervar— immutability by default
Modern Kotlin
- Prefer
data classfor value carriers - Prefer
sealed class/sealed interfacefor exhaustivewhenexpressions - Prefer extension functions over utility classes
- Prefer
objectdeclarations for singletons
PHP — Language-Specific Review Notes
Load this file alongside rules/universal.md. Universal rules are not repeated here — only PHP-specific rules and idioms.
---
PR Analyzer — PHP Risk Signals
var_dump/print_r/echodebug statements left in production code@error suppression operator — masks real errors; verify it is justified// phpcs:ignore/// phpstan-ignorecomments — verify they are justified- Hardcoded credentials, database passwords, or API keys in source
eval()anywhere — almost always a security issue$_GET/$_POST/$_REQUEST/$_COOKIEused without sanitization
---
Code Quality — PHP Checks
- Missing type declarations on function parameters and return types
mixedreturn type used broadly — tighten to specific types- Global variables (
global $var) — pass dependencies explicitly - Long functions (>50 lines) — PHP functions tend to accumulate logic
isset()/empty()used to mask type errors instead of fixing the root cause- Missing
strict_types=1declaration at the top of the file
---
Security
- Flag
$_GET/$_POST/$_REQUESTused directly in SQL queries — require PDO prepared statements - Flag
mysqli_query($conn, "SELECT ... WHERE id = " . $_GET['id'])— SQL injection - Flag
echo $_GET['name']or any unescaped output — XSS; usehtmlspecialchars()withENT_QUOTES - Flag
include/requirewith user-controlled paths — local/remote file inclusion - Flag
eval()— remote code execution risk; no legitimate use in application code - Flag
shell_exec/exec/system/passthruwith user-controlled input — command injection - Flag
unserialize()on untrusted data — arbitrary object instantiation and code execution - Flag
move_uploaded_filewithout MIME type validation and extension whitelist — file upload attack - Flag
header("Location: " . $_GET['url'])without validation — open redirect - Flag missing CSRF token validation on state-changing form endpoints
---
Async / Concurrency
- Flag long-running synchronous operations in a request cycle — offload to a queue (Laravel Queue, RabbitMQ)
- Flag
sleep()used inside a request handler — blocks the PHP-FPM worker - Flag shared mutable state in
staticproperties accessed across requests in long-running processes (Swoole, RoadRunner) - Flag missing idempotency in queued jobs — jobs can be retried on failure
---
Resource Management
- Flag database connections not closed or returned to the pool (
$pdo = nullor$conn->close()) - Flag
fopen/fwritewithout a matchingfcloseon all paths - Flag
curl_initwithoutcurl_close— leaks the curl handle - Flag unbounded file uploads with no size or type restriction
- Flag sessions not explicitly closed (
session_write_close()) before long operations — session locking blocks other requests
---
Exception Handling
- Flag empty
catchblocks — swallowed exceptions - Flag
catch (Exception $e) {}without logging — silent failure - Flag
die()/exit()used for error handling in library code — use exceptions - Flag
@operator used to suppress errors from functions that can fail — check return values instead - Flag
trigger_errorused in new code — prefer exceptions
---
Performance
- Flag N+1 Eloquent / Doctrine queries — use eager loading (
with(),load(),join) - Flag
count($array)called repeatedly in a loop condition — cache the result - Flag
array_push($arr, $val)— use$arr[] = $valwhich is faster - Flag
in_arrayon large arrays without the strict third argument — useisseton a flipped array for O(1) lookup - Flag
file_get_contentson remote URLs in a request cycle — use an HTTP client with timeout and async where possible - Flag Eloquent
all()without pagination — loads entire table into memory
---
Idioms and Best Practices
Type Safety
- Always declare
declare(strict_types=1)at the top of every file - Use union types (
int|string) and nullable types (?string) rather thanmixed - Use typed properties on classes — avoid untyped
public $foo - Use constructor promotion for simple value objects
Modern PHP (8.x)
- Prefer
matchexpressions overswitch— strict comparison, no fall-through - Use named arguments for functions with many optional parameters
- Use
enumfor fixed sets of values instead of class constants - Use
readonlyproperties for immutable data - Use nullsafe operator (
?->) instead of nestedissetchecks - Use
first-class callable syntax(strlen(...)) instead of string references
Laravel / Symfony Specific
- Keep controllers thin — logic belongs in service classes or action classes
- Use form requests for validation — never validate in the controller directly
- Prefer Eloquent relationships over manual joins for readability
- Flag raw queries where the ORM can express the same intent safely
Python — Language-Specific Review Notes
Load this file alongside rules/universal.md. Universal rules are not repeated here — only Python-specific rules and idioms.
---
PR Analyzer — Python Risk Signals
print()statements left in production code# noqaand# type: ignorecomments — verify they are justifiedeval()/exec()with any user-controlled inputpickleused to deserialize untrusted data- Hardcoded credentials or tokens in source
---
Code Quality — Python Checks
- Bare
except:orexcept Exception:swallowing silently - Mutable default arguments (
def foo(items=[])) — shared across calls import *— pollutes namespace and hides dependencies- Missing type hints on public functions and methods
assertused for runtime validation — stripped by-Oflag
---
Security
- Flag
eval()/exec()with any user-controlled input - Flag
pickle.loads()on untrusted data — usejsonormsgpack - Flag
subprocesscalls withshell=Trueand user input - Flag
flask.render_template_string()with user data (SSTI) - Flag
SECRET_KEY/DEBUG = Truecommitted to source
---
Async
- Flag
asyncio.get_event_loop().run_until_complete()inside an already-running loop - Flag mixing
threadingandasynciowithout a clear bridge (run_in_executor) - Flag CPU-bound work inside an
async defwithout offloading toProcessPoolExecutor - Flag
time.sleep()inside async functions — useawait asyncio.sleep()
---
Resource Management
- Flag
open()not used as a context manager (with open(...) as f) - Flag
requests.Sessioncreated per-request instead of shared/reused - Flag database connections not closed or returned to a pool on all paths
- Flag large files read entirely into memory with
.read()— prefer streaming / chunked reads
---
Exception Handling
- Flag bare
except:— catchesBaseExceptionincludingKeyboardInterruptandSystemExit - Flag
except Exception: pass— silently swallows errors - Flag re-raising with
raise einstead ofraise— loses the original traceback - Flag
exceptclause too broad when thetryblock covers multiple operations with different failure modes — split them
---
Performance
- Flag
+string concatenation in loops — use"".join() - Flag repeated
re.compile()inside a loop — compile once at module level - Flag
list.append()in a loop where a list comprehension would be more efficient - Flag
inmembership tests onlistwhere the collection is large — useset - Flag loading entire large files into memory — prefer streaming or chunked reads
---
Idioms and Best Practices
Type Safety
- All public functions and methods should have type annotations
- Prefer
X | None(Python 3.10+) overOptional[X] - Use
TypedDictordataclassover plaindictfor structured data
Modern Python (3.10+)
- Prefer
matchstatements over longif/elifchains - Prefer
dataclassorNamedTupleover plain classes for data carriers - Prefer
pathlib.Pathoveros.pathfor file operations - Prefer f-strings over
.format()or%formatting
None Safety
- Prefer explicit
if x is Noneover falsy checks when0or""are valid values - Flag functions returning
Noneimplicitly — make it explicit or raise
Ruby — Language-Specific Review Notes
Load this file alongside rules/universal.md. Universal rules are not repeated here — only Ruby-specific rules and idioms.
---
PR Analyzer — Ruby Risk Signals
puts/p/ppdebug statements left in production code# rubocop:disablecomments — verify they are justifiedeval/instance_eval/class_evalwith user-controlled input- Hardcoded credentials, tokens, or
SECRET_KEY_BASEin source binding.pry/byebug/debuggerleft in code
---
Code Quality — Ruby Checks
- Methods longer than 15 lines — Ruby idioms favor very small methods
- Classes with more than 10 public methods — possible god object
rescue Exception— catchesSignalExceptionandSystemExit; userescue StandardErroror more specific typesmethod_missingimplemented withoutrespond_to_missing?- Deeply nested blocks (>3 levels) — extract to methods
- String interpolation used where a symbol would suffice (hash keys, etc.)
---
Security
- Flag
eval/instance_evalwith user-controlled strings — remote code execution - Flag
system()/exec()/ backtick calls with user-controlled input — shell injection - Flag
YAML.loadon untrusted data — useYAML.safe_load - Flag
Marshal.loadon untrusted data — arbitrary code execution - Flag raw SQL string interpolation in ActiveRecord — use parameterized queries (
where("name = ?", name)) - Flag
paramspassed directly toredirect_towithout validation — open redirect - Flag
render inline:with user data — XSS via ERB - Flag missing
strong_parametersin Rails controllers — mass assignment vulnerability
---
Async / Concurrency
- Flag shared mutable state accessed from multiple threads without a
Mutex - Flag
Thread.newwithout storing the thread reference — exceptions are silently swallowed - Flag
sleepused as a synchronization mechanism in threaded code - Flag
@@class_variablesmutated in multi-threaded contexts — not thread-safe - Flag Sidekiq / ActiveJob workers that are not idempotent — jobs can be retried
---
Resource Management
- Flag
File.openwithout a block form — the block form guaranteesclose - Flag database connections or HTTP clients not released in
ensureblocks - Flag
ActiveRecordqueries inside loops — N+1 pattern; useincludes/preload/eager_load - Flag
ObjectSpaceusage in production — memory and performance impact
---
Exception Handling
- Flag
rescue Exception— userescue StandardErroror a specific exception class - Flag empty
rescueblocks — swallowed errors - Flag
rescueused for control flow (e.g. rescuingActiveRecord::RecordNotFoundinstead of usingfind_by) - Flag re-raising with
raise einstead of bareraise— loses the original backtrace - Flag
ensureblocks that can raise — masks the original exception
---
Performance
- Flag N+1 ActiveRecord queries — use
includes,preload, oreager_load - Flag
Array#eachwith string concatenation — usemap+join - Flag
select+mapthat could be a singlefilter_map - Flag
.counton an ActiveRecord relation inside a view or loop — triggers a query each time - Flag
requireinside a method body — constant overhead on every call - Flag
Hash#mergein a loop — usemerge!oreach_with_object
---
Idioms and Best Practices
Ruby Style
- Prefer
map/select/reject/reduceover manualeach+ accumulator - Prefer
&method(:name)over{ |x| some_method(x) }for method reference blocks - Prefer
freezeon string constants to avoid repeated object allocation - Use
attr_reader/attr_writer/attr_accessorinstead of manual getter/setter methods - Prefer
Symbol#to_proc(&:method_name) for simple single-method blocks
Rails-Specific
- Keep controllers thin — logic belongs in service objects, models, or concerns
- Use
before_actionfor authentication/authorization checks — never inline - Prefer
find_byoverwhere(...).first— more intent-revealing - Flag
after_commitcallbacks with side effects that should be in a service object - Prefer
respond_toblocks over separate controller actions for format variants
Modern Ruby (3.x)
- Prefer pattern matching (
case/in) for complex data destructuring - Use numbered block parameters (
_1,_2) only for very short, obvious blocks - Prefer
Data.definefor simple immutable value objects (Ruby 3.2+)
Rust — Language-Specific Review Notes
Load this file alongside rules/universal.md. Universal rules are not repeated here — only Rust-specific rules and idioms.
---
PR Analyzer — Rust Risk Signals
unsafe { }blocks — require explicit justification and sign-off#[allow(...)]attributes suppressing lints — verify they are justified.unwrap()/.expect("")onOptionorResultoutside of tests or prototypes- Hardcoded credentials or tokens in source
TODO/FIXMEcomments nearunsafeor ownership code
---
Code Quality — Rust Checks
.unwrap()used broadly in production code — prefer?,if let, ormatchclone()called excessively — may indicate ownership design issuesArc<Mutex<T>>used where a simpler ownership model would workBox<dyn Trait>used where generics (impl Trait) would avoid heap allocationpubfields on structs that should enforce invariants — use accessor methods
---
Security
- Flag
unsafeblocks accessing raw pointers without clear safety invariant documented in a comment - Flag
std::mem::transmute— almost always a logic error or undefined behavior; require strong justification - Flag
from_utf8_uncheckedon user-controlled data — usefrom_utf8with error handling - Flag
unwrap()on user-supplied input parsing — panics are a denial-of-service vector in server code - Flag hardcoded secrets — use environment variables or a secrets crate
---
Async / Concurrency
- Flag
std::sync::Mutexused in async code — usetokio::sync::Mutexto avoid blocking the async runtime - Flag
.awaitinside astd::sync::MutexGuardscope — holds the lock across an await point, blocking other tasks - Flag
spawnwithout storing theJoinHandle— panics in the spawned task are silently ignored - Flag
Arc<Mutex<T>>cloned excessively — consider message passing via channels instead - Flag blocking I/O calls (
std::fs,std::net) inside async functions — use async equivalents
---
Resource Management
- Flag manual
dropcalled explicitly where the natural scope boundary suffices - Flag
Rc<T>used in multi-threaded code — useArc<T>; the compiler catches this but flag in review for architecture discussion - Flag
VecorStringwith large pre-allocated capacity never trimmed — call.shrink_to_fit()if long-lived - Flag
impl Dropthat can panic — causesabortduring stack unwinding
---
Exception Handling
- Flag
.unwrap()in production code outside of tests — use?to propagate or handle explicitly - Flag
.expect("todo")or.expect("")— messages must explain the invariant that guarantees safety - Flag
panic!used for recoverable errors — useResult<T, E> - Flag
unwrap_or_default()where the default silently masks a real error - Prefer typed error enums (
thiserror) overBox<dyn Error>for library crates - Prefer
anyhowfor application-level error context;thiserrorfor library error types
---
Performance
- Flag
.clone()on large types in hot paths — review whether a reference orCow<T>would work - Flag
format!used only to create aStringfrom a literal — use.to_string()orString::from - Flag
collect::<Vec<_>>()followed immediately by.iter()— chain iterators instead - Flag
Box<T>for small types where stack allocation is fine - Flag
Mutexcontention on a hot path — considerRwLockfor read-heavy workloads or sharding
---
Idioms and Best Practices
Ownership
- Prefer borrowing (
&T,&mut T) over cloning wherever the lifetime allows - Use
Cow<'_, str>for functions that sometimes need to own and sometimes borrow - Prefer
impl Traitin function signatures overBox<dyn Trait>for static dispatch
Error Handling
- Use
?operator to propagate errors — avoid manualmatch Err(e) => return Err(e) - Define domain error types with
thiserrorin libraries; useanyhowin binaries - Never use
.unwrap()in library code — it panics the caller's thread
Modern Rust
- Prefer
if let/while letfor single-variant matches over fullmatch - Prefer
?overunwrapeverywhere errors are recoverable - Use
#[derive(Debug, Clone, PartialEq)]consistently on data types - Prefer
iter()chains over manual loops — they compose and optimize well - Use
clippyand treat its lints as required — flag any#[allow(clippy::...)]in review
Swift — Language-Specific Review Notes
Load this file alongside rules/universal.md. Universal rules are not repeated here — only Swift-specific rules and idioms.
---
PR Analyzer — Swift Risk Signals
print()statements left in production code- Force unwrap (
!) on optionals outside of tests or justified init - Force cast (
as!) without a safe fallback - Hardcoded credentials or API keys in source
---
Code Quality — Swift Checks
- Force unwrap (
!) used broadly — preferguard letorif let try!used outside of guaranteed-safe contexts- Retain cycles in closures — missing
[weak self]or[unowned self] @objc/dynamicused without an Objective-C interop reason
---
Security
- Flag credentials stored in
UserDefaults— require Keychain - Flag
URLSessionrequests over plain HTTP in production - Flag
WKWebViewloading arbitrary user-supplied URLs without validation
---
Async / Concurrency
- Flag
DispatchQueue.main.synccalled from the main thread — deadlock - Flag
@escapingclosures capturingselfstrongly in reference cycles — use[weak self] - Flag mixing
async/awaitandDispatchQueuefor the same operation without clear reasoning - Flag
Task { }(unstructured) where a structuredasync letorTaskGroupwould maintain structure - Flag data races — shared mutable state accessed from multiple tasks without an actor
---
Resource Management
- Flag
URLSessionDataTaskstarted with no cancellation handle stored — cannot be cancelled if the view disappears - Flag
NotificationCenterobservers added without a correspondingremoveObserver— memory leak - Flag
CLLocationManager/AVCaptureSessionnot stopped when the owning view controller is dismissed
---
Exception Handling
- Flag
try!outside of guaranteed-safe contexts (test fixtures, constants) — crashes on failure - Flag
try?discarding errors where the failure mode matters to the caller - Flag error types conforming to
Errorwith no associated values or message — makes debugging hard - Flag throwing functions calling
fatalError()as a fallback — choose one error strategy
---
Performance
- Flag
UIImage(named:)called repeatedly for the same asset without caching - Flag synchronous network calls on the main thread
- Flag
Arrayused for frequent membership tests — preferSet - Flag
Stringinterpolation inside tight loops where a pre-built string would avoid allocations
---
Idioms and Best Practices
Optionals
- Prefer
guard letfor early exit;if letfor local scope - Prefer optional chaining (
?.) over force unwrap - Flag implicitly unwrapped optionals (
var x: String!) outside of@IBOutlet
Memory Management
- Flag closures capturing
selfstrongly in reference cycles — use[weak self] - Prefer
structoverclassfor value semantics unless identity or inheritance is needed - Use
unownedonly when the lifetime is guaranteed — otherwiseweak
Concurrency (Swift 5.5+)
- Prefer
async/awaitover completion handlers in new code - Flag
DispatchQueue.main.asyncwhere@MainActororawait MainActor.runis more appropriate
TypeScript / JavaScript — Language-Specific Review Notes
Load this file alongside rules/universal.md. Universal rules are not repeated here — only TypeScript/JavaScript-specific rules and idioms.
---
PR Analyzer — TypeScript / JavaScript Risk Signals
console.log/debuggerstatements left in production code// eslint-disablecomments — verify they are justifiedanytype annotations — require explicit justification@ts-ignore/@ts-expect-error— verify they are justifiedeval()with any dynamic or user-controlled input- Hardcoded API keys or tokens in source
---
Code Quality — TypeScript / JavaScript Checks
anyused broadly instead of proper typing- Non-null assertion (
!) used without justification vardeclarations — preferconst/let- Missing
awaiton async function calls - Floating promises (no
.catch()and noawait) ==used instead of===
---
Security
- Flag
innerHTML,outerHTML,document.write()with user-controlled data — usetextContentor a sanitizer - Flag
dangerouslySetInnerHTMLin React without a sanitizer - Flag
eval()/new Function()with dynamic input - Flag JWT decoded without signature verification
- Flag missing
httpOnly/secureflags on cookies
---
Async / Promises
- Flag floating promises — async calls not
await-ed and without.catch() - Flag
Promise.all()wherePromise.allSettled()is safer (one failure should not cancel siblings) - Flag
asyncfunctions insideforEach—forEachdoes not await; usefor...oforPromise.all() - Flag unhandled promise rejection (no global
unhandledRejectionhandler in Node.js services)
---
Resource Management
- Flag
fs.createReadStream/fs.createWriteStreamwith nocloseordestroyon error - Flag
EventEmitterlisteners added in a loop without removal — memory leak - Flag
setInterval/setTimeouthandles not cleared when the owning component unmounts or exits - Flag database clients / pools not released after use in Node.js
---
Exception Handling
- Flag
catch (e) {}(empty catch) — swallowed error - Flag
catch (e)whereeis used asanywithout narrowing — type the error properly - Flag
Promiserejection not handled —.catch()ortry/await/catchrequired - Flag re-throwing a new
Errorwithout wrapping the original — loses stack context - Use
Errorsubclasses for domain errors rather than plain strings or object literals
---
Performance
- Flag
Array.prototype.find/filter/mapchained multiple times over the same array — combine into one pass - Flag DOM queries (
document.querySelector) inside loops — cache the result - Flag
JSON.parse/JSON.stringifyin a hot path on large objects — consider streaming or partial parsing - Flag
asyncfunctions called sequentially in a loop wherePromise.all()would parallelize them
---
Idioms and Best Practices
Type Safety (TypeScript)
- Prefer
unknownoveranyfor truly unknown values — forces a type guard before use - Prefer type narrowing (
typeof,instanceof, discriminated unions) over casting - Enable
strictmode intsconfig.json - Prefer
interfacefor object shapes that may be extended;typefor unions and aliases
Modern JavaScript / TypeScript
- Prefer
constby default;letonly when reassignment is needed - Prefer optional chaining (
?.) and nullish coalescing (??) over manual null guards - Prefer
structuredClone()over manual deep-copy patterns - Prefer named exports over default exports for better refactoring support
Null / Undefined Safety
- Distinguish between
null(intentional absence) andundefined(not set) — be consistent - Flag
== nullchecks that accidentally includeundefinedwhen only one is intended
code-reviewer
Code review automation for TypeScript, JavaScript, Python, Go, Swift, Kotlin, C#, .NET, Java, C, C++, Rust, Ruby, PHP, and Dart/Flutter. Analyzes PRs for complexity and risk, checks code quality for SOLID violations and code smells, and generates review reports.
The full skill spec is `SKILL.md`. This README is a quick reference for the 3 bundled scripts.
---
How to use
Quick install check
python scripts/pr_analyzer.py --help
python scripts/code_quality_checker.py --help
python scripts/review_report_generator.py --helpAll three scripts are stdlib-only — no pip install required.
Example 1 — review a pull request
# From inside the repo you want to analyze:
python /path/to/skills/code-reviewer/scripts/pr_analyzer.py . --base main --head HEADOutputs: complexity score (1-10), risk categorization (critical / high / medium / low), prioritized review order, commit-message validation.
Example 2 — score a directory's code quality
python scripts/code_quality_checker.py /path/to/code
# Filter by language
python scripts/code_quality_checker.py /path/to/code --language csharp
# Machine-readable
python scripts/code_quality_checker.py /path/to/code --jsonOutputs: quality score (0-100), letter grade, detected code smells, SOLID violations.
Example 3 — combine into a review report
python scripts/review_report_generator.py /path/to/repo --format markdown --output review.mdOutputs: review verdict (approve / request changes / block), score, prioritized action items.
---
Examples bundled with the skill
| File | Purpose |
|---|---|
| `assets/sample_csharp_smells.cs` | C# file with every C#-specific pattern this skill detects, labelled inline |
| `assets/sample_csharp_clean.cs` | Same code refactored per rules/universal.md + languages/csharp.md |
| `assets/sample_java_smells.java` | Java file with every Java-specific pattern this skill detects, labelled inline |
| `assets/sample_java_clean.java` | Same code refactored per rules/universal.md + languages/java.md |
| `assets/sample_c_smells.c` | C file with every C-specific pattern this skill detects, labelled inline |
| `assets/sample_c_clean.c` | Same code refactored per rules/universal.md + languages/c.md |
| `expected_outputs/*.json` | Expected code_quality_checker.py --json output for each fixture |
Use them as a regression-detection harness:
python scripts/code_quality_checker.py assets/sample_java_smells.java --json > /tmp/check.json
diff /tmp/check.json expected_outputs/sample_java_smells_quality.json
# silence means the detector still behaves as documented---
What it detects
See `SKILL.md` for the full pattern list, severity tiers, and references. Quick summary:
- PR Analyzer (
scripts/pr_analyzer.py): hardcoded secrets / connection strings, SQL injection, debug statements (console.*/System.out/printStackTrace), analyzer suppressions (ESLint / Roslyn /@SuppressWarnings),any/dynamicoveruse, TODO/FIXME,unsafeblocks, null-forgiving!,async void, blocking onTask. - Code Quality Checker (
scripts/code_quality_checker.py): long methods, large files, god classes, deep nesting, too many parameters, high cyclomatic complexity, swallowed exceptions, missingawait, undisposedIDisposable,new HttpClient()in method body, unusedusingdirectives. Language-specific smell packs for C# (async void, blocking onTask), Java (empty catch,printStackTrace, swallowedInterruptedException, unclosed resources, per-callObjectMapper/Gson), and C (banned functionsgets/strcpy/strcat/sprintf/vsprintf, format-string vulnerabilityprintf(var), unboundedscanf("%s"), malloc-without-NULL-check, free-without-zeroing,system()with non-literal argument). - Review Report Generator (
scripts/review_report_generator.py): combines the above into a single markdown or JSON verdict.
---
Review rules
Rules are split so every review loads exactly two files — the cross-language baseline plus one language guide (see the dispatch table in `SKILL.md`):
- `rules/universal.md` — cross-language rules: security, async/concurrency, resource management, exception handling, performance
- `languages/` — one self-contained guide per language (
python,typescript,go,swift,kotlin,csharp,java,c,cpp,rust,ruby,php,dart), each with Security / Async / Resource Management / Exception Handling / Performance / Idioms sections
Universal Rules — All Languages
These rules apply regardless of language. Load this file for every review, alongside the relevant languages/*.md file.
---
Security
- Flag any string interpolation or concatenation used to build SQL, shell, or LDAP queries — require parameterized queries or a safe API
- Flag hardcoded credentials, API keys, tokens, or secrets anywhere in source — require environment variables or a secrets manager
- Flag user-controlled input passed to file system, process execution, or URL redirect APIs without validation
- Flag overly broad CORS or CSP policies
---
Async / Concurrency
- Flag shared mutable state accessed from multiple threads/coroutines/tasks without synchronization
- Flag fire-and-forget async operations with no error handling path
- Flag timeouts missing on any network or I/O call
- Flag unbounded queues or thread pools with no backpressure mechanism
---
Resource Management
- Flag any resource (file, socket, DB connection, HTTP connection) acquired without a guaranteed release path
- Flag connection pools not returned to the pool on all code paths (including exceptions)
- Flag unbounded collections that grow without eviction — potential memory leak
- Flag resources held open longer than the operation they serve
---
Exception Handling
- Flag empty catch/except blocks — swallowed exceptions hide bugs silently
- Flag catching the broadest possible exception type (
Exception,Throwable,error) where a specific type is appropriate - Flag exceptions used for normal control flow (signaling "not found", etc.) — use return values or
Optional - Flag error context lost when re-throwing — always wrap with the original cause
---
Performance
- Flag N+1 query patterns — loading a collection then querying for each item individually
- Flag unbounded queries or API calls with no pagination or limit
- Flag synchronous I/O on a thread or event loop that serves concurrent requests
- Flag large objects serialized/deserialized repeatedly when they could be cached
- Flag string concatenation in tight loops — use a builder or join
#!/usr/bin/env python3
"""
PR Analyzer
Analyzes pull request changes for review complexity, risk assessment,
and generates review priorities.
Usage:
python pr_analyzer.py /path/to/repo
python pr_analyzer.py . --base main --head feature-branch
python pr_analyzer.py /path/to/repo --json
"""
import argparse
import json
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import Dict, List, Optional, Tuple
# File categories for review prioritization
FILE_CATEGORIES = {
"critical": {
"patterns": [
r"auth", r"security", r"password", r"token", r"secret",
r"payment", r"billing", r"crypto", r"encrypt"
],
"weight": 5,
"description": "Security-sensitive files requiring careful review"
},
"high": {
"patterns": [
r"api", r"database", r"migration", r"schema", r"model",
r"config", r"env", r"middleware"
],
"weight": 4,
"description": "Core infrastructure files"
},
"medium": {
"patterns": [
r"service", r"controller", r"handler", r"util", r"helper"
],
"weight": 3,
"description": "Business logic files"
},
"low": {
"patterns": [
r"test", r"spec", r"mock", r"fixture", r"story",
r"readme", r"docs", r"\.md$"
],
"weight": 1,
"description": "Tests and documentation"
}
}
# Risky patterns to flag
RISK_PATTERNS = [
{
"name": "hardcoded_secrets",
"pattern": r"(password|secret|api_key|token|connection_?string)\s*[=:]\s*['\"][^'\"]+['\"]",
"severity": "critical",
"message": "Potential hardcoded secret or connection string detected"
},
{
"name": "todo_fixme",
"pattern": r"(TODO|FIXME|HACK|XXX):",
"severity": "low",
"message": "TODO/FIXME comment found"
},
{
"name": "console_log",
"pattern": (
r"console\.(log|debug|info|warn|error)\(|\bDebug\.WriteLine\(|"
r"\bSystem\.out\.print(?:ln)?\(|\.printStackTrace\("
),
"severity": "medium",
"message": (
"Debug output statement found "
"(console.* / Debug.WriteLine / System.out / printStackTrace)"
)
},
{
"name": "debugger",
"pattern": r"\bdebugger\b",
"severity": "high",
"message": "Debugger statement found"
},
{
"name": "analyzer_disable",
"pattern": (
r"eslint-disable|#pragma\s+warning\s+disable|\[SuppressMessage|"
r"@SuppressWarnings"
),
"severity": "medium",
"message": (
"Static-analyzer rule disabled "
"(ESLint / Roslyn / SuppressMessage / @SuppressWarnings)"
)
},
{
"name": "loose_type",
"pattern": r":\s*any\b|\bdynamic\s+\w+\s*[=;]",
"severity": "medium",
"message": "Loose type used (TypeScript 'any' or C# 'dynamic')"
},
{
"name": "sql_concatenation",
"pattern": r"(SELECT|INSERT|UPDATE|DELETE).*\+.*['\"]|(?:FromSql|ExecuteSql)\w*\([^)]*\$\"",
"severity": "critical",
"message": "Potential SQL injection (string concatenation or interpolation in query)"
},
{
"name": "csharp_unsafe_block",
"pattern": (
r"\bunsafe\s+(?:\{|public|private|protected|internal|static|sealed|"
r"partial|class|struct|void|int|string|long|short|byte|double|float|"
r"bool|char|ref|out|fixed)\b"
),
"severity": "high",
"message": "C# 'unsafe' code — requires memory-safety review"
},
{
"name": "csharp_null_forgiving",
"pattern": r"(?:\)\s*!\.|\w+!\.\w+)",
"severity": "medium",
"message": "Null-forgiving operator (!) used — verify the value is truly non-null"
},
{
"name": "csharp_async_void",
"pattern": r"\basync\s+void\s+\w+\s*\(",
"severity": "high",
"message": "'async void' method — use only for event handlers"
},
{
"name": "csharp_blocking_async",
"pattern": r"\.(?:Result\b|Wait\(\)|GetAwaiter\(\)\.GetResult\(\))",
"severity": "high",
"message": "Blocking call on async operation — can deadlock in ASP.NET contexts"
}
]
def run_git_command(cmd: List[str], cwd: Path) -> Tuple[bool, str]:
"""Run a git command and return success status and output."""
try:
result = subprocess.run(
cmd,
cwd=cwd,
capture_output=True,
text=True,
timeout=30
)
return result.returncode == 0, result.stdout.strip()
except subprocess.TimeoutExpired:
return False, "Command timed out"
except Exception as e:
return False, str(e)
def get_changed_files(repo_path: Path, base: str, head: str) -> List[Dict]:
"""Get list of changed files between two refs."""
success, output = run_git_command(
["git", "diff", "--name-status", f"{base}...{head}"],
repo_path
)
if not success:
# Try without the triple dot (for uncommitted changes)
success, output = run_git_command(
["git", "diff", "--name-status", base, head],
repo_path
)
if not success or not output:
# Fall back to staged changes
success, output = run_git_command(
["git", "diff", "--name-status", "--cached"],
repo_path
)
files = []
for line in output.split("\n"):
if not line.strip():
continue
parts = line.split("\t")
if len(parts) >= 2:
status = parts[0][0] # First character of status
filepath = parts[-1] # Handle renames (R100\told\tnew)
status_map = {
"A": "added",
"M": "modified",
"D": "deleted",
"R": "renamed",
"C": "copied"
}
files.append({
"path": filepath,
"status": status_map.get(status, "modified")
})
return files
def get_file_diff(repo_path: Path, filepath: str, base: str, head: str) -> str:
"""Get diff content for a specific file."""
success, output = run_git_command(
["git", "diff", f"{base}...{head}", "--", filepath],
repo_path
)
if not success:
success, output = run_git_command(
["git", "diff", "--cached", "--", filepath],
repo_path
)
return output if success else ""
def categorize_file(filepath: str) -> Tuple[str, int]:
"""Categorize a file based on its path and name."""
filepath_lower = filepath.lower()
for category, info in FILE_CATEGORIES.items():
for pattern in info["patterns"]:
if re.search(pattern, filepath_lower):
return category, info["weight"]
return "medium", 2 # Default category
def analyze_diff_for_risks(diff_content: str, filepath: str) -> List[Dict]:
"""Analyze diff content for risky patterns."""
risks = []
# Only analyze added lines (starting with +)
added_lines = [
line[1:] for line in diff_content.split("\n")
if line.startswith("+") and not line.startswith("+++")
]
content = "\n".join(added_lines)
for risk in RISK_PATTERNS:
matches = re.findall(risk["pattern"], content, re.IGNORECASE)
if matches:
risks.append({
"name": risk["name"],
"severity": risk["severity"],
"message": risk["message"],
"file": filepath,
"count": len(matches)
})
return risks
def count_changes(diff_content: str) -> Dict[str, int]:
"""Count additions and deletions in diff."""
additions = 0
deletions = 0
for line in diff_content.split("\n"):
if line.startswith("+") and not line.startswith("+++"):
additions += 1
elif line.startswith("-") and not line.startswith("---"):
deletions += 1
return {"additions": additions, "deletions": deletions}
def calculate_complexity_score(files: List[Dict], all_risks: List[Dict]) -> int:
"""Calculate overall PR complexity score (1-10)."""
score = 0
# File count contribution (max 3 points)
file_count = len(files)
if file_count > 20:
score += 3
elif file_count > 10:
score += 2
elif file_count > 5:
score += 1
# Total changes contribution (max 3 points)
total_changes = sum(f.get("additions", 0) + f.get("deletions", 0) for f in files)
if total_changes > 500:
score += 3
elif total_changes > 200:
score += 2
elif total_changes > 50:
score += 1
# Risk severity contribution (max 4 points)
critical_risks = sum(1 for r in all_risks if r["severity"] == "critical")
high_risks = sum(1 for r in all_risks if r["severity"] == "high")
score += min(2, critical_risks)
score += min(2, high_risks)
return min(10, max(1, score))
def analyze_commit_messages(repo_path: Path, base: str, head: str) -> Dict:
"""Analyze commit messages in the PR."""
success, output = run_git_command(
["git", "log", "--oneline", f"{base}...{head}"],
repo_path
)
if not success or not output:
return {"commits": 0, "issues": []}
commits = output.strip().split("\n")
issues = []
for commit in commits:
if len(commit) < 10:
continue
# Check for conventional commit format
message = commit[8:] if len(commit) > 8 else commit # Skip hash
if not re.match(r"^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?:", message):
issues.append({
"commit": commit[:7],
"issue": "Does not follow conventional commit format"
})
if len(message) > 72:
issues.append({
"commit": commit[:7],
"issue": "Commit message exceeds 72 characters"
})
return {
"commits": len(commits),
"issues": issues
}
def analyze_pr(
repo_path: Path,
base: str = "main",
head: str = "HEAD"
) -> Dict:
"""Perform complete PR analysis."""
# Get changed files
changed_files = get_changed_files(repo_path, base, head)
if not changed_files:
return {
"status": "no_changes",
"message": "No changes detected between branches"
}
# Analyze each file
all_risks = []
file_analyses = []
for file_info in changed_files:
filepath = file_info["path"]
category, weight = categorize_file(filepath)
# Get diff for the file
diff = get_file_diff(repo_path, filepath, base, head)
changes = count_changes(diff)
risks = analyze_diff_for_risks(diff, filepath)
all_risks.extend(risks)
file_analyses.append({
"path": filepath,
"status": file_info["status"],
"category": category,
"priority_weight": weight,
"additions": changes["additions"],
"deletions": changes["deletions"],
"risks": risks
})
# Sort by priority (highest first)
file_analyses.sort(key=lambda x: (-x["priority_weight"], x["path"]))
# Analyze commits
commit_analysis = analyze_commit_messages(repo_path, base, head)
# Calculate metrics
complexity = calculate_complexity_score(file_analyses, all_risks)
total_additions = sum(f["additions"] for f in file_analyses)
total_deletions = sum(f["deletions"] for f in file_analyses)
return {
"status": "analyzed",
"summary": {
"files_changed": len(file_analyses),
"total_additions": total_additions,
"total_deletions": total_deletions,
"complexity_score": complexity,
"complexity_label": get_complexity_label(complexity),
"commits": commit_analysis["commits"]
},
"risks": {
"critical": [r for r in all_risks if r["severity"] == "critical"],
"high": [r for r in all_risks if r["severity"] == "high"],
"medium": [r for r in all_risks if r["severity"] == "medium"],
"low": [r for r in all_risks if r["severity"] == "low"]
},
"files": file_analyses,
"commit_issues": commit_analysis["issues"],
"review_order": [f["path"] for f in file_analyses[:10]] # Top 10 priority files
}
def get_complexity_label(score: int) -> str:
"""Get human-readable complexity label."""
if score <= 2:
return "Simple"
elif score <= 4:
return "Moderate"
elif score <= 6:
return "Complex"
elif score <= 8:
return "Very Complex"
else:
return "Critical"
def print_report(analysis: Dict) -> None:
"""Print human-readable analysis report."""
if analysis["status"] == "no_changes":
print("No changes detected.")
return
summary = analysis["summary"]
risks = analysis["risks"]
print("=" * 60)
print("PR ANALYSIS REPORT")
print("=" * 60)
print(f"\nComplexity: {summary['complexity_score']}/10 ({summary['complexity_label']})")
print(f"Files Changed: {summary['files_changed']}")
print(f"Lines: +{summary['total_additions']} / -{summary['total_deletions']}")
print(f"Commits: {summary['commits']}")
# Risk summary
print("\n--- RISK SUMMARY ---")
print(f"Critical: {len(risks['critical'])}")
print(f"High: {len(risks['high'])}")
print(f"Medium: {len(risks['medium'])}")
print(f"Low: {len(risks['low'])}")
# Critical and high risks details
if risks["critical"]:
print("\n--- CRITICAL RISKS ---")
for risk in risks["critical"]:
print(f" [{risk['file']}] {risk['message']} (x{risk['count']})")
if risks["high"]:
print("\n--- HIGH RISKS ---")
for risk in risks["high"]:
print(f" [{risk['file']}] {risk['message']} (x{risk['count']})")
# Commit message issues
if analysis["commit_issues"]:
print("\n--- COMMIT MESSAGE ISSUES ---")
for issue in analysis["commit_issues"][:5]:
print(f" {issue['commit']}: {issue['issue']}")
# Review order
print("\n--- SUGGESTED REVIEW ORDER ---")
for i, filepath in enumerate(analysis["review_order"], 1):
file_info = next(f for f in analysis["files"] if f["path"] == filepath)
print(f" {i}. [{file_info['category'].upper()}] {filepath}")
print("\n" + "=" * 60)
def main():
parser = argparse.ArgumentParser(
description="Analyze pull request for review complexity and risks"
)
parser.add_argument(
"repo_path",
nargs="?",
default=".",
help="Path to git repository (default: current directory)"
)
parser.add_argument(
"--base", "-b",
default="main",
help="Base branch for comparison (default: main)"
)
parser.add_argument(
"--head",
default="HEAD",
help="Head branch/commit for comparison (default: HEAD)"
)
parser.add_argument(
"--json",
action="store_true",
help="Output in JSON format"
)
parser.add_argument(
"--output", "-o",
help="Write output to file"
)
args = parser.parse_args()
repo_path = Path(args.repo_path).resolve()
if not (repo_path / ".git").exists():
print(f"Error: {repo_path} is not a git repository", file=sys.stderr)
sys.exit(1)
analysis = analyze_pr(repo_path, args.base, args.head)
if args.json:
output = json.dumps(analysis, indent=2)
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"Results written to {args.output}")
else:
print(output)
else:
print_report(analysis)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Review Report Generator
Generates comprehensive code review reports by combining PR analysis
and code quality findings into structured, actionable reports.
Usage:
python review_report_generator.py /path/to/repo
python review_report_generator.py . --pr-analysis pr_results.json --quality-analysis quality_results.json
python review_report_generator.py /path/to/repo --format markdown --output review.md
"""
import argparse
import json
import os
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple
# Severity weights for prioritization
SEVERITY_WEIGHTS = {
"critical": 100,
"high": 75,
"medium": 50,
"low": 25,
"info": 10
}
# Review verdict thresholds
VERDICT_THRESHOLDS = {
"approve": {"max_critical": 0, "max_high": 0, "max_score": 100},
"approve_with_suggestions": {"max_critical": 0, "max_high": 2, "max_score": 85},
"request_changes": {"max_critical": 0, "max_high": 5, "max_score": 70},
"block": {"max_critical": float("inf"), "max_high": float("inf"), "max_score": 0}
}
def load_json_file(filepath: str) -> Optional[Dict]:
"""Load JSON file if it exists."""
try:
with open(filepath, "r") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return None
def run_pr_analyzer(repo_path: Path) -> Dict:
"""Run pr_analyzer.py and return results."""
script_path = Path(__file__).parent / "pr_analyzer.py"
if not script_path.exists():
return {"status": "error", "message": "pr_analyzer.py not found"}
try:
result = subprocess.run(
[sys.executable, str(script_path), str(repo_path), "--json"],
capture_output=True,
text=True,
timeout=120
)
if result.returncode == 0:
return json.loads(result.stdout)
return {"status": "error", "message": result.stderr}
except Exception as e:
return {"status": "error", "message": str(e)}
def run_quality_checker(repo_path: Path) -> Dict:
"""Run code_quality_checker.py and return results."""
script_path = Path(__file__).parent / "code_quality_checker.py"
if not script_path.exists():
return {"status": "error", "message": "code_quality_checker.py not found"}
try:
result = subprocess.run(
[sys.executable, str(script_path), str(repo_path), "--json"],
capture_output=True,
text=True,
timeout=300
)
if result.returncode == 0:
return json.loads(result.stdout)
return {"status": "error", "message": result.stderr}
except Exception as e:
return {"status": "error", "message": str(e)}
def calculate_review_score(pr_analysis: Dict, quality_analysis: Dict) -> int:
"""Calculate overall review score (0-100)."""
score = 100
# Deduct for PR risks
if "risks" in pr_analysis:
risks = pr_analysis["risks"]
score -= len(risks.get("critical", [])) * 15
score -= len(risks.get("high", [])) * 10
score -= len(risks.get("medium", [])) * 5
score -= len(risks.get("low", [])) * 2
# Deduct for code quality issues
if "issues" in quality_analysis:
issues = quality_analysis["issues"]
score -= len([i for i in issues if i.get("severity") == "critical"]) * 12
score -= len([i for i in issues if i.get("severity") == "high"]) * 8
score -= len([i for i in issues if i.get("severity") == "medium"]) * 4
score -= len([i for i in issues if i.get("severity") == "low"]) * 1
# Deduct for complexity
if "summary" in pr_analysis:
complexity = pr_analysis["summary"].get("complexity_score", 0)
if complexity > 7:
score -= 10
elif complexity > 5:
score -= 5
return max(0, min(100, score))
def determine_verdict(score: int, critical_count: int, high_count: int) -> Tuple[str, str]:
"""Determine review verdict based on score and issue counts."""
if critical_count > 0:
return "block", "Critical issues must be resolved before merge"
if score >= 90 and high_count == 0:
return "approve", "Code meets quality standards"
if score >= 75 and high_count <= 2:
return "approve_with_suggestions", "Minor improvements recommended"
if score >= 50:
return "request_changes", "Several issues need to be addressed"
return "block", "Significant issues prevent approval"
def generate_findings_list(pr_analysis: Dict, quality_analysis: Dict) -> List[Dict]:
"""Combine and prioritize all findings."""
findings = []
# Add PR risk findings
if "risks" in pr_analysis:
for severity, items in pr_analysis["risks"].items():
for item in items:
findings.append({
"source": "pr_analysis",
"severity": severity,
"category": item.get("name", "unknown"),
"message": item.get("message", ""),
"file": item.get("file", ""),
"count": item.get("count", 1)
})
# Add code quality findings
if "issues" in quality_analysis:
for issue in quality_analysis["issues"]:
findings.append({
"source": "quality_analysis",
"severity": issue.get("severity", "medium"),
"category": issue.get("type", "unknown"),
"message": issue.get("message", ""),
"file": issue.get("file", ""),
"line": issue.get("line", 0)
})
# Sort by severity weight
findings.sort(
key=lambda x: -SEVERITY_WEIGHTS.get(x["severity"], 0)
)
return findings
def generate_action_items(findings: List[Dict]) -> List[Dict]:
"""Generate prioritized action items from findings."""
action_items = []
seen_categories = set()
for finding in findings:
category = finding["category"]
severity = finding["severity"]
# Group similar issues
if category in seen_categories and severity not in ["critical", "high"]:
continue
action = {
"priority": "P0" if severity == "critical" else "P1" if severity == "high" else "P2",
"action": get_action_for_category(category, finding),
"severity": severity,
"files_affected": [finding["file"]] if finding.get("file") else []
}
action_items.append(action)
seen_categories.add(category)
return action_items[:15] # Top 15 actions
def get_action_for_category(category: str, finding: Dict) -> str:
"""Get actionable recommendation for issue category."""
actions = {
"hardcoded_secrets": "Remove hardcoded credentials and use environment variables or a secrets manager",
"sql_concatenation": "Use parameterized queries to prevent SQL injection",
"debugger": "Remove debugger statements before merging",
"console_log": "Remove or replace console statements with proper logging",
"todo_fixme": "Address TODO/FIXME comments or create tracking issues",
"disable_eslint": "Address the underlying issue instead of disabling lint rules",
"any_type": "Replace 'any' types with proper type definitions",
"long_function": "Break down function into smaller, focused units",
"god_class": "Split class into smaller, single-responsibility classes",
"too_many_params": "Use parameter objects or builder pattern",
"deep_nesting": "Refactor using early returns, guard clauses, or extraction",
"high_complexity": "Reduce cyclomatic complexity through refactoring",
"missing_error_handling": "Add proper error handling and recovery logic",
"duplicate_code": "Extract duplicate code into shared functions",
"magic_numbers": "Replace magic numbers with named constants",
"large_file": "Consider splitting into multiple smaller modules"
}
return actions.get(category, f"Review and address: {finding.get('message', category)}")
def format_markdown_report(report: Dict) -> str:
"""Generate markdown-formatted report."""
lines = []
# Header
lines.append("# Code Review Report")
lines.append("")
lines.append(f"**Generated:** {report['metadata']['generated_at']}")
lines.append(f"**Repository:** {report['metadata']['repository']}")
lines.append("")
# Executive Summary
lines.append("## Executive Summary")
lines.append("")
summary = report["summary"]
verdict = summary["verdict"]
verdict_emoji = {
"approve": "✅",
"approve_with_suggestions": "✅",
"request_changes": "⚠️",
"block": "❌"
}.get(verdict, "❓")
lines.append(f"**Verdict:** {verdict_emoji} {verdict.upper().replace('_', ' ')}")
lines.append(f"**Score:** {summary['score']}/100")
lines.append(f"**Rationale:** {summary['rationale']}")
lines.append("")
# Issue Counts
lines.append("### Issue Summary")
lines.append("")
lines.append("| Severity | Count |")
lines.append("|----------|-------|")
for severity in ["critical", "high", "medium", "low"]:
count = summary["issue_counts"].get(severity, 0)
lines.append(f"| {severity.capitalize()} | {count} |")
lines.append("")
# PR Statistics (if available)
if "pr_summary" in report:
pr = report["pr_summary"]
lines.append("### Change Statistics")
lines.append("")
lines.append(f"- **Files Changed:** {pr.get('files_changed', 'N/A')}")
lines.append(f"- **Lines Added:** +{pr.get('total_additions', 0)}")
lines.append(f"- **Lines Removed:** -{pr.get('total_deletions', 0)}")
lines.append(f"- **Complexity:** {pr.get('complexity_label', 'N/A')}")
lines.append("")
# Action Items
if report.get("action_items"):
lines.append("## Action Items")
lines.append("")
for i, item in enumerate(report["action_items"], 1):
priority = item["priority"]
emoji = "🔴" if priority == "P0" else "🟠" if priority == "P1" else "🟡"
lines.append(f"{i}. {emoji} **[{priority}]** {item['action']}")
if item.get("files_affected"):
lines.append(f" - Files: {', '.join(item['files_affected'][:3])}")
lines.append("")
# Critical Findings
critical_findings = [f for f in report.get("findings", []) if f["severity"] == "critical"]
if critical_findings:
lines.append("## Critical Issues (Must Fix)")
lines.append("")
for finding in critical_findings:
lines.append(f"- **{finding['category']}** in `{finding.get('file', 'unknown')}`")
lines.append(f" - {finding['message']}")
lines.append("")
# High Priority Findings
high_findings = [f for f in report.get("findings", []) if f["severity"] == "high"]
if high_findings:
lines.append("## High Priority Issues")
lines.append("")
for finding in high_findings[:10]:
lines.append(f"- **{finding['category']}** in `{finding.get('file', 'unknown')}`")
lines.append(f" - {finding['message']}")
lines.append("")
# Review Order (if available)
if "review_order" in report:
lines.append("## Suggested Review Order")
lines.append("")
for i, filepath in enumerate(report["review_order"][:10], 1):
lines.append(f"{i}. `{filepath}`")
lines.append("")
# Footer
lines.append("---")
lines.append("*Generated by Code Reviewer*")
return "\n".join(lines)
def format_text_report(report: Dict) -> str:
"""Generate plain text report."""
lines = []
lines.append("=" * 60)
lines.append("CODE REVIEW REPORT")
lines.append("=" * 60)
lines.append("")
lines.append(f"Generated: {report['metadata']['generated_at']}")
lines.append(f"Repository: {report['metadata']['repository']}")
lines.append("")
summary = report["summary"]
verdict = summary["verdict"].upper().replace("_", " ")
lines.append(f"VERDICT: {verdict}")
lines.append(f"SCORE: {summary['score']}/100")
lines.append(f"RATIONALE: {summary['rationale']}")
lines.append("")
lines.append("--- ISSUE SUMMARY ---")
for severity in ["critical", "high", "medium", "low"]:
count = summary["issue_counts"].get(severity, 0)
lines.append(f" {severity.capitalize()}: {count}")
lines.append("")
if report.get("action_items"):
lines.append("--- ACTION ITEMS ---")
for i, item in enumerate(report["action_items"][:10], 1):
lines.append(f" {i}. [{item['priority']}] {item['action']}")
lines.append("")
critical = [f for f in report.get("findings", []) if f["severity"] == "critical"]
if critical:
lines.append("--- CRITICAL ISSUES ---")
for f in critical:
lines.append(f" [{f.get('file', 'unknown')}] {f['message']}")
lines.append("")
lines.append("=" * 60)
return "\n".join(lines)
def generate_report(
repo_path: Path,
pr_analysis: Optional[Dict] = None,
quality_analysis: Optional[Dict] = None
) -> Dict:
"""Generate comprehensive review report."""
# Run analyses if not provided
if pr_analysis is None:
pr_analysis = run_pr_analyzer(repo_path)
if quality_analysis is None:
quality_analysis = run_quality_checker(repo_path)
# Generate findings
findings = generate_findings_list(pr_analysis, quality_analysis)
# Count issues by severity
issue_counts = {
"critical": len([f for f in findings if f["severity"] == "critical"]),
"high": len([f for f in findings if f["severity"] == "high"]),
"medium": len([f for f in findings if f["severity"] == "medium"]),
"low": len([f for f in findings if f["severity"] == "low"])
}
# Calculate score and verdict
score = calculate_review_score(pr_analysis, quality_analysis)
verdict, rationale = determine_verdict(
score,
issue_counts["critical"],
issue_counts["high"]
)
# Generate action items
action_items = generate_action_items(findings)
# Build report
report = {
"metadata": {
"generated_at": datetime.now().isoformat(),
"repository": str(repo_path),
"version": "1.0.0"
},
"summary": {
"score": score,
"verdict": verdict,
"rationale": rationale,
"issue_counts": issue_counts
},
"findings": findings,
"action_items": action_items
}
# Add PR summary if available
if pr_analysis.get("status") == "analyzed":
report["pr_summary"] = pr_analysis.get("summary", {})
report["review_order"] = pr_analysis.get("review_order", [])
# Add quality summary if available
if quality_analysis.get("status") == "analyzed":
report["quality_summary"] = quality_analysis.get("summary", {})
return report
def main():
parser = argparse.ArgumentParser(
description="Generate comprehensive code review reports"
)
parser.add_argument(
"repo_path",
nargs="?",
default=".",
help="Path to repository (default: current directory)"
)
parser.add_argument(
"--pr-analysis",
help="Path to pre-computed PR analysis JSON"
)
parser.add_argument(
"--quality-analysis",
help="Path to pre-computed quality analysis JSON"
)
parser.add_argument(
"--format", "-f",
choices=["text", "markdown", "json"],
default="text",
help="Output format (default: text)"
)
parser.add_argument(
"--output", "-o",
help="Write output to file"
)
parser.add_argument(
"--json",
action="store_true",
help="Output as JSON (shortcut for --format json)"
)
args = parser.parse_args()
repo_path = Path(args.repo_path).resolve()
if not repo_path.exists():
print(f"Error: Path does not exist: {repo_path}", file=sys.stderr)
sys.exit(1)
# Load pre-computed analyses if provided
pr_analysis = None
quality_analysis = None
if args.pr_analysis:
pr_analysis = load_json_file(args.pr_analysis)
if not pr_analysis:
print(f"Warning: Could not load PR analysis from {args.pr_analysis}")
if args.quality_analysis:
quality_analysis = load_json_file(args.quality_analysis)
if not quality_analysis:
print(f"Warning: Could not load quality analysis from {args.quality_analysis}")
# Generate report
report = generate_report(repo_path, pr_analysis, quality_analysis)
# Format output
output_format = "json" if args.json else args.format
if output_format == "json":
output = json.dumps(report, indent=2)
elif output_format == "markdown":
output = format_markdown_report(report)
else:
output = format_text_report(report)
# Write or print output
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"Report written to {args.output}")
else:
print(output)
if __name__ == "__main__":
main()
Related skills
How it compares
Choose code-reviewer for structured pre-merge quality and security review when you need rule-guided feedback rather than a single-language linter config alone.
FAQ
What rule sources does code-reviewer use?
code-reviewer applies rules from universal.md and language-specific files such as languages/c.md. Reviews target security, correctness, and maintainability issues with concrete pattern replacements.
Does code-reviewer handle C security smells?
code-reviewer flags unsafe C patterns like unbounded string operations and recommends bounds-aware APIs such as fgets, strncpy, strncat, and snprintf, as shown in the sample_c_clean.c refactor.
Is Code Reviewer safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.