creditlint
creditlint is a Rust-native CLI for enforcing Git credit and authorship
metadata policy before unwanted credit markers land in project history.
It is built for teams that use AI-assisted development but do not want tools,
agents, or hosted workflows to silently add authorship-like metadata such as
Co-authored-by, Made with, or generated-tool trailers to commits, pull
requests, and merge messages.
creditlint does not try to detect whether code was AI-generated, and it is not
a legal compliance engine.
What It Checks
creditlint applies a repository policy to the Git metadata surfaces where
credit can appear:
- commit message text
- pull request title/body text when passed through
--message-file - Git author name/email
- Git committer name/email
- final merge or squash messages when a merge bot passes them to the CLI
It is designed to separate two concepts that often get blurred:
- Authorship and credit markers are policy-controlled.
- Provenance markers can be allowed without becoming authorship.
Why
Coding agents, bots, IDEs, and hosted workflows can write metadata that changes how authorship or contribution credit appears in Git history:
Co-authored-by: Codex <...>
Author: Cursor Agent <cursoragent@cursor.com>
Made with Cursor
Generated with Claude
That metadata can be useful for audit and provenance. The problem is when it is stored in fields that imply authorship or contribution credit without an explicit project policy.
creditlint is intentionally narrow: it validates metadata placement and
policy, not the origin of the code.
Current Status
The MVP CLI is implemented. The repository is in delivery preparation for public package and release channels.
Implemented surfaces:
creditlint check --message-filecreditlint check --stdincreditlint check --rangecreditlint audit --allcreditlint initcreditlint install-hookcreditlint github ruleset-pattern- Human and JSON output
- Rust native release artifacts
- Optional npm wrapper with platform package resolution
Planned public distribution channels:
- crates.io package metadata for the Rust CLI
- GitHub Release assets for prebuilt native binaries
- optional npm packages that resolve native binaries before local fallbacks
Install
Until public packages are published, install from this repository:
For local development:
After public package releases are available, consumers should prefer one of:
- the
creditlintcrate from crates.io - a prebuilt native binary from GitHub Releases
- the optional npm package for teams that already install developer tools through npm, pnpm, or npx
The native CLI does not require Node.js, pnpm, or npm in consuming repositories.
Quick Start
Create the default policy file in a Git repository:
Install the managed commit-msg hook:
Check one message:
|
Check pull-request commits in CI:
Audit all reachable Git history:
Generate a conservative GitHub ruleset regex for final squash commit messages:
Use JSON output for automation:
Exit codes:
0: no violations1: policy violations found2: invalid invocation, invalid config, unreadable input, or failed metadata collection
Policy
Without .creditlint.yml, creditlint uses a built-in default policy. That
default blocks common AI/tool authorship and credit markers while allowing
explicit provenance trailers.
Example policy file:
version: 1
rules:
forbidden_identities:
- name_pattern: "(?i)(cursor agent|codex|claude|copilot|openai|anthropic|gemini)"
email_pattern: "(?i)(cursoragent@cursor\\.com|codex|claude|copilot|openai|anthropic|gemini)"
forbidden_trailers:
- key: Co-authored-by
value_pattern: "(?i)(codex|claude|cursor|copilot|openai|anthropic|gemini|ai)"
- key_pattern: "(?i)^made[- ]with\\b.*$"
- key_pattern: "(?i)^made[- ]on\\b.*$"
- key_pattern: "(?i)^generated[- ]with\\b.*$"
allowed_provenance_trailers:
- AI-Assisted
- Tool-Used
- Generated-by
Policy evaluation covers:
- commit message text
- pull request title/body text when passed through
--message-file - Git author name/email
- Git committer name/email
Forbidden rules win before allowed provenance keys. Invalid config fails closed
with exit code 2.
Enforcement Model
creditlint should run at multiple layers because no single hook or CI job sees
every Git metadata surface.
Recommended layers:
- Local
commit-msghook for fast feedback. - CI required check for pull-request commits.
- Pull request title/body check by writing PR text to a temporary file and
running
creditlint check --message-file. - GitHub ruleset metadata restriction for final protected-branch commit messages when squash merge remains enabled.
- Merge-bot validation when the repository controls the final merge message.
The boundary is important: creditlint check --range validates proposed commits.
It does not by itself validate a final squash merge message edited or synthesized
by the hosting platform UI.
For stronger rollout guidance in another repository, use the repository-local skill:
skills/enforcement-rollout/SKILL.md
Copyable agent prompt:
Use the creditlint repository skill `enforcement-rollout` and help me deploy creditlint in this repository for the strongest practical interception. Check what is already covered, identify the remaining gaps across local hooks, CI commit checks, PR title/body checks, final squash/merge message enforcement, and repository settings, then give me an exact rollout plan.
GitHub Actions
Range checks need enough Git history to resolve the base revision. Use
fetch-depth: 0 or an equivalent fetch strategy.
name: creditlint
on:
pull_request:
push:
branches:
- main
jobs:
creditlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
- name: Build creditlint
run: cargo build --release
- name: Check pull request commits
if: github.event_name == 'pull_request'
run: |
./target/release/creditlint check \
--range origin/${{ github.base_ref }}..HEAD
- name: Audit full history on main
if: github.event_name == 'push'
run: ./target/release/creditlint audit --all
To validate pull request title/body text, write it to a file and run the same policy engine:
GitHub Rulesets And Merge Bots
Use creditlint github ruleset-pattern when the active policy can be safely
represented as one GitHub commit-message restriction regex.
Representable as one ruleset regex:
- forbidden trailer rules with an exact trailer key
- trailer value matchers that are exact strings, unanchored regexes, or
Any - free-form marker rules expressed as one anchored line regex
- policies where allowed provenance keys do not overlap forbidden trailer keys
Not representable as one ruleset regex:
- Git author or committer identity rules
- policies that need forbidden/allowed precedence on the same trailer key
- forbidden rules that depend on regex-matched trailer field names
- policy logic that needs normalization or more than one regex pass
When export is unsafe, the command fails closed. Use CI range checks for commit metadata and a controlled merge-bot validation step for the exact final merge message:
npm Wrapper
The npm package is optional. It is for teams that already install developer tools through npm, pnpm, or npx.
Normal npm consumers should not need Rust or Cargo. The creditlint npm package
delegates to a native binary from a platform-specific optional package such as
creditlint-darwin-arm64 or creditlint-linux-x64.
Install after npm packages are published:
Resolution order:
CREDITLINT_BIN- installed platform package binary
- package-local
packages/creditlint/native/ - repository-local Cargo outputs under
target/release/andtarget/debug/
The JavaScript wrapper does not reimplement policy logic or Git metadata collection.
Privacy
creditlint is local-first.
Default behavior:
- reads local message text, Git metadata, and
.creditlint.yml - does not upload commit messages or pull request text
- does not require a hosted account or background service
- does not use network access during policy evaluation
Any future hosted integration should be documented as a separate optional behavior.
Threat Model
The MVP is designed to catch:
- tools that append authorship-like markers
- contributors who accidentally paste AI/tool credit markers
- cloud-agent and CI paths that bypass local hooks
- platform merge paths where the final protected-branch message differs from checked commits
Current out of scope:
- Unicode homoglyph spoofing
- deliberately split or obfuscated markers
- administrator bypass of repository rules
- direct protected-branch writes outside the enforced workflow
Development
Use Cargo for Rust implementation work. Use pnpm only for OpenSpec commands and the optional npm wrapper workspace.
Common commands:
Local tooling:
- stable Rust from
rust-toolchain.toml justfor project recipescargo-nextestfor the preferred test runnercrossfor release packaging tasks- pnpm for OpenSpec and npm wrapper validation
OpenSpec commands:
Implementation work should follow the active OpenSpec change tasks before code or user-facing behavior changes are made.
Release And Publishing
Release preparation covers:
- crates.io for the Rust CLI
- GitHub Actions workflow artifacts for manual release runs
- GitHub Releases for tagged native binaries and
SHA256SUMS - optional npm platform packages plus the main npm wrapper
Useful maintainer commands:
Do not publish the main npm wrapper as a normal user-facing release until the matching platform packages have staged native binaries.
For crates.io publishing, the GitHub release workflow expects
CARGO_REGISTRY_TOKEN to be configured as a repository secret.
Versioning
The project intends to follow SemVer after the first public release.
Before that release:
- keep ongoing work in
CHANGELOG.mdunderUnreleased - bump package versions and cut a changelog heading in the same release change
- treat CLI flags, config schema, exit codes, and JSON output as versioned user contracts
License
MIT