Thank you for your interest in contributing. This guide covers how to set up a local development environment, run tests, follow coding conventions, add a new analyzer, and submit a pull request.
- Python 3.11 or later — the codebase uses
matchstatements,X | Yunion syntax, and other 3.11+ features. - A GitHub Personal Access Token — optional for public-only audits, but useful for private repos, organization metadata, and higher rate limits. Create one at https://github.com/settings/tokens with the narrowest scopes needed for the audit you plan to run.
- Git — standard installation, used for shallow cloning during audits.
# Clone the repo
git clone https://github.com/<your-fork>/GithubRepoAuditor.git
cd GithubRepoAuditor
# Install all runtime + dev dependencies
make install-dev
# Copy the environment template if you need authenticated audit runs
cp .env.example .env
# Edit .env and set GITHUB_TOKEN=<your token>If you do not use make, the equivalent pip command is:
python3 -m pip install -e ".[dev,config]"make testThis runs the full test suite under tests/ with verbose output. The suite covers all 12 analyzers, the scorer, the report generator, the GitHub API client, and the CLI integration.
To run a single test file:
python3 -m pytest tests/test_scorer.py -vTo run tests matching a pattern:
python3 -m pytest tests/ -k "readme" -v# Check for lint errors (does not modify files)
make lint
# Auto-format source and test files
make formatBoth commands target src/ and tests/. The project uses Ruff configured in pyproject.toml with target-version = "py311" and line-length = 100. Rules E, F, and I (errors, pyflakes, isort) are enabled.
CI will reject PRs that fail ruff check. Run make lint before opening a PR.
make type-checkThis runs mypy src/ --ignore-missing-imports. All new functions must have complete type annotations — no Any usage, no untyped parameters.
These conventions come from the project's CLAUDE.md and must be followed in all contributions:
- Type hints on all functions — parameters and return types, always. Use
from __future__ import annotationsat the top of each module. - f-strings — use f-strings for string interpolation, not
%formatting or.format(). pathliboveros.path— usePathobjects for all file system operations.os.path.join,os.listdir, etc. are not welcome.snake_casefor all file names, function names, and variable names.- No PyGithub — the project intentionally uses raw
requestscalls to the GitHub REST API v3. Do not introducePyGithub,ghapi, or any other GitHub client library. - No external analysis frameworks — keep the dependency footprint minimal. Do not add AST analysis libraries, complexity frameworks, or linting engines as runtime dependencies. Pure Python + stdlib is the default; add a dependency only when the value is unambiguous.
- No hardcoded usernames or tokens — credentials come from environment variables (
GITHUB_TOKEN,ANTHROPIC_API_KEY,NOTION_TOKEN). Usernames come from CLI arguments. - No silent error swallowing — always log or re-raise exceptions. The analyzer pipeline uses
logger.warning(...)before returning a zero-score fallback result; follow the same pattern.
Analyzers live in src/analyzers/. Each one scores a single dimension (0.0–1.0) and returns an AnalyzerResult.
Create src/analyzers/<your_dimension>.py and implement a class that extends BaseAnalyzer:
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from src.analyzers.base import BaseAnalyzer
from src.models import AnalyzerResult, RepoMetadata
if TYPE_CHECKING:
from src.github_client import GitHubClient
class YourDimensionAnalyzer(BaseAnalyzer):
name = "your_dimension"
weight = 0.05 # fraction of overall completeness score
def analyze(
self,
repo_path: Path,
metadata: RepoMetadata,
github_client: GitHubClient | None = None,
) -> AnalyzerResult:
score = 0.0
findings: list[str] = []
details: dict[str, object] = {}
# ... scoring logic ...
return self._result(score, findings, details)self._result(score, findings, details) clamps the score to [0.0, 1.0] and wraps it in an AnalyzerResult. Use it rather than constructing AnalyzerResult directly.
Open src/analyzers/__init__.py and:
- Import your new class at the top.
- Append an instance to
ALL_ANALYZERS.
from src.analyzers.your_dimension import YourDimensionAnalyzer
ALL_ANALYZERS = [
...
YourDimensionAnalyzer(),
]Open src/scorer.py and add your dimension name to the WEIGHTS dict. Weights must sum to 1.0 after adding the new entry, so adjust existing weights proportionally.
Add tests/test_your_dimension.py. Cover at minimum:
- A repo that scores the maximum (all checks pass).
- A repo that scores zero (no relevant files).
- At least one partial-credit case.
Tests should use real Path objects pointing to small fixture directories under tests/fixtures/, not mocked file systems.
Before opening a PR, verify:
-
make testpasses with no failures. -
make lintreports no errors. -
make type-checkreports no errors (or pre-existing errors only — do not introduce new ones). - No hardcoded GitHub usernames or API tokens anywhere in the diff.
- New analyzer (if any) is registered in
ALL_ANALYZERSand has a weight inWEIGHTS. - New tests added for any new public functions or analyzer logic.
-
CHANGELOG.mdupdated under## [Unreleased]with a brief description of the change. - Commit messages follow conventional commits:
feat:,fix:,chore:,refactor:,test:,docs:.
Open an issue on GitHub. Please include the output of audit --help and your Python version (python3 --version) when reporting bugs.