Firmware linter
Claude Agent Skills
npx -y skills add alycd/agent-skills --skill firmware-linterAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Lint and review embedded firmware code (C/C++) for best practices, safety, readability, and PR quality. Use when reviewing firmware PRs, checking embedded C or C++ code, auditing hardware abstraction layers, checking for magic numbers, reviewing ISR or HAL code, assessing function size and file organisation, or running a firmware code review. Trigger phrases: "lint my firmware", "review this firmware PR", "check my embedded code", "firmware code review", "check my C code for best practices", "review HAL changes", "check for magic numbers", "firmware PR review".
SKILL.md
6.8 KB, ~1.6k tokens by cl100k_base, as published. Nobody here has run it
Firmware Linter
Reviews embedded C/C++ firmware code for safety, readability, memory correctness, and PR quality.
Workflow
When invoked with a file, diff, or PR:
- Read all changed or provided files using Read/Glob tools
- Run each rule category in order (Safety → Memory → Hardware → Naming → Readability)
- Report findings grouped by severity: BLOCK | WARN | INFO
- Produce a PR verdict at the end: ✅ APPROVE / ⚠️ NEEDS CHANGES / ❌ BLOCK
Rule Categories
1. Safety & Undefined Behaviour (BLOCK on violation)
- No implicit integer conversions across signedness boundaries without explicit cast
- Volatile correctness: all hardware-mapped variables and ISR-shared variables must be
volatile - No unbounded loops without a timeout/counter escape path (especially in polling loops)
- No use of
gets(),sprintf()without bounds — requiresnprintf,fgets - Out-of-bounds array access: flag any index that is not bounds-checked before use
- ISR functions must be short: ISR bodies > 20 lines, or containing blocking calls, are a BLOCK
- No
malloc/calloc/realloc/freeinside ISR handlers or time-critical paths — flag as BLOCK
2. Memory Management (BLOCK on overflow risk, WARN on style)
- Buffer writes: every write to a fixed-size buffer must have a preceding or accompanying length check
- Stack allocation of large objects: arrays > 256 bytes on the stack inside a function are a WARN
- Pointer arithmetic: flag raw pointer arithmetic without bounds context
- String operations:
strcpy,strcatwithout size guard are a BLOCK;strlcpy/strncpywith length are OK - No dynamic allocation in interrupt context (BLOCK)
3. Hardware Abstraction (BLOCK on magic numbers)
- No raw register magic numbers: hex literals like
0x40021000,0x00000001written directly to hardware registers outside of a dedicated driver/HAL file are a BLOCK- Exception: values inside
*_reg.hor*_hal.c/cppdriver files are expected and allowed
- Exception: values inside
- No direct register access outside HAL layer: peripheral register structs must not appear in application-layer files (
main.c, task files, state machines) - Named constants for all pin/port assignments: raw GPIO numbers without a
#defineorconstexpralias are a WARN - Clock and timer values must be derived from macros (e.g.
SystemCoreClock / 1000), not hardcoded
4. Naming & Style (WARN)
- Header guards: every
.hfile must have#pragma onceor an#ifndefguard — missing guard is a WARN - File naming: prefer
snake_casefor C files, consistent with the rest of the codebase - Type-aliased structs:
typedef struct { ... } MyType_t;style must be consistent within a file - Public API prefix: functions in a module should share a common prefix matching the module name (e.g.
uart_init,uart_sendnotinit_uart,UART_Send) - No single-letter variables outside loop counters: flag
int x,uint8_t detc. in non-loop scope
5. Code Readability (WARN on moderate, BLOCK on extreme)
- Function comments: every non-static function must have a comment above its signature (one-liner minimum). Missing = WARN.
- Function length:
-
200 lines: BLOCK — this is a god-function, must be split
- 80–200 lines: WARN — suggest splitting
- ≤ 80 lines: OK
-
- File length:
-
1000 lines: BLOCK — file does too much, split it
- 500–1000 lines: WARN — consider splitting by responsibility
- ≤ 500 lines: OK
-
- One logical concern per file: flag if a single
.cfile mixes unrelated subsystems (e.g. UART driver + business logic in the same file) - No commented-out code blocks: dead code left in PRs is a WARN — it should be deleted or tracked in an issue
PR Review Checklist
Run this when reviewing a PR (not just individual files):
Required (BLOCK if missing)
- PR description includes a change summary: what changed, why, and any risk areas
Strongly Expected (WARN if missing)
- Any ISR or HAL changes are explicitly called out in the PR description with justification
- No magic register hex values introduced outside driver files
- No new god-functions or monolith files added
Good Practice (INFO)
- New functionality has test evidence (log output, unit test result, or hardware test note)
- All new public functions have a comment above the signature
- No commented-out code left in the diff
Output Format
Structure your report like this:
## Firmware Lint Report
### BLOCK — Must fix before merge
- [file.c:42] Magic number 0x40021000 written directly to register outside HAL layer
- [isr_handler.c:88] ISR body is 34 lines and calls blocking function `HAL_Delay()`
### WARN — Should fix, reviewer discretion
- [sensors.c:210] Function `sensors_process_all()` is 150 lines — consider splitting
- [main.c:17] GPIO pin 5 used without named constant
### INFO — Nice to have
- [uart.c:33] New public function `uart_flush()` has no doc comment
### PR Verdict
❌ BLOCK — 2 blocking issues must be resolved before merge.
Examples
Magic number (BLOCK)
// BAD — magic hex in application code
GPIOA->ODR = 0x00000010;
// GOOD — named constant in HAL layer
#define LED_PIN_MASK (1U << 4)
GPIOA->ODR = LED_PIN_MASK;
Missing bounds check (BLOCK)
// BAD
memcpy(dest, src, len); // len unchecked
// GOOD
if (len <= sizeof(dest)) {
memcpy(dest, src, len);
}
Function without comment (WARN)
// BAD
uint8_t calculate_checksum(uint8_t *buf, size_t len) { ... }
// GOOD
/* Returns CRC-8 checksum over buf[0..len-1]. */
uint8_t calculate_checksum(uint8_t *buf, size_t len) { ... }
Volatile missing on ISR-shared variable (BLOCK)
// BAD
bool data_ready = false; // set in ISR, read in main loop
// GOOD
volatile bool data_ready = false;
Notes for the Reviewer
- Apply rules strictly to new code in the diff; don't penalise pre-existing violations unless they're directly adjacent to the change
- If a file is legacy and untouched by this PR, note it as INFO only
- When in doubt about intent (e.g. a magic number that might be intentional), ask the author rather than auto-blocking