From b57c8d6d2d32007dbdd014512f8202afd794e725 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 10:43:15 +0000 Subject: [PATCH 1/4] docs: file the signoff fixes under v2.11.1, where they shipped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit listed #462 and #464 under v2.12.0, but both shipped in v2.11.1 — a release the changelog still had no section for at all. Moves the bullet to a new v2.11.1 section, which also closes the last gap between v2.11.0 and today. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn --- docs/changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 01fe8fa..86b0405 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -82,6 +82,11 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co to the last commit's author. A misconfigured identity used to pass whenever the previous commit happened to be valid. See PR [#461](https://github.com/commit-check/commit-check/pull/461). + +## v2.11.1 (2026-07-09) + +### Bug Fixes + * `require_signed_off_by` accepts any name, and is skipped for authors listed in `ignore_authors`. See PRs [#462](https://github.com/commit-check/commit-check/pull/462) and From 2a09dfad3a810c8442ecb97d118e8b9e5cb77ef0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 11:30:01 +0000 Subject: [PATCH 2/4] docs: consolidate 17 pages into 10 and guard the cross-links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The site had pre-commit.com's content volume spread across nearly twice the navigation destinations: nine pages under 100 lines, and four places where the same material was written twice — install steps in both installation.md and quickstart step 1, CLI invocations in both quickstart and example.md, releases in both what-is-new.md and changelog.md, and "what it checks" on the landing page restating the rules reference. Merges installation and quickstart into one Getting started page; the three deployment guides into Integrations; the two opt-in policy guides into Policy guides; why.md and projects.md into the landing page. what-is-new.md becomes a Highlights table at the top of the changelog that links into the entries below rather than restating them, so a release is written up once. rules.md and configuration.md do not move. Every released version of the package prints commit-check.com/rules/#ccNNN, hardcoded in rules_catalog.py, so that URL is a published interface rather than a documentation path. Every retired URL gets a redirect stub from the build hook — production is GitHub Pages, where netlify.toml has no effect — and the forced /getting-started/ redirect is gone now that a real page lives there. Also fixes what the merge surfaced: the GitHub Actions guide pinned commit-check-action@v1 when the action has been on v2 since v2.0.0, the changelog named the author options author_name/author_email rather than author_name_pattern/author_email_pattern, and the pre-commit rev was two minor versions behind. Version badges are dropped from the ecosystem cards; they went stale on their own. The pages cross-link by section anchor now, and a stale anchor is invisible in a rendered page — it just scrolls to the top. mkdocs validation for anchors and unrecognized links is turned on, so --strict fails the deploy build instead. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn --- docs/changelog.md | 60 ++-- docs/example.md | 6 +- .../quickstart.md => getting-started.md} | 67 +++- docs/getting-started/installation.md | 78 ----- docs/getting-started/why.md | 56 ---- docs/guides/ai-attribution.md | 75 ----- docs/guides/github-actions.md | 102 ------ docs/guides/integrations.md | 294 ++++++++++++++++++ docs/guides/organization.md | 100 ------ docs/guides/policies.md | 162 ++++++++++ docs/guides/pre-commit.md | 89 ------ docs/guides/signoff.md | 81 ----- docs/index.md | 182 +++++++---- docs/projects.md | 41 --- docs/what-is-new.md | 127 -------- mkdocs.yml | 22 +- netlify.toml | 19 +- scripts/mkdocs_hooks.py | 51 ++- 18 files changed, 739 insertions(+), 873 deletions(-) rename docs/{getting-started/quickstart.md => getting-started.md} (65%) delete mode 100644 docs/getting-started/installation.md delete mode 100644 docs/getting-started/why.md delete mode 100644 docs/guides/ai-attribution.md delete mode 100644 docs/guides/github-actions.md create mode 100644 docs/guides/integrations.md delete mode 100644 docs/guides/organization.md create mode 100644 docs/guides/policies.md delete mode 100644 docs/guides/pre-commit.md delete mode 100644 docs/guides/signoff.md delete mode 100644 docs/projects.md delete mode 100644 docs/what-is-new.md diff --git a/docs/changelog.md b/docs/changelog.md index 86b0405..252dc4c 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,7 +4,25 @@ All **notable changes** to this project will be documented in this file. Full changelog available at [GitHub releases](https://github.com/commit-check/commit-check/releases). -## v2.13.0 (2026-08-04) +## Highlights + +The releases worth knowing about, newest first. Each links to its full entry +below and to the page that documents the feature properly. + +| Version | What changed | Documented in | +|---|---|---| +| [2.13.0](#v2130) | Stable rule IDs in terminal output and JSON | [Rules reference](rules.md) | +| [2.12.0](#v2120) | Author name and email patterns became configurable | [CC101](rules.md#cc101) · [CC102](rules.md#cc102) | +| [2.11.0](#v2110) | AI attribution policy | [Policy guides](guides/policies.md#ai-attribution) | +| [2.10.0](#v2100) | `dependabot/` and `renovate/` branches accepted by default | [CC201](rules.md#cc201) | +| [2.9.0](#v290) | AI agent branch prefixes accepted by default | [CC201](rules.md#cc201) | +| [2.8.0](#v280) | Custom `message_pattern`; Python 3.9 dropped | [CC001](rules.md#cc001) | +| [2.7.0](#v270) | Force push blocking | [CC301](rules.md#cc301) | +| [2.6.0](#v260) | `--format json`, `--compact`, `--no-banner` | [Command-line recipes](example.md#output-for-scripts-and-ci) | +| [2.5.0](#v250) | Organization-wide config with `inherit_from` | [Integrations](guides/integrations.md#across-an-organization) | +| [2.0.0](#v200) | Configuration moved from YAML to TOML — breaking | [Migrating from v1](migration.md) | + +## v2.13.0 (2026-08-04) { #v2130 } ### New Features @@ -39,7 +57,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co [#518](https://github.com/commit-check/commit-check/pull/518) and [#519](https://github.com/commit-check/commit-check/pull/519). -## v2.12.2 (2026-08-01) +## v2.12.2 (2026-08-01) { #v2122 } ### Bug Fixes @@ -53,7 +71,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co rejected by `subject_imperative`. See PR [#496](https://github.com/commit-check/commit-check/pull/496). -## v2.12.1 (2026-07-31) +## v2.12.1 (2026-07-31) { #v2121 } ### Bug Fixes @@ -63,13 +81,13 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co * Hardened the `pip install` step flagged by SonarCloud code scanning. See PR [#479](https://github.com/commit-check/commit-check/pull/479). -## v2.12.0 (2026-07-24) +## v2.12.0 (2026-07-24) { #v2120 } ### New Features -* **Configurable author patterns** — `author_name` and `author_email` accept a - custom regex, so organisations can require their own naming or email domain - instead of the built-in patterns. +* **Configurable author patterns** — `author_name_pattern` and + `author_email_pattern` accept a custom regex, so organisations can require + their own naming or email domain instead of the built-in patterns. See PR [#459](https://github.com/commit-check/commit-check/pull/459). ### Bug Fixes @@ -83,7 +101,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co the previous commit happened to be valid. See PR [#461](https://github.com/commit-check/commit-check/pull/461). -## v2.11.1 (2026-07-09) +## v2.11.1 (2026-07-09) { #v2111 } ### Bug Fixes @@ -92,7 +110,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co See PRs [#462](https://github.com/commit-check/commit-check/pull/462) and [#464](https://github.com/commit-check/commit-check/pull/464). -## v2.11.0 (2026-07-06) +## v2.11.0 (2026-07-06) { #v2110 } ### New Features @@ -113,7 +131,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co * Migrated PyPI publishing to `pypa/gh-action-pypi-publish` * Removed OpenSSF Scorecard badge after evaluation (moved to Scorecard dashboard) -## v2.10.1 (2026-06-30) +## v2.10.1 (2026-06-30) { #v2101 } ### Bug Fixes @@ -131,7 +149,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co * Removed legacy YAML config parsing code from `util.py`. See PR [#444](https://github.com/commit-check/commit-check/pull/444). -## v2.10.0 (2026-06-26) +## v2.10.0 (2026-06-26) { #v2100 } ### New Features @@ -140,7 +158,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co so dependency update branches are automatically recognized. See PR [#442](https://github.com/commit-check/commit-check/pull/442). -## v2.9.0 (2026-06-22) +## v2.9.0 (2026-06-22) { #v290 } ### New Features @@ -149,7 +167,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co `DEFAULT_BRANCH_TYPES` so branches created by AI coding agents are recognized as valid. See PR [#438](https://github.com/commit-check/commit-check/pull/438). -## v2.8.1 (2026-06-22) +## v2.8.1 (2026-06-22) { #v281 } ### Chores @@ -159,7 +177,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co * Added SchemaStore IDE autocompletion support for `cchk.toml`. See PR [#433](https://github.com/commit-check/commit-check/pull/433). -## v2.8.0 (2026-06-13) +## v2.8.0 (2026-06-13) { #v280 } ### New Features @@ -175,7 +193,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co `py.typed` marker added for downstream type checkers. See PR [#424](https://github.com/commit-check/commit-check/pull/424). -## v2.7.1 (2026-06-08) +## v2.7.1 (2026-06-08) { #v271 } ### Chores @@ -183,7 +201,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co * Added commit-check vs GitHub Rulesets comparison table to the README. See PR [#419](https://github.com/commit-check/commit-check/pull/419). -## v2.7.0 (2026-05-16) +## v2.7.0 (2026-05-16) { #v270 } ### New Features @@ -208,7 +226,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co engineering, general), growing the total from 234 to 390. See PR [#414](https://github.com/commit-check/commit-check/pull/414). -## v2.6.0 (2026-04-20) +## v2.6.0 (2026-04-20) { #v260 } ### New Features @@ -219,7 +237,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co * Fixed `print_error_header` state handling so repeated validations stay consistent when `--compact` is used. -## v2.5.0 (2026-04-03) +## v2.5.0 (2026-04-03) { #v250 } ### New Features @@ -231,7 +249,7 @@ Full changelog available at [GitHub releases](https://github.com/commit-check/co * Fixed incorrect mock target in `test_main_with_message_empty_string_no_stdin_with_git`: was patching `commit_check.util.get_commit_info` (ineffective) instead of `commit_check.engine.get_commit_info`. -## v2.0.0 (2025-10-01) +## v2.0.0 (2025-10-01) { #v200 } !!! warning @@ -267,10 +285,10 @@ Architecture Redesign: See PR [#280](https://github.com/commit-check/commit-check/pull/280) -## v0.10.2 (2025-08-26) +## v0.10.2 (2025-08-26) { #v0102 } Last release before the big v2.0 changes. -## v0.1.0 (2022-11-02) +## v0.1.0 (2022-11-02) { #v010 } Initial release of commit-check. diff --git a/docs/example.md b/docs/example.md index 00bcd9a..84017fe 100644 --- a/docs/example.md +++ b/docs/example.md @@ -1,8 +1,8 @@ # Command-line recipes -Ways to invoke the checks directly. For wiring them into a workflow, see the -[pre-commit](guides/pre-commit.md) and [GitHub Actions](guides/github-actions.md) -guides instead — those cover the setup this page assumes you already have. +Ways to invoke the checks directly. For wiring them into a workflow, see +[Integrations](guides/integrations.md) instead — that covers the setup this page +assumes you already have. Every option, and the environment variable and TOML key that set it, is listed in [Configuration](configuration.md). diff --git a/docs/getting-started/quickstart.md b/docs/getting-started.md similarity index 65% rename from docs/getting-started/quickstart.md rename to docs/getting-started.md index e3deb6d..b3af7d4 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started.md @@ -1,16 +1,47 @@ -# Quick start +# Getting started -By the end of this page you will have Commit Check rejecting a bad commit -message on your machine, and you will understand what it is telling you. +By the end of this page you will have Commit Check installed, rejecting a bad +commit message on your machine, and wired into the two places it belongs. It takes about five minutes and needs nothing but a Git repository. ## 1. Install +=== "pip" + + ```console + $ pip install commit-check + ``` + +=== "uv" + + ```console + $ uv tool install commit-check + ``` + +=== "pipx" + + ```console + $ pipx install commit-check + ``` + +Verify the install: + ```console -$ pip install commit-check +$ commit-check --version ``` +The CLI is also available as `cchk`, which is the same program under a shorter +name. + +!!! tip "Supported Python versions" + + Commit Check supports Python 3.10 through 3.14, on Linux, macOS and Windows. + +Running it as a [pre-commit hook](guides/integrations.md#as-a-pre-commit-hook) +or a [GitHub Action](guides/integrations.md#in-github-actions) needs no +installation step at all — both fetch it for you. + ## 2. Watch it reject something Commit Check works with no configuration at all. Make a deliberately bad commit @@ -77,8 +108,8 @@ $ commit-check --branch `commit-check --message` never evaluates branch rules, and vice versa. Each check is selected by its own flag, so you can run exactly what a given hook - or CI job needs. The - [rules reference](../rules.md) lists which flag activates each rule. + or CI job needs. The [rules reference](rules.md) lists which flag activates + each rule. ## 5. Write down your policy @@ -109,8 +140,8 @@ $ commit-check --message Even with no config file, Conventional Commits, Conventional Branch, subject length limits of 5–80 characters, and author name/email patterns are - enforced. Check the *Default* column in the - [rules reference](../rules.md) before assuming a rule is off. + enforced. Check the *Default* column in the [rules reference](rules.md) + before assuming a rule is off. ## 6. Make it automatic @@ -126,7 +157,7 @@ belongs: A pre-commit hook rejects the message as you write it, so nothing bad reaches the branch in the first place. - [:octicons-arrow-right-24: Pre-commit guide](../guides/pre-commit.md) + [:octicons-arrow-right-24: Pre-commit hook](guides/integrations.md#as-a-pre-commit-hook) - :material-github:{ .lg .middle } __On every pull request__ @@ -135,14 +166,24 @@ belongs: A GitHub Action checks every commit in the PR and can comment on the PR with what needs fixing. - [:octicons-arrow-right-24: GitHub Actions guide](../guides/github-actions.md) + [:octicons-arrow-right-24: GitHub Actions](guides/integrations.md#in-github-actions) +## Verifying the download + +Releases are built with [SLSA Level 3](https://slsa.dev) provenance. To verify a +release artifact came from this repository's build pipeline: + +```console +$ gh attestation verify commit_check-*.whl --repo commit-check/commit-check +``` + ## Where to go next -- **[Rules reference](../rules.md)** — every rule, what it does, why it matters, +- **[Rules reference](rules.md)** — every rule, what it does, why it matters, and how to configure it. -- **[Configuration](../configuration.md)** — every option, its type and default, +- **[Configuration](configuration.md)** — every option, its type and default, plus the environment variable and CLI flag that override it. -- **[Why Commit Check](why.md)** — the reasoning behind the tool. +- **[Command-line recipes](example.md)** — checking a range, wiring up CI, + reading the JSON output. diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md deleted file mode 100644 index c915fc6..0000000 --- a/docs/getting-started/installation.md +++ /dev/null @@ -1,78 +0,0 @@ -# Installation - -Commit Check runs anywhere Python does, and ships as a GitHub Action and an MCP -server for the places it doesn't. - -## Command line - -=== "pip" - - ```console - $ pip install commit-check - ``` - -=== "uv" - - ```console - $ uv tool install commit-check - ``` - -=== "pipx" - - ```console - $ pipx install commit-check - ``` - -Verify the install: - -```console -$ commit-check --version -``` - -The CLI is also available as `cchk`, which is the same program under a shorter -name. - -!!! tip "Supported Python versions" - - Commit Check supports Python 3.10 through 3.14, on Linux, macOS and Windows. - -## As a pre-commit hook - -No installation step — [pre-commit](https://pre-commit.com) fetches it for you. -See the [pre-commit guide](../guides/pre-commit.md). - -## As a GitHub Action - -No installation step. See the -[GitHub Actions guide](../guides/github-actions.md). - -## Verifying the download - -Releases are built with [SLSA Level 3](https://slsa.dev) provenance. To verify a -release artifact came from this repository's build pipeline: - -```console -$ gh attestation verify commit_check-*.whl --repo commit-check/commit-check -``` - -## Next steps - -
- -- :material-rocket-launch-outline:{ .lg .middle } __Quick start__ - - --- - - Catch your first bad commit in five minutes. - - [:octicons-arrow-right-24: Quick start](quickstart.md) - -- :material-book-open-variant:{ .lg .middle } __Rules reference__ - - --- - - Every rule, what it does, and why it matters. - - [:octicons-arrow-right-24: Rules](../rules.md) - -
diff --git a/docs/getting-started/why.md b/docs/getting-started/why.md deleted file mode 100644 index f114312..0000000 --- a/docs/getting-started/why.md +++ /dev/null @@ -1,56 +0,0 @@ -# Why Commit Check - -## The problem - -Git history is a database that every team writes to and almost nobody validates. - -The cost shows up later, and indirectly. Release notes get written by hand -because commit subjects cannot be grouped. `git bisect` walks through merge -commits that record nothing but a sync. A commit is attributed to `ec2-user` -because a build box had no `user.name`. A contribution has to be rejected -months after the fact because it never carried a `Signed-off-by` trailer. - -None of these are caught by a linter, a type checker, or a test suite. They are -all caught by review, which means they are caught inconsistently, by whoever -happens to be looking, and only after the work is done. - -## The approach - -Commit Check treats commit metadata the way linters treat code: a policy written -down once, enforced identically everywhere, with a stable identifier for every -diagnostic so that findings can be discussed, suppressed, and tracked. - -**One config.** A single `cchk.toml` drives the CLI, the pre-commit hook, the -GitHub Action, and the MCP server. There is no second place where the rules can -disagree with themselves. - -**Fails where it is cheap.** The same check that runs in CI runs in your -`commit-msg` hook. Finding out that a subject is malformed takes a second -locally and a full CI cycle plus a force-push remotely. - -**Stable rule IDs.** Every rule has an ID like [CC003](../rules.md#cc003) that -never changes once released. You can cite it in a review comment, link to its -documentation, and eventually suppress it per-rule. - -**Explains itself.** A failure names the rule, quotes the offending value, says -how to fix it, and links to the reasoning. - -## Where it fits - -Commit Check is deliberately narrow: it validates *metadata*, not code. It is a -lightweight, open alternative to -[GitHub Enterprise metadata restrictions](https://docs.github.com/en/enterprise-server@3.11/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#metadata-restrictions) -and Bitbucket's paid -[Yet Another Commit Checker](https://marketplace.atlassian.com/apps/1211854/yet-another-commit-checker), -without requiring a particular forge or an enterprise plan. - -If you already run `ruff`, `eslint`, or `golangci-lint` on your source, Commit -Check is the equivalent for the commits that carry it. - -## What it is not - -- **Not a code linter.** It never reads your source files. -- **Not a replacement for review.** It enforces the mechanical rules so review - can spend its attention on the change itself. -- **Not opinionated by default.** Most rules are off until you turn them on. See - the [rules reference](../rules.md) for what applies out of the box. diff --git a/docs/guides/ai-attribution.md b/docs/guides/ai-attribution.md deleted file mode 100644 index 7ffdac7..0000000 --- a/docs/guides/ai-attribution.md +++ /dev/null @@ -1,75 +0,0 @@ -# Set an AI attribution policy - -AI coding tools add trailers to commit messages identifying themselves. Whether -that is welcome, required, or unacceptable is a decision each project makes for -itself — and the industry has landed in different places: - -- The **Linux kernel** added an `Assisted-by:` trailer, treating AI assistance - as something to disclose. -- **Some projects disallow AI-assisted contributions outright**, usually over - provenance and licensing. -- **Most projects have no stated position**, which means the question resurfaces - in every code review. - -Commit Check does not take a side. It gives you a way to enforce whichever -position your project has already taken, so it stops being relitigated. - -## The default: no opinion - -```toml -[commit] -ai_attribution = "ignore" # the default -``` - -[CC013](../rules.md#cc013) is off. Commits carrying AI trailers pass, and so do -commits without them. - -## Forbidding AI-attributed commits - -```toml title="cchk.toml" -[commit] -ai_attribution = "forbid" -``` - -Commits carrying a recognised AI signature now fail: - -```text -CC013 ai-attribution check failed ==> feat: add caching layer -AI attribution policy violation -Suggest: This project forbids AI-assisted commits. Remove AI trailers and re-commit. -Docs: https://commit-check.com/rules/#cc013 -``` - -### What counts as a signature - -Trailers and co-author lines naming Claude Code, GitHub Copilot, Codex, Gemini, -Cursor, Devin, Aider, Windsurf and Tabby, plus generic AI model patterns. - -!!! warning "This checks disclosure, not authorship" - - CC013 reads commit metadata. It detects a commit that *says* it was - AI-assisted; it cannot detect one that was AI-assisted and did not say so. - - Set against a policy of "no AI contributions", it is an honesty check on - contributors who are already following the rules — not an enforcement - mechanism against those who aren't. Be clear with yourself about which of - those you are buying. - -## Exempting automation - -Bots that legitimately carry AI trailers can be excluded: - -```toml title="cchk.toml" -[commit] -ai_attribution = "forbid" -ignore_authors = ["dependabot[bot]", "renovate[bot]"] -``` - -## Documenting the decision - -Whichever way you go, the config file is not where contributors look. State the -policy where they will see it — `CONTRIBUTING.md`, the pull request template — -and let Commit Check be the mechanism rather than the announcement. - -Enforcing an undocumented policy produces a confusing failure for somebody -acting in good faith. diff --git a/docs/guides/github-actions.md b/docs/guides/github-actions.md deleted file mode 100644 index 4dc95ae..0000000 --- a/docs/guides/github-actions.md +++ /dev/null @@ -1,102 +0,0 @@ -# Run in GitHub Actions - -Local hooks can be skipped with `--no-verify`. A CI check cannot, which makes -GitHub Actions the place where your policy is actually a policy. - -## Minimal setup - -```yaml title=".github/workflows/commit-check.yml" -name: Commit Check - -on: - push: - pull_request: - branches: [main] - -jobs: - commit-check: - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: actions/checkout@v5 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 # (1)! - - uses: commit-check/commit-check-action@v1 - with: - message: true - branch: true - author-name: true - author-email: true -``` - -1. Commit Check needs the full history to inspect every commit in the pull - request. Without this it only sees the most recent one. - -## Commenting on the pull request - -Instead of making contributors open the job log, have the Action post what -needs fixing directly on the PR: - -```yaml - - uses: commit-check/commit-check-action@v1 - with: - message: true - branch: true - pr-comments: ${{ github.event_name == 'pull_request' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -``` - -This needs extra permissions on the job: - -```yaml - permissions: - contents: read - pull-requests: write -``` - -## Reporting without failing - -While a team is adopting the policy, it is often better to report problems -without blocking merges. `dry-run` always exits `0`: - -```yaml - - uses: commit-check/commit-check-action@v1 - with: - message: true - dry-run: true -``` - -Turn it off once the history is clean. - -## Sharing config with local hooks - -The Action reads the same `cchk.toml` as the CLI, so a repository that already -has one needs no Action-specific configuration. That is the point: the rules -cannot drift between what a developer sees locally and what CI enforces. - -See [Configuration](../configuration.md) for where the file may live, and -[Organization-wide policy](organization.md) for sharing one across repositories. - -## Pull requests from forks - -A `pull_request` workflow triggered by a fork receives a **read-only** -`GITHUB_TOKEN`, and `permissions: pull-requests: write` does not override that. -`pr-comments` will therefore fail to post on fork pull requests unless the -repository has *Send write tokens to workflows from pull requests* enabled under -**Settings → Actions → General**. - -!!! warning "Do not reach for `pull_request_target` casually" - - `pull_request_target` does get a write token, but it runs in the context of - the base repository with access to its secrets. Checking out and executing - the fork's code under that trigger is the "pwn request" pattern and hands - repository access to anyone who can open a pull request. - - If you use it, check out the base branch only and never run code from the - pull request. - -The checks themselves still run on fork pull requests and still fail the build; -only the commenting is affected. diff --git a/docs/guides/integrations.md b/docs/guides/integrations.md new file mode 100644 index 0000000..df88061 --- /dev/null +++ b/docs/guides/integrations.md @@ -0,0 +1,294 @@ +# Integrations + +Commit Check runs in three places, and all three read the same `cchk.toml`. +That is the point: the rules cannot drift between what a developer sees locally +and what CI enforces. + +| Where | Catches problems | Can be bypassed | +|---|---|---| +| [Pre-commit hook](#as-a-pre-commit-hook) | As the message is written | Yes — `--no-verify` | +| [GitHub Actions](#in-github-actions) | On every pull request | No | +| [Organization config](#across-an-organization) | Everywhere at once | — | + +A hook gives fast feedback to people who want to follow the policy. The Action +is what makes it a policy. Most projects want both. + +## As a pre-commit hook + +A pre-commit hook is the cheapest place to enforce commit policy: the developer +finds out while they are still writing the message, not after a CI round trip. + +Add Commit Check to `.pre-commit-config.yaml`: + +```yaml title=".pre-commit-config.yaml" +repos: + - repo: https://github.com/commit-check/commit-check + rev: v2.12.2 + hooks: + - id: check-message + - id: check-branch + - id: check-author-name + - id: check-author-email +``` + +Then install the hooks. `check-message` runs at the `commit-msg` stage, so it +needs its own install step: + +```console +$ pre-commit install --hook-type commit-msg +$ pre-commit install +``` + +That is it. The next malformed commit message is rejected before it exists. + +!!! warning "If `check-message` never runs" + + It is almost always because `pre-commit install --hook-type commit-msg` was + not run — a plain `pre-commit install` only wires up the `pre-commit` stage. + More in [Troubleshooting](../troubleshoot.md). + +### Available hooks + +| Hook ID | Stage | Rules | +|---|---|---| +| `check-message` | `commit-msg` | [CC001–CC013](../rules.md#commit-message-rules) | +| `check-branch` | `pre-commit` | [CC201–CC202](../rules.md#branch-rules) | +| `check-author-name` | `pre-commit` | [CC101](../rules.md#cc101) | +| `check-author-email` | `pre-commit` | [CC102](../rules.md#cc102) | +| `check-no-force-push` | `pre-push` | [CC301](../rules.md#cc301) | + +`check-no-force-push` also needs its own install: + +```console +$ pre-commit install --hook-type pre-push +``` + +### Configuring without a TOML file + +Options can be passed as hook arguments, which keeps everything in one file: + +```yaml title=".pre-commit-config.yaml" +repos: + - repo: https://github.com/commit-check/commit-check + rev: v2.12.2 + hooks: + - id: check-message + args: + - --subject-imperative=true + - --subject-max-length=72 + - --allow-merge-commits=false +``` + +A `cchk.toml` is usually the better choice once you have more than a couple of +options, because CI and the CLI read it too. See +[Configuration](../configuration.md) for the precedence rules. + +### Skipping a hook + +Occasionally you need to get a commit through — a mid-rebase fixup, an +automated migration. `pre-commit` supports this natively: + +```console +$ SKIP=check-message git commit -m "wip" +``` + +!!! warning "Local hooks are not a policy boundary" + + Anyone can pass `--no-verify`. Hooks exist to give fast feedback to people + who want to follow the policy, not to stop people who don't. Pair them with + the [GitHub Action](#in-github-actions), which runs where it cannot be + skipped. + +## In GitHub Actions + +Local hooks can be skipped. A CI check cannot, which makes GitHub Actions the +place where your policy is actually a policy. + +```yaml title=".github/workflows/commit-check.yml" +name: Commit Check + +on: + push: + pull_request: + branches: [main] + +jobs: + commit-check: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 # (1)! + - uses: commit-check/commit-check-action@v2 + with: + message: true + branch: true + author-name: true + author-email: true +``` + +1. Commit Check needs the full history to inspect every commit in the pull + request. Without this it only sees the most recent one. + +The Action reads the same `cchk.toml` as the CLI, so a repository that already +has one needs no Action-specific configuration. + +### Commenting on the pull request + +Instead of making contributors open the job log, have the Action post what +needs fixing directly on the PR: + +```yaml + - uses: commit-check/commit-check-action@v2 + with: + message: true + branch: true + pr-comments: ${{ github.event_name == 'pull_request' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +This needs extra permissions on the job: + +```yaml + permissions: + contents: read + pull-requests: write +``` + +### Reporting without failing + +While a team is adopting the policy, it is often better to report problems +without blocking merges. `dry-run` always exits `0`: + +```yaml + - uses: commit-check/commit-check-action@v2 + with: + message: true + dry-run: true +``` + +Turn it off once the history is clean. + +### Pull requests from forks + +A `pull_request` workflow triggered by a fork receives a **read-only** +`GITHUB_TOKEN`, and `permissions: pull-requests: write` does not override that. +`pr-comments` will therefore fail to post on fork pull requests unless the +repository has *Send write tokens to workflows from pull requests* enabled under +**Settings → Actions → General**. + +!!! warning "Do not reach for `pull_request_target` casually" + + `pull_request_target` does get a write token, but it runs in the context of + the base repository with access to its secrets. Checking out and executing + the fork's code under that trigger is the "pwn request" pattern and hands + repository access to anyone who can open a pull request. + + If you use it, check out the base branch only and never run code from the + pull request. + +The checks themselves still run on fork pull requests and still fail the build; +only the commenting is affected. + +## Across an organization + +Copying `cchk.toml` into forty repositories works until the day you want to +change it. `inherit_from` lets each repository pull a shared base config and +override only what it genuinely needs. + +### The shared config + +Put the policy in a repository every project can read — GitHub's `.github` +repository is the conventional home: + +```toml title="my-org/.github → cchk.toml" +[commit] +conventional_commits = true +subject_imperative = true +subject_max_length = 72 +allow_merge_commits = false + +[branch] +conventional_branch = true +allow_branch_types = ["feature", "bugfix", "hotfix", "release", "chore"] +``` + +### Inheriting it + +Each repository then needs one line: + +```toml title="any-repo → .github/cchk.toml" +inherit_from = "github:my-org/.github:cchk.toml" +``` + +Local settings win, so a project with a different constraint overrides just that +one option: + +```toml title="a-repo-with-longer-subjects → .github/cchk.toml" +inherit_from = "github:my-org/.github:cchk.toml" + +[commit] +subject_max_length = 100 # everything else comes from the org config +``` + +### Pinning the version + +By default the shorthand resolves to the parent repository's default branch, +which means a change to the org config takes effect everywhere on the next run. +That is usually what you want. When it isn't, pin to a ref: + +```toml +inherit_from = "github:my-org/.github@v1:cchk.toml" +``` + +### Other sources + +=== "GitHub shorthand" + + ```toml + inherit_from = "github:my-org/.github:cchk.toml" + ``` + +=== "Local path" + + ```toml + inherit_from = "../../shared/org-cchk.toml" + ``` + + Useful in a monorepo, where the shared config is already checked out. + +=== "HTTPS URL" + + ```toml + inherit_from = "https://example.com/shared/cchk.toml" + ``` + + Plain HTTP is rejected. + +!!! warning "Inheritance fails open" + + If the parent config is unreachable — a network blip, a renamed file, a + private repository — Commit Check silently falls back to the local config + rather than failing the build. This keeps CI green during an outage, but it + also means a typo in `inherit_from` is easy to miss. Verify the merged + result when you first set it up: + + ```console + $ commit-check --message --format json + ``` + +### Rolling it out + +Turning on a strict policy across an organization at once produces a wall of +red. A gentler sequence: + +1. Ship the org config with [`dry-run`](#reporting-without-failing) enabled in + CI, so violations are reported but nothing blocks. +2. Look at what actually fails. Some rules will turn out to be wrong for some + teams — that is information, not an obstacle. +3. Turn off `dry-run` for repositories whose history is already clean. +4. Tighten the shared config over time. diff --git a/docs/guides/organization.md b/docs/guides/organization.md deleted file mode 100644 index 1da9fd0..0000000 --- a/docs/guides/organization.md +++ /dev/null @@ -1,100 +0,0 @@ -# Enforce one policy across an organization - -Copying `cchk.toml` into forty repositories works until the day you want to -change it. `inherit_from` lets each repository pull a shared base config and -override only what it genuinely needs. - -## The shared config - -Put the policy in a repository every project can read — GitHub's `.github` -repository is the conventional home: - -```toml title="my-org/.github → cchk.toml" -[commit] -conventional_commits = true -subject_imperative = true -subject_max_length = 72 -allow_merge_commits = false - -[branch] -conventional_branch = true -allow_branch_types = ["feature", "bugfix", "hotfix", "release", "chore"] -``` - -## Inheriting it - -Each repository then needs one line: - -```toml title="any-repo → .github/cchk.toml" -inherit_from = "github:my-org/.github:cchk.toml" -``` - -Local settings win, so a project with a different constraint overrides just that -one option: - -```toml title="a-repo-with-longer-subjects → .github/cchk.toml" -inherit_from = "github:my-org/.github:cchk.toml" - -[commit] -subject_max_length = 100 # everything else comes from the org config -``` - -## Pinning the version - -By default the shorthand resolves to the parent repository's default branch, -which means a change to the org config takes effect everywhere on the next run. -That is usually what you want. When it isn't, pin to a ref: - -```toml -inherit_from = "github:my-org/.github@v1:cchk.toml" -``` - -## Other sources - -=== "GitHub shorthand" - - ```toml - inherit_from = "github:my-org/.github:cchk.toml" - ``` - -=== "Local path" - - ```toml - inherit_from = "../../shared/org-cchk.toml" - ``` - - Useful in a monorepo, where the shared config is already checked out. - -=== "HTTPS URL" - - ```toml - inherit_from = "https://example.com/shared/cchk.toml" - ``` - - Plain HTTP is rejected. - -!!! warning "Inheritance fails open" - - If the parent config is unreachable — a network blip, a renamed file, a - private repository — Commit Check silently falls back to the local config - rather than failing the build. This keeps CI green during an outage, but it - also means a typo in `inherit_from` is easy to miss. Verify the merged - result when you first set it up: - - ```console - $ commit-check --message --format json - ``` - -## Rolling it out - -Turning on a strict policy across an organization at once produces a wall of -red. A gentler sequence: - -1. Ship the org config with `dry-run` enabled in CI, so violations are reported - but nothing blocks. -2. Look at what actually fails. Some rules will turn out to be wrong for some - teams — that is information, not an obstacle. -3. Turn off `dry-run` for repositories whose history is already clean. -4. Tighten the shared config over time. - -See the [GitHub Actions guide](github-actions.md) for the `dry-run` input. diff --git a/docs/guides/policies.md b/docs/guides/policies.md new file mode 100644 index 0000000..2fa3976 --- /dev/null +++ b/docs/guides/policies.md @@ -0,0 +1,162 @@ +# Policy guides + +Two rules are off by default because they encode a decision only your project +can make: whether contributors must certify the origin of their work, and +whether AI assistance is welcome, disclosed, or refused. + +Commit Check does not take a side on either. It gives you a way to enforce the +position you have already taken, so it stops being relitigated in every review. + +## Require signoff (DCO) + +Projects that use the [Developer Certificate of Origin](https://developercertificate.org/) +require every commit to carry a `Signed-off-by` trailer. The Linux kernel and +much of the CNCF work this way. + +A DCO bot rejecting a pull request after the fact is a poor experience: the +contributor has to rewrite history for every commit in the branch. Checking +locally fixes it before it becomes a problem. + +### Turn it on + +```toml title="cchk.toml" +[commit] +require_signed_off_by = true +``` + +This enables [CC012](../rules.md#cc012), which is off by default. + +### Signing off + +```console +$ git commit --signoff -m "fix: handle an empty config file" +``` + +The trailer is appended automatically from your `user.name` and `user.email`: + +```text +fix: handle an empty config file + +Signed-off-by: Your Name +``` + +Forgot it? Fix the last commit in place: + +```console +$ git commit --amend --signoff --no-edit +``` + +Fix a whole branch: + +```console +$ git rebase --signoff main +``` + +!!! tip "Make it automatic" + + Signing off is easy to forget. Combine this rule with the + [pre-commit hook](integrations.md#as-a-pre-commit-hook) so a missing trailer + is caught at commit time, not at review time. + +### Identity matters + +The DCO is a statement about who wrote the code, so it only means something if +the identity is real. [CC101](../rules.md#cc101) and +[CC102](../rules.md#cc102) check the committer name and email, and are enabled +by default when their check runs: + +```console +$ commit-check --author-name --author-email +``` + +To require a company address: + +```toml title="cchk.toml" +[commit] +author_email_pattern = "^.+@example\\.com$" +``` + +### Bots + +Automation cannot meaningfully sign the DCO, and forcing it to produces +meaningless trailers. Exempt bots instead: + +```toml title="cchk.toml" +[commit] +require_signed_off_by = true +ignore_authors = ["dependabot[bot]", "renovate[bot]"] +``` + +`ignore_authors` matches the commit author and any `Co-authored-by:` trailers. + +## AI attribution + +AI coding tools add trailers to commit messages identifying themselves. Whether +that is welcome, required, or unacceptable is a decision each project makes for +itself — and the industry has landed in different places: + +- The **Linux kernel** added an `Assisted-by:` trailer, treating AI assistance + as something to disclose. +- **Some projects disallow AI-assisted contributions outright**, usually over + provenance and licensing. +- **Most projects have no stated position**, which means the question resurfaces + in every code review. + +### The default: no opinion + +```toml +[commit] +ai_attribution = "ignore" # the default +``` + +[CC013](../rules.md#cc013) is off. Commits carrying AI trailers pass, and so do +commits without them. + +### Forbidding AI-attributed commits + +```toml title="cchk.toml" +[commit] +ai_attribution = "forbid" +``` + +Commits carrying a recognised AI signature now fail: + +```text +CC013 ai-attribution check failed ==> feat: add caching layer +AI attribution policy violation +Suggest: This project forbids AI-assisted commits. Remove AI trailers and re-commit. +Docs: https://commit-check.com/rules/#cc013 +``` + +Recognised signatures are trailers and co-author lines naming Claude Code, +GitHub Copilot, Codex, Gemini, Cursor, Devin, Aider, Windsurf and Tabby, plus +generic AI model patterns. + +!!! warning "This checks disclosure, not authorship" + + CC013 reads commit metadata. It detects a commit that *says* it was + AI-assisted; it cannot detect one that was AI-assisted and did not say so. + + Set against a policy of "no AI contributions", it is an honesty check on + contributors who are already following the rules — not an enforcement + mechanism against those who aren't. Be clear with yourself about which of + those you are buying. + +### Exempting automation + +Bots that legitimately carry AI trailers can be excluded: + +```toml title="cchk.toml" +[commit] +ai_attribution = "forbid" +ignore_authors = ["dependabot[bot]", "renovate[bot]"] +``` + +### Documenting the decision + +Whichever way you go, the config file is not where contributors look. State the +policy where they will see it — `CONTRIBUTING.md`, the pull request template — +and let Commit Check be the mechanism rather than the announcement. + +Enforcing an undocumented policy produces a confusing failure for somebody +acting in good faith. diff --git a/docs/guides/pre-commit.md b/docs/guides/pre-commit.md deleted file mode 100644 index 193f0f9..0000000 --- a/docs/guides/pre-commit.md +++ /dev/null @@ -1,89 +0,0 @@ -# Run as a pre-commit hook - -A pre-commit hook is the cheapest place to enforce commit policy: the developer -finds out while they are still writing the message, not after a CI round trip. - -## Setup - -Add Commit Check to `.pre-commit-config.yaml`: - -```yaml title=".pre-commit-config.yaml" -repos: - - repo: https://github.com/commit-check/commit-check - rev: v2.11.0 - hooks: - - id: check-message - - id: check-branch - - id: check-author-name - - id: check-author-email -``` - -Then install the hooks. `check-message` runs at the `commit-msg` stage, so it -needs its own install step: - -```console -$ pre-commit install --hook-type commit-msg -$ pre-commit install -``` - -That is it. The next malformed commit message is rejected before it exists. - -## Available hooks - -| Hook ID | Stage | Rules | -|---|---|---| -| `check-message` | `commit-msg` | [CC001–CC013](../rules.md#commit-message-rules) | -| `check-branch` | `pre-commit` | [CC201–CC202](../rules.md#branch-rules) | -| `check-author-name` | `pre-commit` | [CC101](../rules.md#cc101) | -| `check-author-email` | `pre-commit` | [CC102](../rules.md#cc102) | -| `check-no-force-push` | `pre-push` | [CC301](../rules.md#cc301) | - -`check-no-force-push` also needs its own install: - -```console -$ pre-commit install --hook-type pre-push -``` - -## Configuring without a TOML file - -Options can be passed as hook arguments, which keeps everything in one file: - -```yaml title=".pre-commit-config.yaml" -repos: - - repo: https://github.com/commit-check/commit-check - rev: v2.11.0 - hooks: - - id: check-message - args: - - --subject-imperative=true - - --subject-max-length=72 - - --allow-merge-commits=false -``` - -A `cchk.toml` is usually the better choice once you have more than a couple of -options, because CI and the CLI read it too. See -[Configuration](../configuration.md) for the precedence rules. - -## Skipping a hook - -Occasionally you need to get a commit through — a mid-rebase fixup, an -automated migration. `pre-commit` supports this natively: - -```console -$ SKIP=check-message git commit -m "wip" -``` - -!!! warning "Local hooks are not a policy boundary" - - Anyone can pass `--no-verify`. Hooks exist to give fast feedback to people - who want to follow the policy, not to stop people who don't. Pair them with - the [GitHub Action](github-actions.md), which runs where it cannot be - skipped. - -## Troubleshooting - -If `check-message` never seems to run, it is almost always because -`pre-commit install --hook-type commit-msg` was not run — a plain -`pre-commit install` only wires up the `pre-commit` stage. - -More in [Troubleshooting](../troubleshoot.md). diff --git a/docs/guides/signoff.md b/docs/guides/signoff.md deleted file mode 100644 index 578c37f..0000000 --- a/docs/guides/signoff.md +++ /dev/null @@ -1,81 +0,0 @@ -# Require signoff (DCO) - -Projects that use the [Developer Certificate of Origin](https://developercertificate.org/) -require every commit to carry a `Signed-off-by` trailer. The Linux kernel and -much of the CNCF work this way. - -A DCO bot rejecting a pull request after the fact is a poor experience: the -contributor has to rewrite history for every commit in the branch. Checking -locally fixes it before it becomes a problem. - -## Turn it on - -```toml title="cchk.toml" -[commit] -require_signed_off_by = true -``` - -This enables [CC012](../rules.md#cc012), which is off by default. - -## Signing off - -```console -$ git commit --signoff -m "fix: handle an empty config file" -``` - -The trailer is appended automatically from your `user.name` and `user.email`: - -```text -fix: handle an empty config file - -Signed-off-by: Your Name -``` - -Forgot it? Fix the last commit in place: - -```console -$ git commit --amend --signoff --no-edit -``` - -Fix a whole branch: - -```console -$ git rebase --signoff main -``` - -!!! tip "Make it automatic" - - Signing off is easy to forget. Combine this rule with the - [pre-commit hook](pre-commit.md) so a missing trailer is caught at commit - time, not at review time. - -## Identity matters - -The DCO is a statement about who wrote the code, so it only means something if -the identity is real. [CC101](../rules.md#cc101) and -[CC102](../rules.md#cc102) check the committer name and email, and are enabled -by default when their check runs: - -```console -$ commit-check --author-name --author-email -``` - -To require a company address: - -```toml title="cchk.toml" -[commit] -author_email_pattern = "^.+@example\\.com$" -``` - -## Bots - -Automation cannot meaningfully sign the DCO, and forcing it to produces -meaningless trailers. Exempt bots instead: - -```toml title="cchk.toml" -[commit] -require_signed_off_by = true -ignore_authors = ["dependabot[bot]", "renovate[bot]"] -``` - -`ignore_authors` matches the commit author and any `Co-authored-by:` trailers. diff --git a/docs/index.md b/docs/index.md index 01c36e1..0165a85 100644 --- a/docs/index.md +++ b/docs/index.md @@ -31,7 +31,7 @@ whatever your AI agent is committing on your behalf. ```yaml title=".pre-commit-config.yaml" repos: - repo: https://github.com/commit-check/commit-check - rev: v2.11.0 + rev: v2.12.2 hooks: - id: check-message - id: check-branch @@ -63,6 +63,59 @@ whatever your AI agent is committing on your behalf. } ``` +## Why it exists + +Git history is a database that every team writes to and almost nobody validates. + +The cost shows up later, and indirectly. Release notes get written by hand +because commit subjects cannot be grouped. `git bisect` walks through merge +commits that record nothing but a sync. A commit is attributed to `ec2-user` +because a build box had no `user.name`. A contribution has to be rejected months +after the fact because it never carried a `Signed-off-by` trailer. + +None of these are caught by a linter, a type checker, or a test suite. They are +all caught by review — which means inconsistently, by whoever happens to be +looking, and only after the work is done. + +Commit Check treats commit metadata the way linters treat code: a policy written +down once, enforced identically everywhere, with a stable identifier for every +diagnostic so findings can be discussed, cited, and tracked. + +
+ +- :material-file-cog-outline:{ .lg .middle } __One config__ + + --- + + A single `cchk.toml` drives the CLI, the pre-commit hook, the GitHub Action + and the MCP server. There is no second place where the rules can disagree + with themselves. + +- :material-lightning-bolt-outline:{ .lg .middle } __Fails where it is cheap__ + + --- + + The same check that runs in CI runs in your `commit-msg` hook. A malformed + subject costs a second locally, or a full CI cycle plus a force-push + remotely. + +- :material-tag-outline:{ .lg .middle } __Stable rule IDs__ + + --- + + Every rule has an ID like `CC003` that never changes once released. Cite it + in a review comment, link to its documentation, suppress it per-rule. + +- :material-shield-check:{ .lg .middle } __Built to be trusted__ + + --- + + SLSA Level 3 build provenance with artifact attestation you can verify + before installing. A failure names the rule, quotes the offending value, + and says how to fix it. + +
+ ## What it checks
@@ -101,7 +154,7 @@ whatever your AI agent is committing on your behalf. Require the `Signed-off-by` trailer locally, so contributors find out before CI rejects the pull request. - [:octicons-arrow-right-24: Signoff guide](guides/signoff.md) + [:octicons-arrow-right-24: Policy guides](guides/policies.md#require-signoff-dco) - :material-robot-outline:{ .lg .middle } __AI attribution__ @@ -110,7 +163,7 @@ whatever your AI agent is committing on your behalf. Whatever your project has decided about AI-assisted commits, enforce it mechanically instead of relitigating it in review. - [:octicons-arrow-right-24: AI attribution guide](guides/ai-attribution.md) + [:octicons-arrow-right-24: Policy guides](guides/policies.md#ai-attribution) - :material-office-building-outline:{ .lg .middle } __Org-wide policy__ @@ -119,34 +172,88 @@ whatever your AI agent is committing on your behalf. Inherit a base config from a shared repository, then let each project override only what it needs. - [:octicons-arrow-right-24: Organization guide](guides/organization.md) + [:octicons-arrow-right-24: Integrations](guides/integrations.md#across-an-organization)
-## Built to be trusted +## What it is not + +Commit Check is deliberately narrow: it validates *metadata*, not code. + +- **Not a code linter.** It never reads your source files. +- **Not a replacement for review.** It enforces the mechanical rules so review + can spend its attention on the change itself. +- **Not opinionated by default.** Most rules are off until you turn them on. See + the [rules reference](rules.md) for what applies out of the box. + +It is a lightweight, open alternative to +[GitHub Enterprise metadata restrictions](https://docs.github.com/en/enterprise-server@3.11/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#metadata-restrictions) +and Bitbucket's paid +[Yet Another Commit Checker](https://marketplace.atlassian.com/apps/1211854/yet-another-commit-checker), +without requiring a particular forge or an enterprise plan. If you already run +`ruff`, `eslint` or `golangci-lint` on your source, Commit Check is the +equivalent for the commits that carry it. + +## Ecosystem + +One policy engine, multiple enforcement surfaces. Write your `cchk.toml` once — +every surface reads the same file. + +```mermaid +graph TB + subgraph Policy["📄 cchk.toml"] + direction LR + Config[One policy file] + end + + subgraph Engine["⚙️ commit-check
(Python core)"] + direction LR + CLI[CLI & pre-commit] + API[Python API] + end + + subgraph Surfaces["🚀 Enforcement surfaces"] + Action[commit-check-action
GitHub Action] + MCP[commit-check-mcp
MCP Server] + end + + Config --> Engine + CLI --> Action + API --> MCP + Action --> CI[CI Pipeline] + MCP --> Agent[AI Coding Agent] +```
-- :material-shield-check:{ .lg .middle } __SLSA Level 3__ +- :fontawesome-brands-python: __commit-check__ --- - Build provenance with artifact attestation you can verify before - installing. + **Core engine** — Python CLI, library and pre-commit hooks. Runs every + validation the other surfaces expose. -- :material-tag-outline:{ .lg .middle } __Stable rule IDs__ + [:octicons-arrow-right-24: Getting started](getting-started.md) + [:octicons-arrow-right-24: Repo](https://github.com/commit-check/commit-check) + +- :material-github: __commit-check-action__ --- - Every diagnostic carries an ID like `CC003` that never changes, so you can - cite it in review, suppress it, or feed it to tooling. + **GitHub Action** — CI integration that posts results as check runs, job + summaries and pull request comments. -- :material-source-commit:{ .lg .middle } __Used in production__ + [:octicons-arrow-right-24: Guide](guides/integrations.md#in-github-actions) + [:octicons-arrow-right-24: Repo](https://github.com/commit-check/commit-check-action) + +- :material-robot: __commit-check-mcp__ --- - Running at Apache, Texas Instruments, Mila, and - [many more](https://github.com/commit-check/commit-check-action/network/dependents). + **MCP server** — exposes the validations as structured tools for AI coding + agents such as Claude Code, Cursor and Copilot. + + [:octicons-arrow-right-24: Repo](https://github.com/commit-check/commit-check-mcp)
@@ -233,48 +340,7 @@ whatever your AI agent is committing on your behalf. -## Ecosystem - -Commit Check is a family of projects — one engine, multiple surfaces. -Write your policy **once** in a `cchk.toml`, enforce it **everywhere**. - -
- -- :fontawesome-brands-python: __commit-check__ `v2.11.0` - - --- - - **Core engine** — Python CLI, library & pre-commit hooks. - - :material-star: AI attribution governance, message patterns, JSON output - - [:octicons-arrow-right-24: Docs](getting-started/installation.md) - [:octicons-arrow-right-24: Repo](https://github.com/commit-check/commit-check) - -- :material-github: __commit-check-action__ `v2.10.0` - - --- - - **GitHub Action** — seamless CI integration with PR comments. - - :material-star: Windows runner, PR title validation - - [:octicons-arrow-right-24: Docs](guides/github-actions.md) - [:octicons-arrow-right-24: Repo](https://github.com/commit-check/commit-check-action) - -- :material-robot: __commit-check-mcp__ `v0.1.7` - - --- - - **MCP Server** — structured tools for AI coding agents. - - :material-star: AI attribution governance, message patterns - - [:octicons-arrow-right-24: Repo](https://github.com/commit-check/commit-check-mcp) - -
- -[See all projects →](projects.md){ .md-button } +And [many more](https://github.com/commit-check/commit-check-action/network/dependents). ## Ready in two minutes @@ -286,8 +352,8 @@ $ commit-check --message --branch No configuration file needed to start — sensible defaults apply immediately, and you tighten them when you are ready. -[Install :octicons-arrow-right-24:](getting-started/installation.md){ .md-button .md-button--primary } -[Why Commit Check?](getting-started/why.md){ .md-button } +[Get started :octicons-arrow-right-24:](getting-started.md){ .md-button .md-button--primary } +[Rules reference](rules.md){ .md-button } --- diff --git a/docs/projects.md b/docs/projects.md deleted file mode 100644 index 8226890..0000000 --- a/docs/projects.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -hide: - - toc ---- - -# Projects - -The commit-check ecosystem is built on a simple architecture: **one policy engine, -multiple enforcement surfaces.** Write your `cchk.toml` once — every surface -reads the same file. - -```mermaid -graph TB - subgraph Policy["📄 cchk.toml"] - direction LR - Config[One policy file] - end - - subgraph Engine["⚙️ commit-check
(Python core)"] - direction LR - CLI[CLI & pre-commit] - API[Python API] - end - - subgraph Surfaces["🚀 Enforcement surfaces"] - Action[commit-check-action
GitHub Action] - MCP[commit-check-mcp
MCP Server] - end - - Config --> Engine - CLI --> Action - API --> MCP - Action --> CI[CI Pipeline] - MCP --> Agent[AI Coding Agent] -``` - -| Surface | What it does | Get started | -|---------|-------------|-------------| -| **commit-check** | CLI tool, pre-commit hooks, and Python library. The core engine that runs all validations. | [`commit-check/commit-check`](https://github.com/commit-check/commit-check) | -| **commit-check-action** | GitHub Action wrapping the core engine. Posts results as check runs, job summaries, and PR comments. | [`commit-check/commit-check-action`](https://github.com/commit-check/commit-check-action) | -| **commit-check-mcp** | MCP server that exposes validations as structured tools for AI coding agents (Claude Code, Cursor, Copilot, etc.). | [`commit-check/commit-check-mcp`](https://github.com/commit-check/commit-check-mcp) | diff --git a/docs/what-is-new.md b/docs/what-is-new.md deleted file mode 100644 index a64fc17..0000000 --- a/docs/what-is-new.md +++ /dev/null @@ -1,127 +0,0 @@ -# Release highlights - -The changes worth knowing about, newest first, each pointing at the page that -documents it properly. For the full record of every change, see the -[changelog](changelog.md). - -## 2.13.0 — Stable rule IDs - -Every diagnostic now carries an ID that never changes once released, so a -failure can be cited in review, looked up, or matched by tooling. - -```console -$ commit-check --message -CC003 subject-imperative check failed ==> docs: revamped the profile -Commit message should use imperative mood (e.g., 'fix bug' not 'fixed bug') -Suggest: Change the first verb to imperative form -Docs: https://commit-check.com/rules/#cc003 -``` - -The ID also appears in `--format json` as `rule_id`, next to a `docs_url`. In a -terminal that renders hyperlinks the ID *is* the link, and the `Docs:` line is -dropped — piped output and CI logs keep it. - -`docs/`, `ci/`, `test/`, `refactor/`, `build/`, `perf/` and `style/` also join -the default branch types. - -[:octicons-arrow-right-24: Rules reference](rules.md) - -## 2.12.0 — Custom author patterns - -`author_name` and `author_email` accept a regex of your own, so a team can -require its own naming convention or email domain rather than the built-in -patterns. - -```toml title="cchk.toml" -[commit] -author_email = "^.+@example\\.com$" -``` - -[:octicons-arrow-right-24: CC101](rules.md#cc101) · -[CC102](rules.md#cc102) - -## 2.11.0 — AI attribution policy - -Commits carrying the trailers AI coding tools add — Claude Code, Copilot, -Codex, Gemini, Cursor, Devin, Aider, Windsurf, Tabby — can now be rejected. - -```toml title="cchk.toml" -[commit] -ai_attribution = "forbid" # "ignore" is the default -``` - -Whether AI-assisted commits are acceptable is a policy question with no single -right answer, so this stays off until you turn it on. - -[:octicons-arrow-right-24: AI attribution guide](guides/ai-attribution.md) · -[CC013](rules.md#cc013) - -## 2.10.0 — Bot branch prefixes accepted by default - -`dependabot/` and `renovate/` branches pass branch validation without -configuration. Automation was previously failing a check it could not satisfy. - -[:octicons-arrow-right-24: CC201](rules.md#cc201) - -## 2.9.0 — AI agent branch prefixes accepted by default - -`ai/`, `claude/`, `codex/`, `copilot/` and `cursor/` joined the default branch -types, following -[Conventional Branch v1.1.0](https://conventional-branch.github.io/). - -[:octicons-arrow-right-24: CC201](rules.md#cc201) - -## 2.8.0 — Custom message patterns - -`message_pattern` replaces the generated Conventional Commits regex with one of -your own, for teams that already enforce a different format. - -```toml title="cchk.toml" -[commit] -message_pattern = "^PROJ-\\d+: .+" -``` - -This release also dropped Python 3.9. The minimum is now 3.10. - -[:octicons-arrow-right-24: CC001](rules.md#cc001) · -[Configuration](configuration.md) - -## 2.7.0 — Force push blocking - -A `pre-push` hook that refuses a force push to a shared branch, plus a -`--no-force-push` flag for running the same check by hand. - -```toml title="cchk.toml" -[push] -allow_force_push = false -``` - -[:octicons-arrow-right-24: CC301](rules.md#cc301) · -[Command-line recipes](example.md#blocking-force-pushes) - -## 2.6.0 — Output controls for scripts and CI - -`--format json` for machine-readable results, `--compact` for one line per -failure, and `--no-banner` to drop the ASCII art that only adds noise to a CI -log. - -[:octicons-arrow-right-24: Command-line recipes](example.md#output-for-scripts-and-ci) - -## 2.5.0 — Organization-wide configuration - -`inherit_from` lets a repository pull a shared base config and override only -what it needs, so a policy change no longer means editing every repository. - -```toml title=".github/cchk.toml" -inherit_from = "github:my-org/.github:cchk.toml" -``` - -[:octicons-arrow-right-24: Organization guide](guides/organization.md) - -## 2.0.0 — TOML configuration - -The configuration format moved from YAML to TOML, the CLI was simplified, and -settings became overridable by environment variable and command-line flag. -This is a breaking change from 1.x. - -[:octicons-arrow-right-24: Migrating from v1](migration.md) diff --git a/mkdocs.yml b/mkdocs.yml index 23ec40d..5b6975f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -17,6 +17,14 @@ edit_uri: edit/main/docs/ docs_dir: docs +# The deploy workflow builds with --strict, which turns these into errors. The +# pages cross-link by section anchor rather than by page alone, and an anchor +# that no longer exists is invisible in a rendered page — it just scrolls to the +# top. Failing the build is the only way that gets noticed. +validation: + unrecognized_links: warn + anchors: warn + theme: name: material language: en @@ -133,16 +141,10 @@ hooks: nav: - Home: index.md - - Getting started: - - Installation: getting-started/installation.md - - Quick start: getting-started/quickstart.md - - Why Commit Check: getting-started/why.md + - Getting started: getting-started.md - Guides: - - Pre-commit hook: guides/pre-commit.md - - GitHub Actions: guides/github-actions.md - - Organization-wide policy: guides/organization.md - - Signoff and DCO: guides/signoff.md - - AI attribution policy: guides/ai-attribution.md + - Integrations: guides/integrations.md + - Policy guides: guides/policies.md - Command-line recipes: example.md - Reference: - Rules: rules.md @@ -150,8 +152,6 @@ nav: - About: - Migrating from v1: migration.md - Troubleshooting: troubleshoot.md - - Release highlights: what-is-new.md - Changelog: changelog.md - - Projects: projects.md - Blog: - blog/index.md diff --git a/netlify.toml b/netlify.toml index 0d67e73..8478346 100644 --- a/netlify.toml +++ b/netlify.toml @@ -75,7 +75,7 @@ [[redirects]] from = "/what-is-new.html" - to = "/what-is-new/" + to = "/changelog/" status = 301 force = true @@ -101,7 +101,7 @@ [[redirects]] from = "/README.html" - to = "/getting-started/installation/" + to = "/getting-started/" status = 301 force = true @@ -117,10 +117,11 @@ status = 301 force = true -# "Getting started" is a nav section rather than a page, so the bare directory -# has nothing to serve. The old landing site published this URL. -[[redirects]] - from = "/getting-started/" - to = "/getting-started/quickstart/" - status = 301 - force = true +# "Getting started" used to be a nav section with nothing at its own URL, and +# this file redirected the bare directory to the quick start page. It is a real +# page now, so the redirect is gone — a forced redirect here would shadow it. +# +# The pages that were merged into it, and the other retired URLs, are covered by +# the stubs scripts/mkdocs_hooks.py writes into the build. Those are directory +# URLs with no page behind them, so unlike the .html rules above they cannot +# collide with anything and need no entry here. diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py index c3b30da..bb0f49b 100644 --- a/scripts/mkdocs_hooks.py +++ b/scripts/mkdocs_hooks.py @@ -20,15 +20,39 @@ "migration": "migration/", "troubleshoot": "troubleshoot/", "changelog": "changelog/", - "what-is-new": "what-is-new/", # The CLI reference is no longer a generated page; the flags it listed are # documented alongside the settings that control them. "cli_args": "configuration/", "cli": "configuration/", - "README": "getting-started/installation/", + "README": "getting-started/", + "what-is-new": "changelog/", "genindex": "", } +#: Retired page URL -> the page that absorbed it. +# +# Unlike LEGACY_URLS these are directory URLs, so the stub is written to +# ``/index.html``. Nothing is served at the old path any more, so the stub +# cannot shadow a real page the way a flat ``rules.html`` would, and it is +# written on every host rather than skipped on Netlify. +# +# Two URLs are deliberately absent: ``rules/`` and ``configuration/``. The rule +# IDs printed by every released version of the package link to +# ``commit-check.com/rules/#ccNNN`` — the URL is hardcoded in +# ``commit_check/rules_catalog.py`` — so those two pages do not move. +MOVED_URLS = { + "getting-started/installation": "getting-started/", + "getting-started/quickstart": "getting-started/", + "getting-started/why": "", + "guides/pre-commit": "guides/integrations/", + "guides/github-actions": "guides/integrations/", + "guides/organization": "guides/integrations/", + "guides/signoff": "guides/policies/", + "guides/ai-attribution": "guides/policies/", + "what-is-new": "changelog/", + "projects": "", +} + # Redirect stubs for the URLs the Sphinx site served. # The script carries the fragment across, because the links most worth keeping # alive are the per-rule ones (``rules.html#cc003``) and a plain redirect drops @@ -58,18 +82,27 @@ def on_post_build(config, **kwargs) -> None: - """Write a redirect stub for each URL the Sphinx site used to serve.""" + """Write a redirect stub for each URL this site no longer serves.""" site = Path(config["site_dir"]) - # On Netlify the stubs would be served in place of the real pages: Netlify - # resolves ``/rules/`` to the file ``rules.html``, so the stub that + # Deploy previews pass their own URL in, and it may arrive without the + # trailing slash the targets below are joined onto. + base = (config["site_url"] or "/").rstrip("/") + "/" + + # Retired pages first: these run on every host, Netlify included. The old + # path has no page of its own any more, so the stub is the only thing that + # can answer for it. + for old, target in MOVED_URLS.items(): + stub = site / old / "index.html" + stub.parent.mkdir(parents=True, exist_ok=True) + stub.write_text(REDIRECT.format(url=base + target), encoding="utf-8") + + # On Netlify the .html stubs would be served in place of the real pages: + # Netlify resolves ``/rules/`` to the file ``rules.html``, so the stub that # redirects to ``/rules/`` would shadow ``rules/index.html`` and loop. The # ``[[redirects]]`` table in netlify.toml covers the same URLs with real - # 301s, so the stubs are only written for hosts without redirects. + # 301s, so these are only written for hosts without redirects. if os.environ.get("NETLIFY") == "true": return - # Deploy previews pass their own URL in, and it may arrive without the - # trailing slash the targets below are joined onto. - base = (config["site_url"] or "/").rstrip("/") + "/" for legacy, target in LEGACY_URLS.items(): (site / f"{legacy}.html").write_text( REDIRECT.format(url=base + target), encoding="utf-8" From 410bc33b90d8c246d31bfb20b83351da71224943 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 11:49:16 +0000 Subject: [PATCH 3/4] docs: point the pre-commit examples at v2.13.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v2.13.0 shipped on 2026-08-04, so the changelog date that was a placeholder is now the real one and needs no change. The five pre-commit `rev:` pins across the guides, the landing page, the recipes and the configuration reference were spread over four different versions, none of them current. The blog post keeps its own pin — a dated post records what was true when it was written. Adds #514 to the v2.13.0 documentation entry, which the release notes list alongside the other three domain-move PRs. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn --- docs/changelog.md | 3 ++- docs/configuration.md | 2 +- docs/example.md | 2 +- docs/guides/integrations.md | 4 ++-- docs/index.md | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 252dc4c..1ca8a19 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -53,7 +53,8 @@ below and to the page that documents the feature properly. * The documentation, the landing page and the blog moved to [commit-check.com](https://commit-check.com) and are published from one domain. `docs.commit-check.com` URLs redirect. - See PRs [#515](https://github.com/commit-check/commit-check/pull/515), + See PRs [#514](https://github.com/commit-check/commit-check/pull/514), + [#515](https://github.com/commit-check/commit-check/pull/515), [#518](https://github.com/commit-check/commit-check/pull/518) and [#519](https://github.com/commit-check/commit-check/pull/519). diff --git a/docs/configuration.md b/docs/configuration.md index 736511e..04272c0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -188,7 +188,7 @@ The primary use case for CLI arguments is configuring commit-check in `.pre-comm ```yaml repos: - repo: https://github.com/commit-check/commit-check - rev: v2.5.0 + rev: v2.13.0 hooks: - id: check-message args: diff --git a/docs/example.md b/docs/example.md index 84017fe..1c38170 100644 --- a/docs/example.md +++ b/docs/example.md @@ -104,7 +104,7 @@ pushed: ```yaml title=".pre-commit-config.yaml" repos: - repo: https://github.com/commit-check/commit-check - rev: v2.11.0 + rev: v2.13.0 hooks: - id: check-no-force-push stages: [pre-push] diff --git a/docs/guides/integrations.md b/docs/guides/integrations.md index df88061..e108a11 100644 --- a/docs/guides/integrations.md +++ b/docs/guides/integrations.md @@ -23,7 +23,7 @@ Add Commit Check to `.pre-commit-config.yaml`: ```yaml title=".pre-commit-config.yaml" repos: - repo: https://github.com/commit-check/commit-check - rev: v2.12.2 + rev: v2.13.0 hooks: - id: check-message - id: check-branch @@ -70,7 +70,7 @@ Options can be passed as hook arguments, which keeps everything in one file: ```yaml title=".pre-commit-config.yaml" repos: - repo: https://github.com/commit-check/commit-check - rev: v2.12.2 + rev: v2.13.0 hooks: - id: check-message args: diff --git a/docs/index.md b/docs/index.md index 0165a85..adff20c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -31,7 +31,7 @@ whatever your AI agent is committing on your behalf. ```yaml title=".pre-commit-config.yaml" repos: - repo: https://github.com/commit-check/commit-check - rev: v2.12.2 + rev: v2.13.0 hooks: - id: check-message - id: check-branch From 3a62d919c11c26d5b9726b532337f5138f888ae3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 11:51:56 +0000 Subject: [PATCH 4/4] ci: check the docs against the released package, not main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The docs-sync job installed commit-check from main because the rule IDs this site documents were merged but unreleased, and PyPI would have reported a mismatch that was not real. v2.13.0 is on PyPI now, so that exception has expired — and while it stood, the job was validating the site against code no reader could install. Left unpinned on purpose: the check is meaningful only against the version a reader gets from `pip install commit-check`, and a pin would let the site and the package drift until somebody remembered to raise it. Verified against the released 2.13.0 rather than the working tree — all seven checks pass, so the rule names, headings and documented defaults on the site match the package as shipped. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn --- .github/workflows/deploy.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c2c6b27..7f2b865 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -25,15 +25,20 @@ jobs: with: python-version: '3.x' - # Installed from main rather than PyPI: the site documents rule IDs that - # are merged but not yet released, so the published package would report - # a spurious mismatch. Switch this to the released package once a version - # carrying them is on PyPI. - - name: Install commit-check from main + # The released package, deliberately unpinned. This site documents the + # version a reader can actually install, so the check is only meaningful + # against that — pinning would let the two drift apart silently until + # somebody bumped the pin. + # + # This used to install from main, because the rule IDs the site documents + # were merged but unreleased and PyPI would have reported a spurious + # mismatch. v2.13.0 shipped them, so the exception is over. If a future + # release is documented here ahead of time, point this back at main for + # as long as that is true, and no longer. + - name: Install the released commit-check run: | python -m pip install --upgrade pip - python -m pip install pytest - python -m pip install "commit-check @ git+https://github.com/commit-check/commit-check@main" + python -m pip install pytest commit-check - name: Check the docs against the package run: pytest tests/ -q