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.
@@ -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
+ ![CI](https://github.com/yunaremaia/diff-contract/actions/workflows/ci.yml/badge.svg)
@@ -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
+ ![CI](https://github.com/yunaremaia/diff-contract/actions/workflows/ci.yml/badge.svg)
@@ -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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,7 @@
1
+ """diff-contract — deterministic guardrails for ai-generated diffs."""
2
+
3
+ from diff_contract.engine import GitDiffError
4
+
5
+ __version__ = "0.1.1"
6
+
7
+ __all__ = ["__version__", "GitDiffError"]