diff-contract 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diff_contract-0.1.1/LICENSE +21 -0
- diff_contract-0.1.1/PKG-INFO +225 -0
- diff_contract-0.1.1/README.md +204 -0
- diff_contract-0.1.1/pyproject.toml +46 -0
- diff_contract-0.1.1/setup.cfg +4 -0
- diff_contract-0.1.1/src/diff_contract/__init__.py +7 -0
- diff_contract-0.1.1/src/diff_contract/cli.py +436 -0
- diff_contract-0.1.1/src/diff_contract/contract.py +95 -0
- diff_contract-0.1.1/src/diff_contract/engine.py +199 -0
- diff_contract-0.1.1/src/diff_contract/sarif.py +92 -0
- diff_contract-0.1.1/src/diff_contract.egg-info/PKG-INFO +225 -0
- diff_contract-0.1.1/src/diff_contract.egg-info/SOURCES.txt +20 -0
- diff_contract-0.1.1/src/diff_contract.egg-info/dependency_links.txt +1 -0
- diff_contract-0.1.1/src/diff_contract.egg-info/entry_points.txt +2 -0
- diff_contract-0.1.1/src/diff_contract.egg-info/requires.txt +7 -0
- diff_contract-0.1.1/src/diff_contract.egg-info/top_level.txt +1 -0
- diff_contract-0.1.1/tests/test_cli.py +156 -0
- diff_contract-0.1.1/tests/test_contract.py +203 -0
- diff_contract-0.1.1/tests/test_engine.py +219 -0
- diff_contract-0.1.1/tests/test_init_templates.py +149 -0
- diff_contract-0.1.1/tests/test_sarif.py +99 -0
- diff_contract-0.1.1/tests/test_validate.py +97 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yunare Maia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: diff-contract
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Deterministic guardrails for AI-generated diffs — define what files can change, block violations.
|
|
5
|
+
Author-email: Yunare Maia <yunare@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yunaremaia/diff-contract
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pyyaml>=6.0
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
18
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
19
|
+
Requires-Dist: types-PyYAML; extra == "dev"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# diff-contract
|
|
23
|
+
|
|
24
|
+
**Deterministic guardrails for AI-generated diffs — define what files can change, block violations.**
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install diff-contract
|
|
28
|
+
diff-contract check --contract .diffcontract.yml
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## The Problem
|
|
32
|
+
|
|
33
|
+
AI coding tools (Cursor, Claude Code, Codex) sometimes modify unrelated files, introduce changes outside the intended scope, or drift from the original structure. `diff-contract` sits between AI-generated code and your repo, enforcing **deterministic** constraints — not relying on another AI pass to review.
|
|
34
|
+
|
|
35
|
+
> "Most tools either help generate code or review it after the fact, but there's no real control layer in between." — HN discussion, 2026
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### 1. Install
|
|
40
|
+
```bash
|
|
41
|
+
pip install diff-contract
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Define your contract
|
|
45
|
+
```yaml
|
|
46
|
+
# .diffcontract.yml
|
|
47
|
+
version: 1
|
|
48
|
+
rules:
|
|
49
|
+
- name: "Block core changes"
|
|
50
|
+
deny:
|
|
51
|
+
- "src/core/**"
|
|
52
|
+
- "*.env"
|
|
53
|
+
on_violation: block
|
|
54
|
+
|
|
55
|
+
- name: "Allow feature X"
|
|
56
|
+
allow:
|
|
57
|
+
- "src/features/X/**"
|
|
58
|
+
- "tests/features/X/**"
|
|
59
|
+
on_violation: block
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Check your diff
|
|
63
|
+
```bash
|
|
64
|
+
# Check current branch vs main
|
|
65
|
+
diff-contract check
|
|
66
|
+
|
|
67
|
+
# Check specific files
|
|
68
|
+
diff-contract check --files src/app.py src/utils.py
|
|
69
|
+
|
|
70
|
+
# JSON output (for CI)
|
|
71
|
+
diff-contract check --output json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 4. GitHub Action
|
|
75
|
+
```yaml
|
|
76
|
+
# .github/workflows/diff-contract.yml
|
|
77
|
+
name: diff-contract
|
|
78
|
+
on: pull_request
|
|
79
|
+
|
|
80
|
+
jobs:
|
|
81
|
+
check:
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
steps:
|
|
84
|
+
- uses: actions/checkout@v4
|
|
85
|
+
- uses: yunaremaia/diff-contract@main
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Validate files (no git required)
|
|
89
|
+
|
|
90
|
+
Validate specific files against your contract without a git diff — ideal for pre-commit hooks:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
diff-contract validate --files src/app.py tests/test_app.py
|
|
94
|
+
echo "src/foo.py" | diff-contract validate --from-stdin
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Initialize a contract
|
|
98
|
+
|
|
99
|
+
Create a starter `.diffcontract.yml`:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
diff-contract init # default Python project contract
|
|
103
|
+
diff-contract init --template react # React/Next.js
|
|
104
|
+
diff-contract init --template django # Django
|
|
105
|
+
diff-contract init --template rust # Rust workspace
|
|
106
|
+
diff-contract init --template docs # documentation-only
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Ready-to-use templates for React, Django, Rust, and documentation-only projects are available in the [`examples/`](examples/) directory.
|
|
110
|
+
|
|
111
|
+
## Pre-commit hook
|
|
112
|
+
|
|
113
|
+
diff-contract ships a pre-commit hook. Add to your `.pre-commit-config.yaml`:
|
|
114
|
+
|
|
115
|
+
```yaml
|
|
116
|
+
repos:
|
|
117
|
+
- repo: https://github.com/yunaremaia/diff-contract
|
|
118
|
+
rev: v0.1.0
|
|
119
|
+
hooks:
|
|
120
|
+
- id: diff-contract
|
|
121
|
+
args: ["--contract", ".diffcontract.yml"]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## SARIF Output (GitHub Code Scanning)
|
|
125
|
+
|
|
126
|
+
Generate SARIF 2.1.0 output for GitHub Code Scanning integration:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
diff-contract check --sarif > diff-contract.sarif
|
|
130
|
+
diff-contract validate --files src/foo.py --sarif
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
GitHub Actions workflow:
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
- uses: yunaremaia/diff-contract@main
|
|
137
|
+
with:
|
|
138
|
+
format: sarif
|
|
139
|
+
sarif-output: diff-contract.sarif
|
|
140
|
+
|
|
141
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
142
|
+
with:
|
|
143
|
+
sarif_file: diff-contract.sarif
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
SARIF output includes one rule per violation type. Block violations emit at `error` level, warnings at `warning`.
|
|
147
|
+
|
|
148
|
+
## Exit Codes
|
|
149
|
+
|
|
150
|
+
| Code | Meaning |
|
|
151
|
+
|------|---------|
|
|
152
|
+
| 0 | Clean — no violations |
|
|
153
|
+
| 1 | Block violation — file denied, outside allowed scope, or an aggregate limit exceeded with `on_violation: block` |
|
|
154
|
+
| 2 | Warning — non-blocking violation (e.g., large diff with `on_violation: warn`) |
|
|
155
|
+
|
|
156
|
+
## Rules
|
|
157
|
+
|
|
158
|
+
- **allow**: File globs that are permitted (all others blocked)
|
|
159
|
+
- **deny**: File globs that are denied (takes priority)
|
|
160
|
+
- **on_violation**: `block` (exit 1) or `warn` (exit 2)
|
|
161
|
+
- **max_files** / **max_lines**: optional aggregate size limits (see below)
|
|
162
|
+
|
|
163
|
+
## Aggregate Limits
|
|
164
|
+
|
|
165
|
+
Path allow/deny rules constrain *which* files may change. `max_files` and `max_lines` constrain *how large* a change set may be. They are evaluated against the whole diff after per-file rules run.
|
|
166
|
+
|
|
167
|
+
| Field | Meaning |
|
|
168
|
+
|-------|---------|
|
|
169
|
+
| `max_files` | Maximum number of changed files in the diff |
|
|
170
|
+
| `max_lines` | Maximum total changed lines (`added + deleted` from `git diff --numstat`) |
|
|
171
|
+
|
|
172
|
+
A rule may set either field, both, or neither. Limits are omitted by default (no size budget). When a limit is exceeded, the engine records an aggregate violation on the synthetic path `<aggregate>` and applies that rule's `on_violation`:
|
|
173
|
+
|
|
174
|
+
- `on_violation: block` — fail the check (exit code **1**). Use this to stop AI agents or CI from landing oversized diffs.
|
|
175
|
+
- `on_violation: warn` — report the overshoot but continue (exit code **2** if there are no block violations). Use this as a PR-size nudge.
|
|
176
|
+
|
|
177
|
+
Typical uses:
|
|
178
|
+
|
|
179
|
+
- Cap feature work so an agent cannot rewrite half the tree while implementing one ticket.
|
|
180
|
+
- Keep documentation or bugfix rules tight even when the path globs are broad.
|
|
181
|
+
- Warn on large diffs without blocking hotfixes.
|
|
182
|
+
|
|
183
|
+
Limits are compared against the **entire** change list, not only files that match that rule's `allow`/`deny` globs. A rule that only sets `max_files` / `max_lines` (no path patterns) is a global size guard.
|
|
184
|
+
|
|
185
|
+
### Example
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
# .diffcontract.yml
|
|
189
|
+
version: 1
|
|
190
|
+
rules:
|
|
191
|
+
- name: "Block core changes"
|
|
192
|
+
deny:
|
|
193
|
+
- "src/core/**"
|
|
194
|
+
- "*.env"
|
|
195
|
+
on_violation: block
|
|
196
|
+
|
|
197
|
+
- name: "Feature development"
|
|
198
|
+
allow:
|
|
199
|
+
- "src/features/**"
|
|
200
|
+
- "tests/features/**"
|
|
201
|
+
max_files: 15
|
|
202
|
+
max_lines: 400
|
|
203
|
+
on_violation: block
|
|
204
|
+
|
|
205
|
+
- name: "Large diff warning"
|
|
206
|
+
max_files: 20
|
|
207
|
+
max_lines: 600
|
|
208
|
+
on_violation: warn
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
In this contract:
|
|
212
|
+
|
|
213
|
+
- Changes under `src/core/**` or `*.env` are blocked.
|
|
214
|
+
- Feature-area diffs may proceed only if they stay within 15 files and 400 lines; exceeding either budget is a **block** (exit 1).
|
|
215
|
+
- Any diff larger than 20 files or 600 lines also produces a **warning** (exit 2 when nothing is blocked).
|
|
216
|
+
|
|
217
|
+
See [`examples/strict.yml`](examples/strict.yml) for a fuller contract that combines deny rules with per-rule size budgets.
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
MIT
|
|
222
|
+
|
|
223
|
+
# diff-contract
|
|
224
|
+
|
|
225
|
+

|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# diff-contract
|
|
2
|
+
|
|
3
|
+
**Deterministic guardrails for AI-generated diffs — define what files can change, block violations.**
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install diff-contract
|
|
7
|
+
diff-contract check --contract .diffcontract.yml
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## The Problem
|
|
11
|
+
|
|
12
|
+
AI coding tools (Cursor, Claude Code, Codex) sometimes modify unrelated files, introduce changes outside the intended scope, or drift from the original structure. `diff-contract` sits between AI-generated code and your repo, enforcing **deterministic** constraints — not relying on another AI pass to review.
|
|
13
|
+
|
|
14
|
+
> "Most tools either help generate code or review it after the fact, but there's no real control layer in between." — HN discussion, 2026
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### 1. Install
|
|
19
|
+
```bash
|
|
20
|
+
pip install diff-contract
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. Define your contract
|
|
24
|
+
```yaml
|
|
25
|
+
# .diffcontract.yml
|
|
26
|
+
version: 1
|
|
27
|
+
rules:
|
|
28
|
+
- name: "Block core changes"
|
|
29
|
+
deny:
|
|
30
|
+
- "src/core/**"
|
|
31
|
+
- "*.env"
|
|
32
|
+
on_violation: block
|
|
33
|
+
|
|
34
|
+
- name: "Allow feature X"
|
|
35
|
+
allow:
|
|
36
|
+
- "src/features/X/**"
|
|
37
|
+
- "tests/features/X/**"
|
|
38
|
+
on_violation: block
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 3. Check your diff
|
|
42
|
+
```bash
|
|
43
|
+
# Check current branch vs main
|
|
44
|
+
diff-contract check
|
|
45
|
+
|
|
46
|
+
# Check specific files
|
|
47
|
+
diff-contract check --files src/app.py src/utils.py
|
|
48
|
+
|
|
49
|
+
# JSON output (for CI)
|
|
50
|
+
diff-contract check --output json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 4. GitHub Action
|
|
54
|
+
```yaml
|
|
55
|
+
# .github/workflows/diff-contract.yml
|
|
56
|
+
name: diff-contract
|
|
57
|
+
on: pull_request
|
|
58
|
+
|
|
59
|
+
jobs:
|
|
60
|
+
check:
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v4
|
|
64
|
+
- uses: yunaremaia/diff-contract@main
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Validate files (no git required)
|
|
68
|
+
|
|
69
|
+
Validate specific files against your contract without a git diff — ideal for pre-commit hooks:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
diff-contract validate --files src/app.py tests/test_app.py
|
|
73
|
+
echo "src/foo.py" | diff-contract validate --from-stdin
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Initialize a contract
|
|
77
|
+
|
|
78
|
+
Create a starter `.diffcontract.yml`:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
diff-contract init # default Python project contract
|
|
82
|
+
diff-contract init --template react # React/Next.js
|
|
83
|
+
diff-contract init --template django # Django
|
|
84
|
+
diff-contract init --template rust # Rust workspace
|
|
85
|
+
diff-contract init --template docs # documentation-only
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Ready-to-use templates for React, Django, Rust, and documentation-only projects are available in the [`examples/`](examples/) directory.
|
|
89
|
+
|
|
90
|
+
## Pre-commit hook
|
|
91
|
+
|
|
92
|
+
diff-contract ships a pre-commit hook. Add to your `.pre-commit-config.yaml`:
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
repos:
|
|
96
|
+
- repo: https://github.com/yunaremaia/diff-contract
|
|
97
|
+
rev: v0.1.0
|
|
98
|
+
hooks:
|
|
99
|
+
- id: diff-contract
|
|
100
|
+
args: ["--contract", ".diffcontract.yml"]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## SARIF Output (GitHub Code Scanning)
|
|
104
|
+
|
|
105
|
+
Generate SARIF 2.1.0 output for GitHub Code Scanning integration:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
diff-contract check --sarif > diff-contract.sarif
|
|
109
|
+
diff-contract validate --files src/foo.py --sarif
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
GitHub Actions workflow:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
- uses: yunaremaia/diff-contract@main
|
|
116
|
+
with:
|
|
117
|
+
format: sarif
|
|
118
|
+
sarif-output: diff-contract.sarif
|
|
119
|
+
|
|
120
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
121
|
+
with:
|
|
122
|
+
sarif_file: diff-contract.sarif
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
SARIF output includes one rule per violation type. Block violations emit at `error` level, warnings at `warning`.
|
|
126
|
+
|
|
127
|
+
## Exit Codes
|
|
128
|
+
|
|
129
|
+
| Code | Meaning |
|
|
130
|
+
|------|---------|
|
|
131
|
+
| 0 | Clean — no violations |
|
|
132
|
+
| 1 | Block violation — file denied, outside allowed scope, or an aggregate limit exceeded with `on_violation: block` |
|
|
133
|
+
| 2 | Warning — non-blocking violation (e.g., large diff with `on_violation: warn`) |
|
|
134
|
+
|
|
135
|
+
## Rules
|
|
136
|
+
|
|
137
|
+
- **allow**: File globs that are permitted (all others blocked)
|
|
138
|
+
- **deny**: File globs that are denied (takes priority)
|
|
139
|
+
- **on_violation**: `block` (exit 1) or `warn` (exit 2)
|
|
140
|
+
- **max_files** / **max_lines**: optional aggregate size limits (see below)
|
|
141
|
+
|
|
142
|
+
## Aggregate Limits
|
|
143
|
+
|
|
144
|
+
Path allow/deny rules constrain *which* files may change. `max_files` and `max_lines` constrain *how large* a change set may be. They are evaluated against the whole diff after per-file rules run.
|
|
145
|
+
|
|
146
|
+
| Field | Meaning |
|
|
147
|
+
|-------|---------|
|
|
148
|
+
| `max_files` | Maximum number of changed files in the diff |
|
|
149
|
+
| `max_lines` | Maximum total changed lines (`added + deleted` from `git diff --numstat`) |
|
|
150
|
+
|
|
151
|
+
A rule may set either field, both, or neither. Limits are omitted by default (no size budget). When a limit is exceeded, the engine records an aggregate violation on the synthetic path `<aggregate>` and applies that rule's `on_violation`:
|
|
152
|
+
|
|
153
|
+
- `on_violation: block` — fail the check (exit code **1**). Use this to stop AI agents or CI from landing oversized diffs.
|
|
154
|
+
- `on_violation: warn` — report the overshoot but continue (exit code **2** if there are no block violations). Use this as a PR-size nudge.
|
|
155
|
+
|
|
156
|
+
Typical uses:
|
|
157
|
+
|
|
158
|
+
- Cap feature work so an agent cannot rewrite half the tree while implementing one ticket.
|
|
159
|
+
- Keep documentation or bugfix rules tight even when the path globs are broad.
|
|
160
|
+
- Warn on large diffs without blocking hotfixes.
|
|
161
|
+
|
|
162
|
+
Limits are compared against the **entire** change list, not only files that match that rule's `allow`/`deny` globs. A rule that only sets `max_files` / `max_lines` (no path patterns) is a global size guard.
|
|
163
|
+
|
|
164
|
+
### Example
|
|
165
|
+
|
|
166
|
+
```yaml
|
|
167
|
+
# .diffcontract.yml
|
|
168
|
+
version: 1
|
|
169
|
+
rules:
|
|
170
|
+
- name: "Block core changes"
|
|
171
|
+
deny:
|
|
172
|
+
- "src/core/**"
|
|
173
|
+
- "*.env"
|
|
174
|
+
on_violation: block
|
|
175
|
+
|
|
176
|
+
- name: "Feature development"
|
|
177
|
+
allow:
|
|
178
|
+
- "src/features/**"
|
|
179
|
+
- "tests/features/**"
|
|
180
|
+
max_files: 15
|
|
181
|
+
max_lines: 400
|
|
182
|
+
on_violation: block
|
|
183
|
+
|
|
184
|
+
- name: "Large diff warning"
|
|
185
|
+
max_files: 20
|
|
186
|
+
max_lines: 600
|
|
187
|
+
on_violation: warn
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
In this contract:
|
|
191
|
+
|
|
192
|
+
- Changes under `src/core/**` or `*.env` are blocked.
|
|
193
|
+
- Feature-area diffs may proceed only if they stay within 15 files and 400 lines; exceeding either budget is a **block** (exit 1).
|
|
194
|
+
- Any diff larger than 20 files or 600 lines also produces a **warning** (exit 2 when nothing is blocked).
|
|
195
|
+
|
|
196
|
+
See [`examples/strict.yml`](examples/strict.yml) for a fuller contract that combines deny rules with per-rule size budgets.
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT
|
|
201
|
+
|
|
202
|
+
# diff-contract
|
|
203
|
+
|
|
204
|
+

|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "diff-contract"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Deterministic guardrails for AI-generated diffs — define what files can change, block violations."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Yunare Maia", email = "yunare@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
]
|
|
20
|
+
dependencies = ["pyyaml>=6.0"]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
dev = ["mypy>=1.10", "pytest>=8.0", "ruff>=0.5", "types-PyYAML"]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
diff-contract = "diff_contract.cli:main"
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/yunaremaia/diff-contract"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
33
|
+
include = ["diff_contract*"]
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
testpaths = ["tests"]
|
|
37
|
+
python_files = ["test_*.py"]
|
|
38
|
+
PYTHONDONTWRITEBYTECODE = 1
|
|
39
|
+
|
|
40
|
+
[tool.ruff]
|
|
41
|
+
target-version = "py310"
|
|
42
|
+
line-length = 100
|
|
43
|
+
|
|
44
|
+
[tool.ruff.lint]
|
|
45
|
+
select = ["E", "F", "W", "I"]
|
|
46
|
+
ignore = ["E501"]
|