Getting Started¶
Install¶
Requires Python 3.10 or newer. Verify the install:
Create a contract¶
init writes a starter .diffcontract.yml in the current directory. It never
overwrites an existing file — if one is already there it prints a warning and
exits 1.
Templates: python (default), react, django, rust, docs. See
init templates.
Alternatively, write the file yourself:
# .diffcontract.yml
version: 1
rules:
- name: "Block core changes"
deny:
- "src/core/**"
- "*.env"
on_violation: block
- name: "Allow feature X"
allow:
- "src/features/X/**"
- "tests/features/X/**"
on_violation: block
Check your branch¶
check runs git diff --numstat main...HEAD and validates every changed path.
The default base branch is main:
If the base branch has a different name:
The comparison is base...HEAD (three dots), so only commits on your branch
count — changes landed on main after the branch point are excluded. Your
working tree is not included: uncommitted edits do not affect the result.
Read the output¶
A clean diff:
Exit code 0.
A violation:
✗ 2 violation(s):
🔴 [BLOCK] File 'src/core/x.py' is denied by rule 'Block core changes'
🔴 [BLOCK] File 'src/core/x.py' not allowed by rule 'Allow feature X'
Exit code 1. Both lines are reported because two separate rules matched; the
first is a deny match, the second is an allow miss.
A warning:
Exit code 2.
Check specific files¶
To skip git entirely — useful in a hook, a script, or a repo that is not a git working tree — pass the paths yourself:
Piping a list works too, one path per line:
validate needs one of --files or --from-stdin; with neither it prints
ERROR: Provide --files or --from-stdin and exits 1.
Next steps¶
- Usage — every flag, and the exit-code contract
- Rules — how glob matching and deny precedence actually work
- Configuration — every key in the contract file
- CI — run it on every pull request