chore: add CLAUDE.md, clang-tidy, and agent workflow tooling (#419)
## 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>
This commit is contained in:
66
scripts/run_clang_tidy.py
Normal file
66
scripts/run_clang_tidy.py
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user