
Code Review Facilitator
- 72 installs
- 19 repo stars
- Updated May 26, 2026
- wedsamuel1230/arduino-skills
Helps with ai & agent building tasks.
About
code-review-facilitator is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- code-review-facilitator
- AI & Agent Building
- AI-coding skill
Code Review Facilitator by the numbers
- 72 all-time installs (skills.sh)
- +4 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #5,635 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wedsamuel1230/arduino-skills --skill code-review-facilitatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 72 |
|---|---|
| repo stars | ★ 19 |
| Last updated | May 26, 2026 |
| Repository | wedsamuel1230/arduino-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Code Review Facilitator
Provides systematic code review for microcontroller projects.
Resources
This skill includes bundled tools:
- scripts/analyze_code.py - Static analyzer detecting 15+ common Arduino issues
Quick Start
Analyze a file:
uv run --no-project scripts/analyze_code.py sketch.inoAnalyze entire project:
uv run --no-project scripts/analyze_code.py --dir /path/to/projectInteractive mode (paste code):
uv run --no-project scripts/analyze_code.py --interactiveFilter by severity:
uv run --no-project scripts/analyze_code.py sketch.ino --severity warningWhen to Use
- "Review my code"
- "Is this code okay?"
- "How can I improve this?"
- Before publishing to GitHub
- After completing a feature
- When code "works but feels wrong"
---
Review Categories
1. 🏗️ Structure & Organization
Check For:
□ Single responsibility - each function does ONE thing
□ File organization - separate concerns (config, sensors, display, network)
□ Consistent naming convention (camelCase for variables, UPPER_CASE for constants)
□ Reasonable function length (< 50 lines ideally)
□ Header comments explaining purposeCommon Issues:
| Issue | Bad | Good |
|---|---|---|
| God function | 200-line loop() | Split into readSensors(), updateDisplay(), etc. |
| Mixed concerns | WiFi code in sensor file | Separate network.cpp/h |
| Unclear names | int x, temp1, val; | int sensorReading, temperatureC; |
Example Refactoring:
// ❌ Bad: Everything in loop()
void loop() {
// 50 lines of sensor reading
// 30 lines of display update
// 40 lines of network code
}
// ✅ Good: Organized functions
void loop() {
SensorData data = readAllSensors();
updateDisplay(data);
if (shouldTransmit()) {
sendToServer(data);
}
handleSleep();
}---
2. 💾 Memory Safety
Critical Checks:
□ No String class in time-critical code (use char arrays)
□ Buffer sizes declared as constants
□ Array bounds checking
□ No dynamic memory allocation in loop()
□ Static buffers for frequently used stringsMemory Issues Table:
| Issue | Problem | Solution |
|---|---|---|
| String fragmentation | Heap corruption over time | Use char arrays, snprintf() |
| Stack overflow | Large local arrays | Use static/global, reduce size |
| Buffer overflow | strcpy without bounds | Use strncpy, snprintf |
| Memory leak | malloc without free | Avoid dynamic allocation |
Safe String Handling:
// ❌ Dangerous: String class in loop
void loop() {
String msg = "Temp: " + String(temp) + "C"; // Fragments heap
Serial.println(msg);
}
// ✅ Safe: Static buffer with snprintf
void loop() {
static char msg[32];
snprintf(msg, sizeof(msg), "Temp: %.1fC", temp);
Serial.println(msg);
}
// ✅ Safe: F() macro for flash strings
Serial.println(F("This string is in flash, not RAM"));Memory Monitoring:
// Add to setup() for debugging
Serial.print(F("Free heap: "));
Serial.println(ESP.getFreeHeap());
// Periodic check in loop()
if (ESP.getFreeHeap() < 10000) {
Serial.println(F("WARNING: Low memory!"));
}---
3. 🔢 Magic Numbers & Constants
Check For:
□ No unexplained numbers in code
□ Pin assignments in config.h
□ Timing values named
□ Threshold values documentedExamples:
// ❌ Bad: Magic numbers everywhere
if (analogRead(A0) > 512) {
digitalWrite(4, HIGH);
delay(1500);
}
// ✅ Good: Named constants
// config.h
#define MOISTURE_SENSOR_PIN A0
#define PUMP_RELAY_PIN 4
#define MOISTURE_THRESHOLD 512 // ~50% soil moisture
#define PUMP_RUN_TIME_MS 1500 // 1.5 second watering
// main.ino
if (analogRead(MOISTURE_SENSOR_PIN) > MOISTURE_THRESHOLD) {
digitalWrite(PUMP_RELAY_PIN, HIGH);
delay(PUMP_RUN_TIME_MS);
}---
4. ⚠️ Error Handling
Check For:
□ Sensor initialization verified
□ Network connections have timeouts
□ File operations check return values
□ Graceful degradation when components fail
□ User feedback for errors (LED, serial, display)Error Handling Patterns:
// ❌ Bad: Assume everything works
void setup() {
bme.begin(0x76); // What if it fails?
}
// ✅ Good: Check and handle failures
void setup() {
Serial.begin(115200);
if (!bme.begin(0x76)) {
Serial.println(F("BME280 not found!"));
errorBlink(ERROR_SENSOR); // Visual feedback
// Either halt or continue without sensor
sensorAvailable = false;
}
// WiFi with timeout
WiFi.begin(SSID, PASSWORD);
unsigned long startAttempt = millis();
while (WiFi.status() != WL_CONNECTED) {
if (millis() - startAttempt > WIFI_TIMEOUT_MS) {
Serial.println(F("WiFi failed - continuing offline"));
wifiAvailable = false;
break;
}
delay(500);
}
}---
5. ⏱️ Timing & Delays
Check For:
□ No blocking delay() in main loop (except simple projects)
□ millis() overflow handled (after 49 days)
□ Debouncing for buttons/switches
□ Rate limiting for sensors/networkNon-Blocking Pattern:
// ❌ Bad: Blocking delays
void loop() {
readSensor();
delay(1000); // Blocks everything for 1 second
}
// ✅ Good: Non-blocking with millis()
unsigned long previousMillis = 0;
const unsigned long INTERVAL = 1000;
void loop() {
unsigned long currentMillis = millis();
// Handle button immediately (responsive)
checkButton();
// Sensor reading at interval
if (currentMillis - previousMillis >= INTERVAL) {
previousMillis = currentMillis;
readSensor();
}
}
// ✅ millis() overflow safe (works after 49 days)
// The subtraction handles overflow automatically with unsigned mathDebouncing:
// Button debouncing
const unsigned long DEBOUNCE_MS = 50;
unsigned long lastDebounce = 0;
int lastButtonState = HIGH;
int buttonState = HIGH;
void checkButton() {
int reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonState) {
lastDebounce = millis();
}
if ((millis() - lastDebounce) > DEBOUNCE_MS) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState == LOW) {
handleButtonPress();
}
}
}
lastButtonState = reading;
}---
6. 🔌 Hardware Interactions
Check For:
□ Pin modes set in setup()
□ Pull-up/pull-down resistors considered
□ Voltage levels compatible (3.3V vs 5V)
□ Current limits respected
□ Proper power sequencingPin Configuration:
// ❌ Bad: Missing or incorrect pin modes
digitalWrite(LED_PIN, HIGH); // Works by accident on some boards
// ✅ Good: Explicit configuration
void setup() {
// Outputs
pinMode(LED_PIN, OUTPUT);
pinMode(RELAY_PIN, OUTPUT);
// Inputs with pull-up (button connects to GND)
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Analog input (no pinMode needed but document it)
// SENSOR_PIN is analog input - no pinMode required
// Set safe initial states
digitalWrite(RELAY_PIN, LOW); // Relay off at start
}---
7. 📡 Network & Communication
Check For:
□ Credentials not hardcoded (use config file)
□ Connection retry logic
□ Timeout handling
□ Secure connections (HTTPS where possible)
□ Data validationSecure Credential Handling:
// ❌ Bad: Credentials in main code
WiFi.begin("MyNetwork", "password123");
// ✅ Good: Separate config file (add to .gitignore)
// config.h
#ifndef CONFIG_H
#define CONFIG_H
#define WIFI_SSID "your-ssid"
#define WIFI_PASSWORD "your-password"
#define API_KEY "your-api-key"
#endif
// .gitignore
config.h---
8. 🔋 Power Efficiency
Check For:
□ Unused peripherals disabled
□ Appropriate sleep modes used
□ WiFi off when not needed
□ LED brightness reduced (PWM)
□ Sensor power controlledPower Optimization:
// ESP32 power management
void goToSleep(int seconds) {
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
btStop();
esp_sleep_enable_timer_wakeup(seconds * 1000000ULL);
esp_deep_sleep_start();
}
// Sensor power control
#define SENSOR_POWER_PIN 25
void readSensorWithPowerControl() {
digitalWrite(SENSOR_POWER_PIN, HIGH); // Power on
delay(100); // Stabilization time
int value = analogRead(SENSOR_PIN);
digitalWrite(SENSOR_POWER_PIN, LOW); // Power off
return value;
}---
Review Checklist Generator
Generate project-specific checklist:
## Code Review Checklist for [Project Name]
### Critical (Must Fix)
- [ ] Memory: No String in loop()
- [ ] Safety: All array accesses bounds-checked
- [ ] Error: Sensor init failures handled
### Important (Should Fix)
- [ ] No magic numbers
- [ ] Non-blocking delays where possible
- [ ] Timeouts on all network operations
### Nice to Have
- [ ] F() macro for string literals
- [ ] Consistent naming convention
- [ ] Comments for complex logic
### Platform-Specific (ESP32)
- [ ] WiFi reconnection logic
- [ ] Brownout detector consideration
- [ ] Deep sleep properly configured---
Code Smell Detection
Automatic Red Flags
| Pattern | Severity | Action |
|---|---|---|
String + in loop() | 🔴 Critical | Replace with snprintf |
delay(>100) in loop() | 🟡 Warning | Consider millis() |
while(1) without yield() | 🔴 Critical | Add yield() or refactor |
| Hardcoded credentials | 🔴 Critical | Move to config.h |
malloc/new without free/delete | 🔴 Critical | Track allocations |
sprintf (not snprintf) | 🟡 Warning | Use snprintf for safety |
Global variables without volatile for ISR | 🔴 Critical | Add volatile keyword |
---
Review Response Template
## Code Review Summary
**Overall Assessment:** ⭐⭐⭐☆☆ (3/5)
### 🔴 Critical Issues (Fix Before Use)
1. **Memory leak in line 45** - String concatenation in loop()
- Current: `String msg = "Value: " + String(val);`
- Fix: Use `snprintf(buffer, sizeof(buffer), "Value: %d", val);`
### 🟡 Important Issues (Fix Soon)
1. **Missing error handling** - BME280 init not checked
2. **Magic number** - `delay(1500)` unexplained
### 🟢 Suggestions (Nice to Have)
1. Consider adding F() macro to Serial.print strings
2. Function `readAllSensors()` could be split
### ✅ Good Practices Found
- Clear variable naming
- Consistent formatting
- Good use of constants in config.h
### Recommended Next Steps
1. Fix critical memory issue first
2. Add sensor error handling
3. Run memory monitoring to verify fix---
Quick Reference Commands
// Memory debugging
Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());
Serial.printf("Min free heap: %d bytes\n", ESP.getMinFreeHeap());
// Stack high water mark (FreeRTOS)
Serial.printf("Stack remaining: %d bytes\n", uxTaskGetStackHighWaterMark(NULL));
// Find I2C devices
void scanI2C() {
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.printf("Found device at 0x%02X\n", addr);
}
}
}{
"name": "code-review-facilitator",
"metadata": {
"description": "Automated code review for Arduino/ESP32/RP2040 projects focusing on best practices, memory safety, and common pitfalls. Use when user wants code feedback, says \"review my code\", needs help improving code quality, or before finalizing a project. Generates actionable checklists and specific improvement suggestions.",
"version": "0.8.0",
"license": "MIT",
"author": "arduino-skills contributors",
"tags": ["code-review", "quality-assurance", "best-practices", "embedded-systems", "education"],
"category": "maker-tools"
},
"plugins": [
{
"name": "code-review-facilitator",
"description": "Automated code review focusing on best practices, memory safety, and optimization for embedded systems",
"enabled": true
}
]
}
#!/usr/bin/env python3
"""
Arduino Code Analyzer - Static analysis for Arduino/C++ sketches
Analyzes code for common issues:
- Memory problems (String usage, large arrays)
- Blocking code (delay in loops)
- Missing volatile for ISR variables
- Pin conflicts
- Power efficiency issues
Usage:
uv run --no-project scripts/analyze_code.py sketch.ino
uv run --no-project scripts/analyze_code.py --dir /path/to/project
uv run --no-project scripts/analyze_code.py --interactive
"""
import argparse
import re
import os
from dataclasses import dataclass
from typing import List, Dict, Optional
from pathlib import Path
# =============================================================================
# Issue Definitions
# =============================================================================
@dataclass
class Issue:
"""Code issue found during analysis"""
severity: str # error, warning, info
category: str
line_number: int
description: str
suggestion: str
code_snippet: str = ""
# Analysis rules
RULES = {
# Memory Issues
"string_in_loop": {
"pattern": r'void\s+loop\s*\(\s*\).*?String\s+\w+',
"multiline": True,
"severity": "warning",
"category": "memory",
"description": "String object created inside loop() - causes memory fragmentation",
"suggestion": "Move String declarations outside loop() or use char arrays"
},
"string_concat_loop": {
"pattern": r'for\s*\(.*?\).*?(?:\w+\s*\+=\s*|String.*?\+)',
"multiline": True,
"severity": "warning",
"category": "memory",
"description": "String concatenation in loop - allocates memory repeatedly",
"suggestion": "Pre-allocate buffer or use sprintf() with char array"
},
"large_array": {
"pattern": r'(?:int|float|double|long)\s+\w+\s*\[\s*(\d{3,})\s*\]',
"severity": "warning",
"category": "memory",
"description": "Large array may exhaust RAM",
"suggestion": "Use PROGMEM for constants, or consider external storage"
},
# Blocking Code
"delay_in_loop": {
"pattern": r'void\s+loop\s*\(\s*\).*?delay\s*\(\s*(\d+)\s*\)',
"multiline": True,
"severity": "info",
"category": "performance",
"description": "delay() blocks execution - consider millis() for non-blocking",
"suggestion": "Use millis()-based timing for responsive code"
},
"long_delay": {
"pattern": r'delay\s*\(\s*(\d{4,})\s*\)',
"severity": "warning",
"category": "performance",
"description": "Long delay ({0}ms) blocks all code execution",
"suggestion": "Use millis() timing pattern for delays > 100ms"
},
# ISR Issues
"non_volatile_isr": {
"pattern": r'(?:attachInterrupt|ISR\s*\().*?(\w+)',
"severity": "error",
"category": "correctness",
"description": "Variable used in ISR may not be declared volatile",
"suggestion": "Declare shared ISR variables as: volatile int variableName;"
},
"serial_in_isr": {
"pattern": r'(?:ISR\s*\(|void\s+\w+ISR\s*\().*?Serial\.',
"multiline": True,
"severity": "error",
"category": "correctness",
"description": "Serial operations inside ISR can cause crashes",
"suggestion": "Set a flag in ISR, handle Serial in loop()"
},
"delay_in_isr": {
"pattern": r'(?:ISR\s*\(|void\s+\w+ISR\s*\().*?delay\s*\(',
"multiline": True,
"severity": "error",
"category": "correctness",
"description": "delay() does not work inside ISR",
"suggestion": "ISRs should be fast - set flag and exit"
},
# Digital I/O
"pinmode_in_loop": {
"pattern": r'void\s+loop\s*\(\s*\).*?pinMode\s*\(',
"multiline": True,
"severity": "warning",
"category": "performance",
"description": "pinMode() called repeatedly in loop()",
"suggestion": "Move pinMode() to setup() - only needs to run once"
},
"analog_read_speed": {
"pattern": r'for\s*\(.*?\).*?analogRead\s*\(',
"multiline": True,
"severity": "info",
"category": "performance",
"description": "analogRead() is slow (~100µs per read)",
"suggestion": "Consider averaging or reducing read frequency"
},
# Power Efficiency
"no_sleep": {
"pattern": r'void\s+loop\s*\(\s*\).*?while\s*\(\s*(?:1|true)\s*\)',
"multiline": True,
"severity": "info",
"category": "power",
"description": "Busy wait loop - wastes power",
"suggestion": "Use sleep modes for battery-powered projects"
},
"adc_always_on": {
"pattern": r'analogRead\s*\(',
"severity": "info",
"category": "power",
"description": "ADC consumes power even between reads",
"suggestion": "Disable ADC when not needed for power savings"
},
# Common Bugs
"assignment_in_condition": {
"pattern": r'if\s*\(\s*\w+\s*=\s*[^=]',
"severity": "error",
"category": "correctness",
"description": "Assignment (=) in if condition - likely meant comparison (==)",
"suggestion": "Use == for comparison: if (x == 5)"
},
"floating_point_comparison": {
"pattern": r'if\s*\(.*?(?:float|double)\s+.*?==',
"multiline": True,
"severity": "warning",
"category": "correctness",
"description": "Direct floating-point comparison may fail",
"suggestion": "Use tolerance: if (abs(a - b) < 0.001)"
},
"unsigned_negative": {
"pattern": r'(?:byte|unsigned)\s+\w+\s*=\s*-',
"severity": "error",
"category": "correctness",
"description": "Negative value assigned to unsigned type",
"suggestion": "Use signed type (int) or ensure value is positive"
},
# Best Practices
"magic_numbers": {
"pattern": r'(?:pinMode|digitalWrite|analogWrite|analogRead)\s*\(\s*\d+\s*[,\)]',
"severity": "info",
"category": "style",
"description": "Magic number used for pin - harder to maintain",
"suggestion": "Use named constants: const int LED_PIN = 13;"
},
"missing_f_macro": {
"pattern": r'Serial\.print(?:ln)?\s*\(\s*"[^"]{20,}"',
"severity": "info",
"category": "memory",
"description": "Long string literal uses RAM",
"suggestion": "Use F() macro: Serial.println(F(\"text\"))"
},
"no_serial_check": {
"pattern": r'Serial\.begin.*?Serial\.print',
"multiline": True,
"severity": "info",
"category": "robustness",
"description": "Serial used without checking if ready",
"suggestion": "Add: while (!Serial) delay(10); after Serial.begin()"
}
}
def analyze_file(filepath: str) -> List[Issue]:
"""Analyze a single file for issues"""
issues = []
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
lines = content.split('\n')
except Exception as e:
return [Issue("error", "file", 0, f"Could not read file: {e}", "Check file path")]
for rule_name, rule in RULES.items():
pattern = rule["pattern"]
flags = re.DOTALL if rule.get("multiline") else 0
# Search in full content for multiline patterns
if rule.get("multiline"):
matches = re.finditer(pattern, content, flags | re.IGNORECASE)
for match in matches:
# Calculate line number
line_num = content[:match.start()].count('\n') + 1
# Get snippet
snippet_start = max(0, match.start() - 20)
snippet_end = min(len(content), match.end() + 20)
snippet = content[snippet_start:snippet_end].replace('\n', ' ')[:60]
description = rule["description"]
if '{0}' in description and match.groups():
description = description.format(*match.groups())
issues.append(Issue(
severity=rule["severity"],
category=rule["category"],
line_number=line_num,
description=description,
suggestion=rule["suggestion"],
code_snippet=snippet
))
else:
# Line-by-line search
for line_num, line in enumerate(lines, 1):
if re.search(pattern, line, re.IGNORECASE):
description = rule["description"]
match = re.search(pattern, line)
if match and match.groups() and '{0}' in description:
description = description.format(*match.groups())
issues.append(Issue(
severity=rule["severity"],
category=rule["category"],
line_number=line_num,
description=description,
suggestion=rule["suggestion"],
code_snippet=line.strip()[:60]
))
return issues
def analyze_directory(dirpath: str) -> Dict[str, List[Issue]]:
"""Analyze all .ino, .cpp, .h files in directory"""
results = {}
for ext in ['*.ino', '*.cpp', '*.c', '*.h']:
for filepath in Path(dirpath).rglob(ext):
issues = analyze_file(str(filepath))
if issues:
results[str(filepath)] = issues
return results
def format_report(issues: List[Issue], filename: str = "") -> str:
"""Format issues as readable report"""
if not issues:
return "✅ No issues found!"
lines = [
"=" * 60,
f"Code Analysis Report{': ' + filename if filename else ''}",
"=" * 60,
""
]
# Group by severity
errors = [i for i in issues if i.severity == "error"]
warnings = [i for i in issues if i.severity == "warning"]
infos = [i for i in issues if i.severity == "info"]
# Summary
lines.append(f"Summary: {len(errors)} errors, {len(warnings)} warnings, {len(infos)} suggestions")
lines.append("")
# Errors first
if errors:
lines.append("🔴 ERRORS (must fix)")
lines.append("-" * 40)
for issue in errors:
lines.append(f" Line {issue.line_number}: {issue.description}")
lines.append(f" Fix: {issue.suggestion}")
if issue.code_snippet:
lines.append(f" Code: {issue.code_snippet}")
lines.append("")
if warnings:
lines.append("🟡 WARNINGS (should fix)")
lines.append("-" * 40)
for issue in warnings:
lines.append(f" Line {issue.line_number}: {issue.description}")
lines.append(f" Fix: {issue.suggestion}")
lines.append("")
if infos:
lines.append("🔵 SUGGESTIONS (nice to have)")
lines.append("-" * 40)
for issue in infos:
lines.append(f" Line {issue.line_number}: {issue.description}")
lines.append(f" Tip: {issue.suggestion}")
lines.append("")
return "\n".join(lines)
def interactive_mode():
"""Interactive code analysis"""
print("=" * 60)
print("Arduino Code Analyzer - Interactive Mode")
print("=" * 60)
print()
print("Paste your code (press Enter twice when done):")
print()
lines = []
empty_count = 0
while True:
line = input()
if not line:
empty_count += 1
if empty_count >= 2:
break
else:
empty_count = 0
lines.append(line)
# Save to temp file and analyze
temp_file = "_temp_analysis.ino"
with open(temp_file, 'w') as f:
f.write('\n'.join(lines))
issues = analyze_file(temp_file)
os.remove(temp_file)
print(format_report(issues, "pasted code"))
def main():
parser = argparse.ArgumentParser(description="Arduino Code Analyzer")
parser.add_argument("file", nargs="?", help="File to analyze")
parser.add_argument("--dir", "-d", type=str, help="Directory to analyze")
parser.add_argument("--interactive", "-i", action="store_true", help="Interactive mode")
parser.add_argument("--json", "-j", action="store_true", help="Output as JSON")
parser.add_argument("--severity", "-s", type=str, help="Min severity: error, warning, info")
args = parser.parse_args()
if args.interactive:
interactive_mode()
return
if args.dir:
results = analyze_directory(args.dir)
if args.json:
import json
print(json.dumps({k: [vars(i) for i in v] for k, v in results.items()}, indent=2))
else:
for filepath, issues in results.items():
print(format_report(issues, filepath))
print()
elif args.file:
issues = analyze_file(args.file)
# Filter by severity if specified
if args.severity:
severity_order = {"error": 0, "warning": 1, "info": 2}
min_level = severity_order.get(args.severity, 2)
issues = [i for i in issues if severity_order.get(i.severity, 2) <= min_level]
if args.json:
import json
print(json.dumps([vars(i) for i in issues], indent=2))
else:
print(format_report(issues, args.file))
else:
parser.print_help()
if __name__ == "__main__":
main()