|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +github_linter is a Python tool for auditing GitHub repositories at scale. It scans repositories for common configuration issues, missing files, and standardization opportunities across multiple repos. |
| 8 | + |
| 9 | +## Development Commands |
| 10 | + |
| 11 | +### Testing and Linting |
| 12 | + |
| 13 | +- Run all precommit checks: `make precommit` |
| 14 | +- Run linting: `uv run ruff check github_linter tests` |
| 15 | +- Run type checking: `uv run mypy --strict github_linter tests` |
| 16 | +- Run tests: `uv run pytest github_linter tests` |
| 17 | +- Run single test: `uv run pytest tests/test_<module>.py::<test_name>` |
| 18 | + |
| 19 | +### Running the CLI |
| 20 | + |
| 21 | +- Run the CLI: `uv run python -m github_linter` |
| 22 | +- Run with filters: `uv run python -m github_linter --repo <repo_name> --owner <owner_name>` |
| 23 | +- Run specific module: `uv run python -m github_linter --module <module_name>` |
| 24 | +- Run with fixes: `uv run python -m github_linter --fix` |
| 25 | +- List available repos: `uv run python -m github_linter --list-repos` |
| 26 | + |
| 27 | +### Web Interface |
| 28 | + |
| 29 | +- Start web server: `uv run python -m github_linter.web` |
| 30 | +- Or use the script: `./run_web.sh` |
| 31 | + |
| 32 | +### Docker |
| 33 | + |
| 34 | +- Build container: `make docker_build` |
| 35 | +- Run web server in container: `make docker_run` |
| 36 | + |
| 37 | +## Architecture |
| 38 | + |
| 39 | +### Core Components |
| 40 | + |
| 41 | +1. **GithubLinter** (`github_linter/__init__.py`) - Main orchestrator that: |
| 42 | + - Handles GitHub authentication (via environment variable `GITHUB_TOKEN` or config file) |
| 43 | + - Manages rate limiting |
| 44 | + - Coordinates module execution across repositories |
| 45 | + - Generates reports |
| 46 | + |
| 47 | +2. **RepoLinter** (`github_linter/repolinter.py`) - Per-repository handler that: |
| 48 | + - Manages file caching for the repository |
| 49 | + - Runs test modules against the repository |
| 50 | + - Tracks errors, warnings, and fixes |
| 51 | + - Provides utility methods for checking files and languages |
| 52 | + - Handles file creation/updates with protected branch awareness |
| 53 | + |
| 54 | +3. **Test Modules** (`github_linter/tests/`) - Pluggable modules that check specific aspects: |
| 55 | + - Each module must define `CATEGORY`, `LANGUAGES`, and `DEFAULT_CONFIG` |
| 56 | + - Functions starting with `check_` are automatically discovered and run |
| 57 | + - Functions starting with `fix_` are run when `--fix` flag is used |
| 58 | + - Modules are loaded dynamically in `tests/__init__.py` |
| 59 | + |
| 60 | +### Available Test Modules |
| 61 | + |
| 62 | +- `branch_protection` - Validates and configures branch protection on default branches |
| 63 | +- `codeowners` - Validates CODEOWNERS files |
| 64 | +- `dependabot` - Validates Dependabot configuration |
| 65 | +- `generic` - Checks for unwanted files, CODEOWNERS, FUNDING.yml |
| 66 | +- `github_actions` - Validates GitHub Actions workflows |
| 67 | +- `homebrew` - Homebrew-specific checks |
| 68 | +- `issues` - Reports on open issues and PRs |
| 69 | +- `mkdocs` - Ensures mkdocs projects have proper CI setup |
| 70 | +- `pyproject` - Validates pyproject.toml (authors, naming, configuration) |
| 71 | +- `security_md` - Checks for SECURITY.md |
| 72 | +- `terraform` - Checks Terraform provider configurations |
| 73 | + |
| 74 | +### Module Language Filtering |
| 75 | + |
| 76 | +Modules declare which languages they apply to via the `LANGUAGES` attribute: |
| 77 | +- Use `["all"]` for modules that apply to all repositories |
| 78 | +- Use specific languages (e.g., `["python"]`, `["rust"]`) to run only on repos with those languages |
| 79 | +- Language detection is based on GitHub's automatic language detection |
| 80 | + |
| 81 | +### Configuration |
| 82 | + |
| 83 | +Configuration file locations (in priority order): |
| 84 | +1. `./github_linter.json` (local directory) |
| 85 | +2. `~/.config/github_linter.json` (user config) |
| 86 | + |
| 87 | +Each module can define `DEFAULT_CONFIG` which gets merged with user configuration. |
| 88 | + |
| 89 | +#### Branch Protection Configuration |
| 90 | + |
| 91 | +The `branch_protection` module supports both legacy branch protection rules and modern GitHub rulesets. It can check existing protection, create new protection, and migrate from legacy rules to rulesets. |
| 92 | + |
| 93 | +```json |
| 94 | +{ |
| 95 | + "branch_protection": { |
| 96 | + "enable_protection": true, |
| 97 | + "allow_admin_bypass": true, |
| 98 | + "require_pull_request": true, |
| 99 | + "required_approving_review_count": 1, |
| 100 | + "dismiss_stale_reviews": true, |
| 101 | + "require_code_owner_review": false, |
| 102 | + "use_rulesets": true, |
| 103 | + "migrate_to_rulesets": false, |
| 104 | + "warn_on_mismatch": true, |
| 105 | + "language_checks": { |
| 106 | + "Python": ["pytest", "ruff", "mypy"], |
| 107 | + "Rust": ["cargo-test", "clippy"], |
| 108 | + "JavaScript": ["test", "lint"], |
| 109 | + "TypeScript": ["test", "lint"], |
| 110 | + "Shell": ["shellcheck"], |
| 111 | + "Go": ["test", "lint"] |
| 112 | + } |
| 113 | + } |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +**Configuration Options:** |
| 118 | +- `enable_protection` - Whether to enable branch protection checks (default: true) |
| 119 | +- `allow_admin_bypass` - Allow repository admins to bypass protection requirements (default: true). For legacy protection, this sets `enforce_admins=false`. For rulesets, this adds repository admin role (ID 5) to `bypass_actors`. |
| 120 | +- `require_pull_request` - Require pull request before merging (default: true) |
| 121 | +- `required_approving_review_count` - Number of required PR approvals (default: 1) |
| 122 | +- `dismiss_stale_reviews` - Dismiss stale reviews when new commits are pushed (default: true) |
| 123 | +- `require_code_owner_review` - Require review from code owners (default: false) |
| 124 | +- `use_rulesets` - Prefer GitHub rulesets over legacy branch protection (default: true) |
| 125 | +- `migrate_to_rulesets` - Automatically migrate from legacy protection to rulesets when fixing (default: false) |
| 126 | +- `warn_on_mismatch` - If protection exists but doesn't match config, warn instead of error (default: true) |
| 127 | +- `language_checks` - Map of GitHub language names to required status check names. The module automatically determines which checks to require based on detected repository languages. |
| 128 | + |
| 129 | +**Legacy vs Rulesets:** |
| 130 | +- Legacy branch protection: Traditional branch protection API (one rule per branch) |
| 131 | +- Rulesets: Modern GitHub protection (multiple rulesets aggregate, more features) |
| 132 | +- The module detects which system is in use and can work with both |
| 133 | +- With `use_rulesets: true`, new protection is created as rulesets |
| 134 | +- With `migrate_to_rulesets: true`, the fix function will convert legacy protection to rulesets |
| 135 | +- Both systems can coexist; the module checks both and reports on mismatches |
| 136 | + |
| 137 | +**Implementation Notes:** |
| 138 | +- Uses PyGithub's `_requester` API for rulesets since PyGithub doesn't natively support them yet |
| 139 | +- Rulesets API requires GitHub API version 2022-11-28 |
| 140 | +- Rulesets provide more granular control and organization-wide enforcement |
| 141 | + |
| 142 | +### Exception Handling |
| 143 | + |
| 144 | +The codebase uses custom exceptions for flow control: |
| 145 | +- `SkipOnArchived` - Skip check for archived repositories |
| 146 | +- `SkipOnPrivate` - Skip check for private repositories |
| 147 | +- `SkipOnPublic` - Skip check for public repositories |
| 148 | +- `SkipOnProtected` - Skip check when default branch is protected |
| 149 | +- `SkipNoLanguage` - Skip check when required language not present |
| 150 | +- `NoChangeNeeded` - Indicates no action needed |
| 151 | + |
| 152 | +These exceptions are caught in `RepoLinter.run_module()` and suppress the check/fix function gracefully. |
| 153 | + |
| 154 | +## Adding New Test Modules |
| 155 | + |
| 156 | +1. Create a new module under `github_linter/tests/` |
| 157 | +2. Define required module-level attributes: |
| 158 | + - `CATEGORY: str` - Display name for reports |
| 159 | + - `LANGUAGES: List[str]` - Languages this module applies to (or `["all"]`) |
| 160 | + - `DEFAULT_CONFIG: Dict[str, Any]` - Default configuration |
| 161 | +3. Implement check functions: `def check_<something>(repo: RepoLinter) -> None` |
| 162 | +4. Implement fix functions: `def fix_<something>(repo: RepoLinter) -> None` |
| 163 | +5. Import the module in `github_linter/tests/__init__.py` |
| 164 | +6. Use `repo.error()`, `repo.warning()`, or `repo.fix()` to report results |
| 165 | + |
| 166 | +## Testing Strategy |
| 167 | + |
| 168 | +- Unit tests are in `/tests/` directory |
| 169 | +- Tests requiring network/authentication are marked with `@pytest.mark.network` |
| 170 | +- Run tests without network: `uv run pytest -m "not network"` |
| 171 | +- The codebase uses both PyGithub and github3.py libraries |
| 172 | + |
| 173 | +## Code Style |
| 174 | + |
| 175 | +- Line length: 200 characters (configured in pyproject.toml) |
| 176 | +- Type checking: strict mypy mode |
| 177 | +- Linting: ruff with pylint-pydantic plugin |
| 178 | +- Use pydantic for validation where appropriate |
| 179 | +- Use loguru for logging |
| 180 | + |
| 181 | +## Dependencies |
| 182 | + |
| 183 | +- Main GitHub libraries: `pygithub`, `github3-py` |
| 184 | +- Web framework: `fastapi`, `uvicorn` |
| 185 | +- Configuration: `json5`, `pyyaml`, `tomli`, `python-hcl2` |
| 186 | +- Type checking: `mypy` with strict mode |
| 187 | +- Testing: `pytest` |
0 commit comments