new-cpp-lint
Create a new C++ lint rule, test it, run it across the codebase, and selectively apply fixes. Usage - /new-cpp-lint <rule description>
SKILL.mdsoftware/cat_game/lib/FastLED-master/.claude/skills/new-cpp-lint/SKILL.md
GitHubCreate New C++ Lint Rule
You are creating a new C++ lint rule for the FastLED codebase. The default home for the new rule is the Rust crate ci/lint_cpp_rs/; the legacy Python tier (ci/lint_cpp/) is reserved for AST ratchets and cross-file structural checks. Follow this workflow.
Input
$ARGUMENTS
Phase 1: Design the Rule
- Parse the rule description from the input
- Research the codebase: Grep for existing patterns, violations, and edge cases
- Choose detection strategy — prefer the simplest approach that works:
- Rust regex checker (default): Use when the pattern is a keyword, token, or textual pattern that can be reliably detected with word-boundary regex / per-line state. Examples: banning a keyword, detecting
std::namespace usage, finding#pragmadirectives, flagging rawnew/delete. The overwhelming majority of lint rules belong here. Fast, parallel, no libclang dependency. - Python AST ratchet (libclang): Use only when the rule requires semantic understanding that regex cannot provide — e.g., type-aware checks, matching function signatures, detecting inheritance patterns, or analyzing template instantiations. AST parsing is heavy and slow; don't use it when a Rust regex checker suffices. See
ci/tools/check_noexcept.pyfor the canonical pattern.
- Rust regex checker (default): Use when the pattern is a keyword, token, or textual pattern that can be reliably detected with word-boundary regex / per-line state. Examples: banning a keyword, detecting
- Define scope: Which directories should be checked (src/fl/, platforms/, examples/, tests/)
- Identify exemptions: Comments, macros, templates, third_party/, platform-guarded code that should be allowed
Output:
## Rule Design
**Rule**: [one-line rule statement]
**Detection**: rust-regex / python-ast
**Scope**: [directories]
**Exemptions**: [what should NOT be flagged]
**Suppression**: [comment pattern to suppress, e.g. "// nolint" or "// ok no X"]
Phase 2: Write the Checker + Tests
Default (Rust regex) path:
- Read reference checkers: Study 1-2 similar existing checkers in
ci/lint_cpp_rs/src/checkers/(seeci/lint_cpp_rs/src/checkers/README.mdfor the policy-area grouping) - Add a checker struct to the matching file under
ci/lint_cpp_rs/src/checkers/(e.g.basic.rs,style.rs,preprocessor.rs). ImplementFileContentChecker(trait inci/lint_cpp_rs/src/lint_core/processor_registry_cli.rs):name()returns the class-style name (e.g."YourRuleChecker")should_process_file(file_path, project_root)filters by extension + scopecheck_file_content(file_content)returnsVec<(usize, String)>of(line_number, message)
- Add inline Rust tests to
ci/lint_cpp_rs/src/lint_core/tests.rswith:- Tests for violations (should flag)
- Tests for correct code (should pass)
- Tests for exemptions (comments, macros, suppression marker)
- Tests for edge cases (multi-line, templates, nested scopes)
- Pre-compile any regex in
ci/lint_cpp_rs/src/lint_core/regexes.rs— neverRegex::newinside the hot loop - Build + run the Rust tests:
uv run python ci/lint_cpp/rust_binary_cache.py(rebuilds the cached binary, which runs the inline tests during cargo build)
Python AST ratchet path (only when libclang semantics are required):
- Add a new tool under
ci/tools/check_<rule>.pyfollowing the pattern inci/tools/check_noexcept.py(translation unit + clang-query + baseline diff) - Wire it into
ci/lint_cpp/run_all_checkers.pynext torun_noexcept_ast_check/run_array_param_ast_check - Add a checked-in baseline so the ratchet can only ratchet down
Phase 3: Run Across Codebase (Dry Run)
- Run the Rust binary directly against the tree:
uv run python ci/lint_cpp/rust_binary_cache.pythen./ci/lint_cpp_rs/target/debug/fastled-lint --checker your_rule(or justbash lint --cppand grep for your checker name) - Count violations: Report how many files/lines are affected
- Sample review: Show 5-10 representative violations to verify correctness
- Check for false positives: If any look wrong, refine
should_process_fileor the detection logic and re-run
Output:
## Dry Run Results
**Violations found**: [N] across [M] files
**Sample violations**:
- file.h:42: [violation text]
- file.cpp:100: [violation text]
**False positives**: [none / list of issues found and how they were fixed]
Phase 4: Register in Lint Pipeline
Four edits, all small:
ci/lint_cpp_rs/src/lint_core/processor_registry_cli.rs:- Add the snake_case name to
supported_checker_names() - Add the class-style name to
supported_python_checker_names() - Add
("your_rule", Box::new(YourRuleChecker))to thecheckersvec increate_checkers()
- Add the snake_case name to
ci/lint_cpp/rust_bridge.py:- Add
"YourRuleChecker"to theRUST_SUPPORTED_CHECKERSfrozenset
- Add
- Verify integration: Run
bash lint --cpp— ensure it runs without breaking other checks - If violations are expected: Add suppression comments to known exceptions, or report them
Phase 5: Apply Fixes (Selective)
Only if the rule has a clear autofix pattern:
- Create fixer script (if needed) in
ci/tools/for batch-applying fixes - Apply to one file first: Verify the fix is correct
- Run tests:
bash test --cppafter each batch of fixes - Apply incrementally: Fix one directory at a time, testing after each
- Do NOT auto-fix ambiguous cases — report them for manual review
If no autofix is appropriate: Report the violation list and let the user decide.
Phase 6: Summary
## New Lint Rule Created
**Rule**: [description]
**Checker**: ci/lint_cpp_rs/src/checkers/<file>.rs::YourRuleChecker
**Tests**: ci/lint_cpp_rs/src/lint_core/tests.rs (#[test] fn your_rule_*)
**Registration**:
- ci/lint_cpp_rs/src/lint_core/processor_registry_cli.rs (3 sites)
- ci/lint_cpp/rust_bridge.py (RUST_SUPPORTED_CHECKERS)
**Detection**: [rust-regex/python-ast]
**Violations**: [N] found, [M] fixed, [K] remaining
**Files modified**: [list]
**Suppression**: Use `// [suppression comment]` to suppress individual lines
Key Rules
- Default tier is Rust — only fall back to Python for AST ratchets or cross-file structural checks
- Test FIRST — never register a checker without passing inline
#[test]functions - Dry run FIRST — never auto-fix without reviewing the violation list
- Incremental fixes — fix one directory at a time, test after each
- Stay in project root — never
cdto subdirectories - Use
bash test --cppandbash lint --cpp— never barecargo,meson, or build commands - Return violations from
check_file_contentasVec<(usize, String)>— no shared mutable state, dispatch israyon-parallel - Support suppression — always allow a comment marker (e.g.
// nolint) to suppress individual lines - Handle Windows paths — call
normalize_path()before path comparisons