Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# cpp-linter-hooks AI Coding Guide

## Project Overview
Pre-commit hooks wrapper that auto-installs and runs clang-format and clang-tidy from Python wheels. Supports Python 3.9-3.14 across Windows, Linux, and macOS.

## Architecture

### Entry Points & Flow
- **Entry scripts**: `clang-format-hook` and `clang-tidy-hook` (defined in `pyproject.toml`)
- **Hook definitions**: `.pre-commit-hooks.yaml` configures both hooks with `types_or: [c++, c]`
- **Execution pattern**: Parse args → resolve/install tool version → subprocess.run → return (exit_code, output)

### Core Modules
- **`clang_format.py`**: Wraps clang-format with `-i` (in-place), supports `--verbose` and `--dry-run` modes
- Returns `-1` for dry-run to distinguish from actual failures
- **`clang_tidy.py`**: Wraps clang-tidy, forces exit code 1 if "warning:" or "error:" in output
- **`util.py`**: Version resolution and pip-based tool installation
- `_resolve_version()`: Supports partial matches (e.g., "20" → "20.1.7")
- `DEFAULT_CLANG_FORMAT_VERSION` and `DEFAULT_CLANG_TIDY_VERSION` read from `pyproject.toml`
- **`versions.py`**: Auto-generated by `scripts/update_versions.py` (runs weekly via GitHub Actions)

### Version Management Pattern
```python
# Users can specify partial versions
--version=21 # Resolves to latest 21.x.x
--version=21.1 # Resolves to latest 21.1.x
--version=21.1.8 # Exact version
```

## Development Workflows

### Local Testing
```bash
# Test hooks locally without installing
pre-commit try-repo ./.. clang-format --verbose --all-files
pre-commit try-repo ./.. clang-tidy --verbose --all-files
Comment on lines +35 to +36
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Read the copilot-instructions.md file to see the context around lines 35-36
head -n 60 .github/copilot-instructions.md | tail -n 30

Repository: cpp-linter/cpp-linter-hooks

Length of output: 1223


🏁 Script executed:

# Search for any other pre-commit try-repo usage in the repository
rg -n 'try-repo' --type md --type sh --type yaml --type yml

Repository: cpp-linter/cpp-linter-hooks

Length of output: 98


🏁 Script executed:

# Check if there are any GitHub Actions workflows that use pre-commit
find .github -name "*.yaml" -o -name "*.yml" | head -20

Repository: cpp-linter/cpp-linter-hooks

Length of output: 405


🏁 Script executed:

# Read the pre-commit workflow file to see how try-repo is used there
cat .github/workflows/pre-commit.yml

Repository: cpp-linter/cpp-linter-hooks

Length of output: 314


🌐 Web query:

pre-commit try-repo documentation syntax path argument

💡 Result:

pre-commit try-repo syntax is:

pre-commit try-repo [options] repo [hook]
  • repo is the “Repository to source hooks from” (positional argument). It can be a git repo URL or a local filesystem path to a repo checkout (for example: ../path/to/my-hooks). [1][2]
  • hook (optional) is a single hook id to run; if omitted, it will generate a config snippet / run the repo’s hooks per your selection. [1]
  • To try a specific revision, use --ref / --rev; if you don’t specify it, try-repo uses the repo’s HEAD. [1]

Example with a local path:

pre-commit try-repo ../LOCAL/PATH/TO/my-hooks

[2]

Example with a remote + ref:

pre-commit try-repo --ref v4.0.0 https://github.com/pre-commit/pre-commit-hooks trailing-whitespace

[1]

Sources: [1] https://www.mankier.com/1/pre-commit-try-repo [2] https://blog.dagworks.io/p/custom-pre-commit-hooks-for-safer


🏁 Script executed:

# Search for any setup.py, pyproject.toml, or README that might document this
rg -n 'try-repo' -type md -A 2 -B 2

Repository: cpp-linter/cpp-linter-hooks

Length of output: 98


🏁 Script executed:

# Check if there's any documentation about where to run pre-commit commands from
rg -n 'pre-commit' README.md -A 2 -B 2 | head -50

Repository: cpp-linter/cpp-linter-hooks

Length of output: 1281


🏁 Script executed:

# Check pyproject.toml or tox.ini for pre-commit usage patterns
fd -e toml -e ini | xargs grep -l 'pre-commit' 2>/dev/null

Repository: cpp-linter/cpp-linter-hooks

Length of output: 85


🏁 Script executed:

# Check pyproject.toml for pre-commit related configurations
grep -n 'pre-commit' pyproject.toml -A 5 -B 5

Repository: cpp-linter/cpp-linter-hooks

Length of output: 695


🏁 Script executed:

# Let me also check what the actual repository root structure looks like
# and understand where commands would typically be run from
ls -la | head -20

Repository: cpp-linter/cpp-linter-hooks

Length of output: 1225


Fix pre-commit try-repo path from ./.. to .

Lines 35-36 use ./.. as the repository path, which resolves to the parent directory rather than the current repository. When running these commands from the repository root (as the documentation context indicates), the path should be . instead:

Suggested fix
- pre-commit try-repo ./.. clang-format --verbose --all-files
- pre-commit try-repo ./.. clang-tidy --verbose --all-files
+ pre-commit try-repo . clang-format --verbose --all-files
+ pre-commit try-repo . clang-tidy --verbose --all-files
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pre-commit try-repo ./.. clang-format --verbose --all-files
pre-commit try-repo ./.. clang-tidy --verbose --all-files
pre-commit try-repo . clang-format --verbose --all-files
pre-commit try-repo . clang-tidy --verbose --all-files
🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In @.github/copilot-instructions.md around lines 35 - 36, The two pre-commit
try-repo commands use the wrong repository path (`./..`) and should point to the
current repo; update the lines containing "pre-commit try-repo ./.. clang-format
--verbose --all-files" and "pre-commit try-repo ./.. clang-tidy --verbose
--all-files" to use a single dot instead: "pre-commit try-repo . clang-format
--verbose --all-files" and "pre-commit try-repo . clang-tidy --verbose
--all-files".


# Run test suite
uv run pytest -vv # All tests
uv run coverage run -m pytest # With coverage
uv run pytest -m benchmark # Performance tests only
```

### Adding/Modifying Features
1. **Update hook logic** in `cpp_linter_hooks/{clang_format,clang_tidy}.py`
2. **Add tests** in `tests/test_*.py` with `@pytest.mark.benchmark` for performance tracking
3. **Test with sample files** in `testing/` directory (use `good.c` as expected output)
4. **Update README.md** if user-facing behavior changes

### Dependency Management
- **Uses `uv`** for all dev operations (not pip directly)
- **Pin versions**: Default tool versions in `pyproject.toml` dependencies section
- **Update versions**: Run `python scripts/update_versions.py` (auto-runs weekly on Monday 2 AM UTC)

## Project-Specific Conventions

### Return Value Pattern
All hook functions return `Tuple[int, str]`:
- `(0, "")` → Success
- `(1, output)` → Failure (print output)
- `(-1, output)` → Dry-run mode (clang-format only, convert to success in main)

### Testing Conventions
- Use `tmp_path` fixture to avoid modifying repo files
- Parametrize version tests: `@pytest.mark.parametrize` with versions 16-21
- Mark performance-sensitive tests with `@pytest.mark.benchmark`
- Compare formatted output against `testing/good.c` for correctness

### Argument Handling
```python
# Standard pattern in both hooks
parser = ArgumentParser()
parser.add_argument("--version", default=DEFAULT_VERSION)
hook_args, other_args = parser.parse_known_args(args)
# ... install tool if needed ...
command = ["tool-name"] + other_args # Pass through unknown args
```

## Critical Files

- **`pyproject.toml`**: Defines entry points, dependencies, default versions
- **`versions.py`**: Auto-updated; DO NOT edit manually (see comment)
- **`.pre-commit-hooks.yaml`**: Hook metadata for pre-commit framework
- **`testing/run.sh`**: Integration test script used in CI

## Integration Points

### PyPI Dependencies
- Fetches available versions from `https://pypi.org/pypi/{package}/json`
- Filters out pre-release versions using regex pattern `(alpha|beta|rc|dev|a\d+|b\d+)`
- Installs via `subprocess.run([sys.executable, "-m", "pip", "install", f"{tool}=={version}"])`

### Pre-commit Framework
- Hooks run in parallel (`require_serial: false`) for performance
- File type filtering via `types_or: [c++, c]`
- Users configure via `.pre-commit-config.yaml` with `args:` list

## Common Tasks

**Add support for a new argument:**
1. Add to ArgumentParser in hook module
2. Pass to subprocess command or handle in Python
3. Add test case in `tests/test_*.py`

**Update default tool versions:**
1. Edit `dependencies` in `pyproject.toml`
2. Run tests to ensure compatibility
3. Update version in README examples

**Debug hook failures:**
- Add `--verbose` to clang-format args for detailed output
- Check `testing/run.sh` for integration test patterns
- Use `pre-commit run --verbose` for detailed pre-commit logs
Loading