## Summary - Add `.claude/CLAUDE.md` with project guide, C++ coding style, correction patterns from past AI interactions, and pre-commit/pre-PR workflows - Add `.claude/commands/` with `/build`, `/test`, `/format` slash commands for Claude Code - Add `.clang-tidy` configuration - Add `scripts/run_clang_tidy.py` for parallel clang-tidy with progress reporting (reads CDB, filters to `src/` and `tests/` only) - Add pixi tasks: `clang-tidy`, `lint-cpp`, `lint-python`, `lint` - Add `clang-tools` dependency for clang-tidy - Update `.gitignore` to track `.claude/CLAUDE.md` and `.claude/commands/` ## Test plan - [x] `pixi run format` passes - [x] `pixi run lint-python` passes (ruff check) - [x] `scripts/run_clang_tidy.py` correctly filters to project files only 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
67 lines
1.9 KiB
Python
67 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Run clang-tidy in parallel on all files in compile_commands.json."""
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
from pathlib import Path
|
|
|
|
|
|
def main():
|
|
build_dir = sys.argv[1] if len(sys.argv) > 1 else "build/RelWithDebInfo"
|
|
cdb_path = Path(build_dir) / "compile_commands.json"
|
|
|
|
if not cdb_path.exists():
|
|
print(f"Error: {cdb_path} not found. Run cmake-config first.", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
project_root = Path(__file__).resolve().parent.parent
|
|
src_dirs = (project_root / "src", project_root / "tests")
|
|
|
|
cdb = json.loads(cdb_path.read_text())
|
|
files = [
|
|
entry["file"]
|
|
for entry in cdb
|
|
if any(Path(entry["file"]).resolve().is_relative_to(d) for d in src_dirs)
|
|
]
|
|
total = len(files)
|
|
|
|
lock = threading.Lock()
|
|
done = 0
|
|
failed = []
|
|
|
|
def run(file: str) -> tuple[str, int, str]:
|
|
result = subprocess.run(
|
|
["clang-tidy", "-p", build_dir, "--quiet", file],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
return file, result.returncode, result.stdout + result.stderr
|
|
|
|
with ThreadPoolExecutor() as pool:
|
|
futures = {pool.submit(run, f): f for f in files}
|
|
for future in as_completed(futures):
|
|
file, code, output = future.result()
|
|
with lock:
|
|
done += 1
|
|
name = Path(file).name
|
|
if code != 0:
|
|
failed.append(file)
|
|
print(f"[{done}/{total}] FAIL {name}")
|
|
if output.strip():
|
|
print(output, end="")
|
|
else:
|
|
print(f"[{done}/{total}] OK {name}")
|
|
|
|
if failed:
|
|
print(f"\nclang-tidy failed on {len(failed)}/{total} files.", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
print(f"\nclang-tidy passed on {total} files.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|