diffimpactscout 0.1.0__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.
- diffimpactscout-0.1.0/LICENSE +21 -0
- diffimpactscout-0.1.0/PKG-INFO +314 -0
- diffimpactscout-0.1.0/README.md +297 -0
- diffimpactscout-0.1.0/pyproject.toml +7 -0
- diffimpactscout-0.1.0/setup.cfg +4 -0
- diffimpactscout-0.1.0/setup.py +22 -0
- diffimpactscout-0.1.0/src/diffimpactscout/__init__.py +3 -0
- diffimpactscout-0.1.0/src/diffimpactscout/__main__.py +8 -0
- diffimpactscout-0.1.0/src/diffimpactscout/base.py +95 -0
- diffimpactscout-0.1.0/src/diffimpactscout/checks/__init__.py +20 -0
- diffimpactscout-0.1.0/src/diffimpactscout/checks/base.py +206 -0
- diffimpactscout-0.1.0/src/diffimpactscout/checks/eslint.py +164 -0
- diffimpactscout-0.1.0/src/diffimpactscout/checks/hygiene.py +104 -0
- diffimpactscout-0.1.0/src/diffimpactscout/checks/prettier.py +103 -0
- diffimpactscout-0.1.0/src/diffimpactscout/checks/repo_checks.py +95 -0
- diffimpactscout-0.1.0/src/diffimpactscout/checks/ruff.py +210 -0
- diffimpactscout-0.1.0/src/diffimpactscout/checks/syntax.py +133 -0
- diffimpactscout-0.1.0/src/diffimpactscout/cli.py +220 -0
- diffimpactscout-0.1.0/src/diffimpactscout/config.py +205 -0
- diffimpactscout-0.1.0/src/diffimpactscout/env.py +29 -0
- diffimpactscout-0.1.0/src/diffimpactscout/gitrun.py +68 -0
- diffimpactscout-0.1.0/src/diffimpactscout/guard.py +116 -0
- diffimpactscout-0.1.0/src/diffimpactscout/impact/__init__.py +1 -0
- diffimpactscout-0.1.0/src/diffimpactscout/impact/cache.py +55 -0
- diffimpactscout-0.1.0/src/diffimpactscout/impact/diff_parser.py +181 -0
- diffimpactscout-0.1.0/src/diffimpactscout/impact/impact.py +345 -0
- diffimpactscout-0.1.0/src/diffimpactscout/impact/python_analyzer.py +284 -0
- diffimpactscout-0.1.0/src/diffimpactscout/impact/reporter.py +107 -0
- diffimpactscout-0.1.0/src/diffimpactscout/impact/route_linker.py +363 -0
- diffimpactscout-0.1.0/src/diffimpactscout/launcher.py +108 -0
- diffimpactscout-0.1.0/src/diffimpactscout/profiles/django.json +14 -0
- diffimpactscout-0.1.0/src/diffimpactscout/profiles/fastapi.json +14 -0
- diffimpactscout-0.1.0/src/diffimpactscout/profiles/plain.json +8 -0
- diffimpactscout-0.1.0/src/diffimpactscout/scope.py +210 -0
- diffimpactscout-0.1.0/src/diffimpactscout.egg-info/PKG-INFO +314 -0
- diffimpactscout-0.1.0/src/diffimpactscout.egg-info/SOURCES.txt +38 -0
- diffimpactscout-0.1.0/src/diffimpactscout.egg-info/dependency_links.txt +1 -0
- diffimpactscout-0.1.0/src/diffimpactscout.egg-info/entry_points.txt +2 -0
- diffimpactscout-0.1.0/src/diffimpactscout.egg-info/top_level.txt +1 -0
- diffimpactscout-0.1.0/tests/test_fixture_smoke.py +115 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DiffImpactScout contributors
|
|
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,314 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: diffimpactscout
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Incremental pre-push guard and AST blast-radius impact analyzer for git repositories.
|
|
5
|
+
Author: DiffImpactScout contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.6
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Dynamic: author
|
|
11
|
+
Dynamic: description
|
|
12
|
+
Dynamic: description-content-type
|
|
13
|
+
Dynamic: license
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
Dynamic: requires-python
|
|
16
|
+
Dynamic: summary
|
|
17
|
+
|
|
18
|
+
# DiffImpactScout
|
|
19
|
+
|
|
20
|
+
An incremental pre-push guard and AST blast-radius impact analyzer for git repositories.
|
|
21
|
+
|
|
22
|
+
- Runs a battery of hygiene, syntax, and linter checks on just the files you changed, before you push.
|
|
23
|
+
- Estimates the blast radius of a change: which Python symbols you touched and where they are referenced across Python, Django templates, and frontend code.
|
|
24
|
+
- Installs as a git pre-push hook that is non-blocking by default, so it informs without getting in the way.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- Incremental checks: every check is scoped to the change-set or changed lines, not the whole repository.
|
|
29
|
+
- Auto-fixing hygiene checks (line endings, trailing whitespace, end-of-file newline) that clean files in place.
|
|
30
|
+
- Python AST symbol map: detects changed classes, functions, methods, module fields, and class fields, including deletions and renames.
|
|
31
|
+
- Cross-layer impact linking: Python symbols to Django `{% url %}` template tags and frontend `http.get()/post()`-style calls.
|
|
32
|
+
- Symbol cache (`.impact_analysis_cache.json`) so repeated runs skip unchanged files.
|
|
33
|
+
- Profiles for `plain`, `django`, and `fastapi` projects.
|
|
34
|
+
- Configurable pre-push hook installation that never silently clobbers an existing hook.
|
|
35
|
+
|
|
36
|
+
## Core idea
|
|
37
|
+
|
|
38
|
+
DiffImpactScout answers one question for a developer about to push: did I break something?
|
|
39
|
+
|
|
40
|
+
- **Only your changes are analyzed.** Every guard check and the impact report are computed against the diff between your branch and the resolved change base (upstream master or sprint branch when present, otherwise closest origin branch). Pre-existing violations that arrived from upstream or from sync merges are not re-reported, so a run answers "did I introduce this?" instead of "does this file have a problem?"
|
|
41
|
+
- **Non-blocking by default.** A push is only blocked when strictness is explicitly requested (strict mode or an always-block check). Default runs inform without getting in the way.
|
|
42
|
+
- **Fixes over complaints.** Hygiene problems that can be auto-fixed are fixed in place instead of failing the run.
|
|
43
|
+
- **Blast radius, not just lint.** The impact report shows which code outside your diff could be affected by your change, so the "not my change" threshold is applied to blame, not to risk.
|
|
44
|
+
|
|
45
|
+
The change-set also handles the full breadth of realistic push shapes: direct pushes, force pushes, new branches, sync merges (merge, rebase, squash), stacked branches, shallow clones, repos without a remote anchor, and fork upstream sync. All are covered by the automated pre-push scenario suite, so a missing upstream anchor never silently means "nothing was checked".
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
pip install diffimpactscout
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The package is pure Python (standard library only at runtime) and requires Python 3.6 or newer and the `git` CLI on `PATH`.
|
|
54
|
+
|
|
55
|
+
Optional tools are only needed for the checks that use them: `ruff`, `eslint` (via `npx`), and `prettier` (via `npx`). If one is missing, the affected check emits a warning and the guard continues; it never blocks a push because a tool is absent.
|
|
56
|
+
|
|
57
|
+
## Quick start
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
# 1. Write a .diffimpactscout.json config (optional; defaults are used without one)
|
|
61
|
+
diffimpactscout init
|
|
62
|
+
|
|
63
|
+
# 2. Run the guard checks on your change-set
|
|
64
|
+
diffimpactscout guard
|
|
65
|
+
|
|
66
|
+
# 3. Analyze the blast radius of your change
|
|
67
|
+
diffimpactscout impact
|
|
68
|
+
|
|
69
|
+
# 4. Run the guard automatically on every push
|
|
70
|
+
diffimpactscout install-hooks
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`init` accepts `--profile plain|django|fastapi` to seed profile-appropriate settings.
|
|
74
|
+
|
|
75
|
+
## Commands
|
|
76
|
+
|
|
77
|
+
The `init` command only needs a directory; every other command must be run inside a git repository.
|
|
78
|
+
|
|
79
|
+
### `diffimpactscout init`
|
|
80
|
+
|
|
81
|
+
Writes a `.diffimpactscout.json` into the current directory. If the file already exists it is left unchanged.
|
|
82
|
+
|
|
83
|
+
| Flag | Description |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `--profile {plain,django,fastapi}` | Seed defaults from the named profile (default: `plain`). |
|
|
86
|
+
|
|
87
|
+
### `diffimpactscout guard`
|
|
88
|
+
|
|
89
|
+
Runs every check listed in `guard.checks` over the change-set and prints any issues. Exits non-zero only when the run is in blocking mode and a blocking check found issues (see [Pre-push hook](#pre-push-hook) and [Configuration](#configuration)).
|
|
90
|
+
|
|
91
|
+
| Flag | Description |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `--staged` | Check only staged files (`git diff --cached`). |
|
|
94
|
+
| `--all` | Check every file tracked by git. |
|
|
95
|
+
| `files...` | Check only the given paths. |
|
|
96
|
+
|
|
97
|
+
With no flags, the change-set is the diff between your branch and the closest remote ref (upstream preferred, then origin), falling back to the union of committed, staged, and worktree changes when no remote base can be resolved.
|
|
98
|
+
|
|
99
|
+
### `diffimpactscout impact`
|
|
100
|
+
|
|
101
|
+
Analyzes the blast radius of the change-set. In an interactive terminal it prints the report and asks `Proceed with push? (Y/n)`; answering `n` or `no` exits non-zero.
|
|
102
|
+
|
|
103
|
+
| Flag | Description |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `--staged` | Analyze the staged diff (base ref becomes `HEAD`). |
|
|
106
|
+
| `--fast` | Skip template and frontend scanning; limit Python scanning to the changed files plus import-linked files. |
|
|
107
|
+
| `--json` | Emit the report as JSON (`{changed_count, rows, unresolved}`) instead of a markdown table. |
|
|
108
|
+
|
|
109
|
+
### `diffimpactscout check CHECK_ID [files...]`
|
|
110
|
+
|
|
111
|
+
Runs a single check by id (see [Checks reference](#checks-reference)). With no file arguments it runs against the change-set.
|
|
112
|
+
|
|
113
|
+
### `diffimpactscout install-hooks`
|
|
114
|
+
|
|
115
|
+
Installs a `pre-push` git hook that runs `diffimpactscout guard` on push.
|
|
116
|
+
|
|
117
|
+
| Flag | Description |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| `--force` | Overwrite an existing `pre-push` hook that DiffImpactScout did not install. |
|
|
120
|
+
| `--uninstall` | Remove the DiffImpactScout `pre-push` hook. |
|
|
121
|
+
|
|
122
|
+
### `diffimpactscout --version`
|
|
123
|
+
|
|
124
|
+
Prints the installed version.
|
|
125
|
+
|
|
126
|
+
## Configuration
|
|
127
|
+
|
|
128
|
+
DiffImpactScout is configured by a `.diffimpactscout.json` file in the repository root. Without one, the defaults below are used.
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"version": 1,
|
|
133
|
+
"mode": "pre-push",
|
|
134
|
+
"ignore_paths": [
|
|
135
|
+
"**/node_modules/**",
|
|
136
|
+
"**/venv/**",
|
|
137
|
+
"**/.venv/**",
|
|
138
|
+
"**/migrations/**",
|
|
139
|
+
"**/staticfiles/**",
|
|
140
|
+
"**/dist/**",
|
|
141
|
+
"**/build/**",
|
|
142
|
+
"**/.git/**"
|
|
143
|
+
],
|
|
144
|
+
"use_gitignore": false,
|
|
145
|
+
"guard": {
|
|
146
|
+
"checks": [
|
|
147
|
+
{"id": "hygiene/mixed-line-ending"},
|
|
148
|
+
{"id": "hygiene/trailing-whitespace"},
|
|
149
|
+
{"id": "hygiene/end-of-file-fixer"},
|
|
150
|
+
{"id": "syntax/json-syntax"},
|
|
151
|
+
{"id": "syntax/ast-syntax"},
|
|
152
|
+
{"id": "syntax/merge-conflict"},
|
|
153
|
+
{"id": "repo/large-files", "args": ["--maxkb=250000"]},
|
|
154
|
+
{"id": "repo/private-key"}
|
|
155
|
+
],
|
|
156
|
+
"blocking": "warn"
|
|
157
|
+
},
|
|
158
|
+
"impact": {
|
|
159
|
+
"profile": "plain",
|
|
160
|
+
"urls_globs": [],
|
|
161
|
+
"template_globs": [],
|
|
162
|
+
"frontend_globs": [],
|
|
163
|
+
"cache_file": ".impact_analysis_cache.json",
|
|
164
|
+
"fast_mode": false,
|
|
165
|
+
"threads": 4
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
| Section | Key | Meaning |
|
|
171
|
+
| --- | --- | --- |
|
|
172
|
+
| top-level | `ignore_paths` | Glob patterns (matching any path suffix) excluded from all checks and from impact scanning. |
|
|
173
|
+
| top-level | `use_gitignore` | When `true`, also excludes paths ignored by `git check-ignore`. |
|
|
174
|
+
| `guard` | `checks` | Ordered list of check entries; each `{"id": ...}` may add `args`, `blocking`, and `always_block` overrides. |
|
|
175
|
+
| `guard` | `blocking` | `"warn"` (default) or `"strict"`. In strict mode every check marked blocking can fail the run. |
|
|
176
|
+
| `impact` | `profile` | `plain`, `django`, or `fastapi`; selects route extraction plus default globs. |
|
|
177
|
+
| `impact` | `urls_globs` / `template_globs` / `frontend_globs` | Glob patterns for route files, Django templates, and frontend sources. |
|
|
178
|
+
| `impact` | `cache_file` | Path of the symbol cache (relative to the repo root). |
|
|
179
|
+
| `impact` | `fast_mode`, `threads` | Reserved defaults from `init`; fast mode is currently selected with the `impact --fast` flag. |
|
|
180
|
+
|
|
181
|
+
A config entry can also declare a custom external check:
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{"id": "my-lint", "type": "external", "command": ["make", "lint"]}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
External checks run `command` (each `{file}` placeholder is replaced with the file path) and report a failed exit status as an issue. `scoped` can be `"files"` (command runs once per file) or `"repo"` (once for the whole repository).
|
|
188
|
+
|
|
189
|
+
### Profiles
|
|
190
|
+
|
|
191
|
+
- `plain`: no route or cross-layer globs; impact analysis reports only Python references.
|
|
192
|
+
- `django`: route files `**/urls.py`, templates `**/templates/**/*.html`, frontend `**/src/**/*.ts` and `**/app/**/*.js`; guard checks append `ruff` and `ruff-format`.
|
|
193
|
+
- `fastapi`: routes extracted from `**/*.py`, same frontend globs; guard checks append `ruff` and `ruff-format`.
|
|
194
|
+
|
|
195
|
+
### Blocking behavior
|
|
196
|
+
|
|
197
|
+
- `guard.blocking` is `"warn"` by default: issues are reported but the run exits `0`. Only checks marked `always_block` (by default just `repo/private-key`) fail the run in warn mode.
|
|
198
|
+
- Set `guard.blocking` to `"strict"` (or export `IMPACT_CHECK_STRICT=1`): every check marked `{"blocking": true}` that finds issues fails the run.
|
|
199
|
+
- Override per check, for example:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{"id": "syntax/ast-syntax", "always_block": true}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Pre-push hook
|
|
206
|
+
|
|
207
|
+
`diffimpactscout install-hooks` writes a `pre-push` hook that runs `diffimpactscout guard` on every push.
|
|
208
|
+
|
|
209
|
+
- The hook directory is resolved from `git rev-parse --git-common-dir` and honors `core.hooksPath`.
|
|
210
|
+
- The hook script is marked with `# diffimpactscout pre-push hook`. On `--uninstall` (or a re-install with `--force`), only a hook carrying this marker is touched.
|
|
211
|
+
- If a `pre-push` hook already exists without the marker, installation refuses with a `use --force to overwrite` message.
|
|
212
|
+
- The hook runs `diffimpactscout guard`; if the tool is not found, it prints a notice and exits `0`, so pushes are never blocked by a missing install.
|
|
213
|
+
- Because the guard is warn-by-default, an installed hook reports issues without blocking the push unless you enable strict mode.
|
|
214
|
+
|
|
215
|
+
### Environment variables
|
|
216
|
+
|
|
217
|
+
| Variable | Effect |
|
|
218
|
+
| --- | --- |
|
|
219
|
+
| `DIFFIMPACTSCOUT_SKIP` | When set (any value), `guard` and `impact` skip entirely and exit `0`. |
|
|
220
|
+
| `IMPACT_CHECK_SKIP` | Legacy alias with the same behavior as `DIFFIMPACTSCOUT_SKIP`. |
|
|
221
|
+
| `IMPACT_CHECK_STRICT` | Falsy values are `0`, `false`, `no`, `off`, and empty; anything else puts `guard` in strict mode and makes `impact` non-interactive runs fail on High/Medium hits. |
|
|
222
|
+
| `PRE_COMMIT_FROM_REF` / `PRE_COMMIT_TO_REF` | Override the diff range used to compute the change-set (e.g. set by a pre-commit-style wrapper). |
|
|
223
|
+
|
|
224
|
+
## Impact analysis
|
|
225
|
+
|
|
226
|
+
The `impact` command answers: if I push these changes, what else could break?
|
|
227
|
+
|
|
228
|
+
1. It parses the change-set (`git diff --find-renames`) and extracts the Python entities you added, removed, or renamed: classes, functions, methods, module fields, and class fields.
|
|
229
|
+
2. It builds an AST symbol map of every tracked Python file in the repo (skipping excluded paths), cached in `impact.cache_file`, and finds every reference to the changed symbols. Lookup kind follows the entity: class fields match attribute loads, methods match name and attribute loads, functions/classes also match imports.
|
|
230
|
+
3. Depending on the profile it extracts routes:
|
|
231
|
+
- `django`: `path()`/`re_path()`/`url()` entries in `urls_globs` that carry a `name=`.
|
|
232
|
+
- `fastapi`: `get`/`post`/`put`/`delete`/`patch`/`options` decorators on `router`/`app`-style objects in `urls_globs`.
|
|
233
|
+
4. It links layers: Django templates using `{% url 'name' %}` resolve through the route to its handler; frontend calls (`http.get(...)`, `HttpClient.post(...)`, `$http.delete(...)`) resolve the quoted endpoint to a route by path. References that cannot be resolved to a route land in an "unresolved" bucket flagged for manual checking.
|
|
234
|
+
5. Every impacted reference gets a severity:
|
|
235
|
+
|
|
236
|
+
| Condition | Severity |
|
|
237
|
+
| --- | --- |
|
|
238
|
+
| Symbol was deleted or renamed | High |
|
|
239
|
+
| Symbol is referenced across 2+ layers (python / template / frontend) | High |
|
|
240
|
+
| Reference is inside a file that is itself in the change-set | Low |
|
|
241
|
+
| No reference path or no changed paths to compare | Low |
|
|
242
|
+
| Otherwise | Medium |
|
|
243
|
+
|
|
244
|
+
Actions follow severity: High rows say `review/verify`, Medium rows `verify`, Low rows `ok`, and deleted symbols `verify dangling references`.
|
|
245
|
+
|
|
246
|
+
### Example report excerpt
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
# Impact Analysis Report
|
|
250
|
+
| # | Impacted File Path | Module / Subsystem | Category | Detected Reference / Usage | Severity | Action Required |
|
|
251
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
252
|
+
| 1 | app/views/dashboard.py | app/views | python | render_dashboard (import at dashboard.py:12) | Medium | verify |
|
|
253
|
+
| 2 | app/templates/dashboard.html | app/templates | template | {% url 'dashboard' %} at app/templates/dashboard.html:8 | High | review/verify |
|
|
254
|
+
| 3 | frontend/src/api.ts | frontend/src | frontend | http.get("/dashboard/") at api.ts:41 | Medium | verify |
|
|
255
|
+
|
|
256
|
+
## Unresolved references (manual check required)
|
|
257
|
+
- {'file': 'frontend/src/api.ts', 'path': '/account/settings', 'dynamic': False}
|
|
258
|
+
|
|
259
|
+
Summary: 3 changed file(s); High: 1, Medium: 2, Low: 0
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
In a terminal, `impact` then asks `Proceed with push? (Y/n)`. On a non-interactive run (for example after a CI trigger), the report is shown as a warning and the push is not blocked unless `IMPACT_CHECK_STRICT` is set.
|
|
263
|
+
|
|
264
|
+
## Checks reference
|
|
265
|
+
|
|
266
|
+
All checks are incremental: file-scoped checks run on files in the change-set, and line-scoped checks flag only changed lines.
|
|
267
|
+
|
|
268
|
+
| id | What it checks | Blocks by default? |
|
|
269
|
+
| --- | --- | --- |
|
|
270
|
+
| `hygiene/mixed-line-ending` | Converts CRLF/CR to LF (`--fix=lf` only); auto-fixes files in place. | No (fixes) |
|
|
271
|
+
| `hygiene/trailing-whitespace` | Strips trailing spaces/tabs and trailing blank lines; auto-fixes files in place. | No (fixes) |
|
|
272
|
+
| `hygiene/end-of-file-fixer` | Ensures files end with exactly one newline; auto-fixes files in place. | No (fixes) |
|
|
273
|
+
| `syntax/json-syntax` | Validates that `.json` files parse. | No |
|
|
274
|
+
| `syntax/ast-syntax` | Validates that `.py` files parse as Python. | No |
|
|
275
|
+
| `syntax/merge-conflict` | Flags `<<<<<<<` / `=======` / `>>>>>>>` conflict markers on changed lines. | No |
|
|
276
|
+
| `repo/large-files` | Flags files over `--maxkb` (default 250000). | No |
|
|
277
|
+
| `repo/private-key` | Flags files containing private-key material (RSA, EC, OpenSSH, DSA, PGP blocks). | **Always** |
|
|
278
|
+
| `ruff` | Runs `ruff check` on changed lines of `.py` files (needs `ruff`). | No |
|
|
279
|
+
| `ruff-format` | Runs `ruff format --check` on changed lines (needs `ruff`). | No |
|
|
280
|
+
| `eslint` | Runs `npx eslint` on changed files, honoring a baseline (needs `eslint`). | No |
|
|
281
|
+
| `prettier` | Runs `npx prettier --check` on changed files under `src/` or `app/`, honoring a baseline (needs `prettier`). | No |
|
|
282
|
+
|
|
283
|
+
"Always" means the check has `always_block: true`, so `repo/private-key` fails the guard even in warn mode: a private key should never be pushed. Every other blocking check only fails the run in strict mode (`guard.blocking: "strict"` or `IMPACT_CHECK_STRICT=1`).
|
|
284
|
+
|
|
285
|
+
## Requirements
|
|
286
|
+
|
|
287
|
+
- Python 3.6 or newer.
|
|
288
|
+
- Standard library only at runtime; no third-party dependencies.
|
|
289
|
+
- The `git` CLI on `PATH`.
|
|
290
|
+
- Optional: `ruff`, `eslint`, and `prettier` for their respective checks (missing tools degrade to warnings).
|
|
291
|
+
|
|
292
|
+
## FAQ
|
|
293
|
+
|
|
294
|
+
**Why is nothing failing?**
|
|
295
|
+
DiffImpactScout is warn-by-default. `guard` reports issues but exists `0` unless a `repo/private-key` hit, or strict mode is active. The same applies to `impact` in non-interactive contexts.
|
|
296
|
+
|
|
297
|
+
**How do I make it block?**
|
|
298
|
+
Set `IMPACT_CHECK_STRICT=1` (or any truthy value), or set `"blocking": "strict"` in the `guard` section of `.diffimpactscout.json`. You can also force specific checks to always block with `"always_block": true` on a check entry.
|
|
299
|
+
|
|
300
|
+
**I want to skip the guard, how?**
|
|
301
|
+
Export either `DIFFIMPACTSCOUT_SKIP` or `IMPACT_CHECK_SKIP` (any value); both `guard` and `impact` will exit `0` without doing anything.
|
|
302
|
+
|
|
303
|
+
**I already have a pre-push hook.**
|
|
304
|
+
`install-hooks` refuses to overwrite a hook it did not install. Review the existing hook and re-run with `--force` to replace it, or call `guard` from your own hook.
|
|
305
|
+
|
|
306
|
+
**My templates are in nested or unusual locations.**
|
|
307
|
+
Set `impact.template_globs` (and `frontend_globs`) in the config to cover your layout; use the `django` profile for a sensible starting point.
|
|
308
|
+
|
|
309
|
+
**The hygiene checks modified my files.**
|
|
310
|
+
That is by design: mixed line endings, trailing whitespace, and missing final newlines are fixed in place. Checks that can fix do so rather than failing; run `diffimpactscout check hygiene/end-of-file-fixer <path>` to apply one fixer to specific files.
|
|
311
|
+
|
|
312
|
+
## License
|
|
313
|
+
|
|
314
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# DiffImpactScout
|
|
2
|
+
|
|
3
|
+
An incremental pre-push guard and AST blast-radius impact analyzer for git repositories.
|
|
4
|
+
|
|
5
|
+
- Runs a battery of hygiene, syntax, and linter checks on just the files you changed, before you push.
|
|
6
|
+
- Estimates the blast radius of a change: which Python symbols you touched and where they are referenced across Python, Django templates, and frontend code.
|
|
7
|
+
- Installs as a git pre-push hook that is non-blocking by default, so it informs without getting in the way.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Incremental checks: every check is scoped to the change-set or changed lines, not the whole repository.
|
|
12
|
+
- Auto-fixing hygiene checks (line endings, trailing whitespace, end-of-file newline) that clean files in place.
|
|
13
|
+
- Python AST symbol map: detects changed classes, functions, methods, module fields, and class fields, including deletions and renames.
|
|
14
|
+
- Cross-layer impact linking: Python symbols to Django `{% url %}` template tags and frontend `http.get()/post()`-style calls.
|
|
15
|
+
- Symbol cache (`.impact_analysis_cache.json`) so repeated runs skip unchanged files.
|
|
16
|
+
- Profiles for `plain`, `django`, and `fastapi` projects.
|
|
17
|
+
- Configurable pre-push hook installation that never silently clobbers an existing hook.
|
|
18
|
+
|
|
19
|
+
## Core idea
|
|
20
|
+
|
|
21
|
+
DiffImpactScout answers one question for a developer about to push: did I break something?
|
|
22
|
+
|
|
23
|
+
- **Only your changes are analyzed.** Every guard check and the impact report are computed against the diff between your branch and the resolved change base (upstream master or sprint branch when present, otherwise closest origin branch). Pre-existing violations that arrived from upstream or from sync merges are not re-reported, so a run answers "did I introduce this?" instead of "does this file have a problem?"
|
|
24
|
+
- **Non-blocking by default.** A push is only blocked when strictness is explicitly requested (strict mode or an always-block check). Default runs inform without getting in the way.
|
|
25
|
+
- **Fixes over complaints.** Hygiene problems that can be auto-fixed are fixed in place instead of failing the run.
|
|
26
|
+
- **Blast radius, not just lint.** The impact report shows which code outside your diff could be affected by your change, so the "not my change" threshold is applied to blame, not to risk.
|
|
27
|
+
|
|
28
|
+
The change-set also handles the full breadth of realistic push shapes: direct pushes, force pushes, new branches, sync merges (merge, rebase, squash), stacked branches, shallow clones, repos without a remote anchor, and fork upstream sync. All are covered by the automated pre-push scenario suite, so a missing upstream anchor never silently means "nothing was checked".
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
pip install diffimpactscout
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The package is pure Python (standard library only at runtime) and requires Python 3.6 or newer and the `git` CLI on `PATH`.
|
|
37
|
+
|
|
38
|
+
Optional tools are only needed for the checks that use them: `ruff`, `eslint` (via `npx`), and `prettier` (via `npx`). If one is missing, the affected check emits a warning and the guard continues; it never blocks a push because a tool is absent.
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
# 1. Write a .diffimpactscout.json config (optional; defaults are used without one)
|
|
44
|
+
diffimpactscout init
|
|
45
|
+
|
|
46
|
+
# 2. Run the guard checks on your change-set
|
|
47
|
+
diffimpactscout guard
|
|
48
|
+
|
|
49
|
+
# 3. Analyze the blast radius of your change
|
|
50
|
+
diffimpactscout impact
|
|
51
|
+
|
|
52
|
+
# 4. Run the guard automatically on every push
|
|
53
|
+
diffimpactscout install-hooks
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`init` accepts `--profile plain|django|fastapi` to seed profile-appropriate settings.
|
|
57
|
+
|
|
58
|
+
## Commands
|
|
59
|
+
|
|
60
|
+
The `init` command only needs a directory; every other command must be run inside a git repository.
|
|
61
|
+
|
|
62
|
+
### `diffimpactscout init`
|
|
63
|
+
|
|
64
|
+
Writes a `.diffimpactscout.json` into the current directory. If the file already exists it is left unchanged.
|
|
65
|
+
|
|
66
|
+
| Flag | Description |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `--profile {plain,django,fastapi}` | Seed defaults from the named profile (default: `plain`). |
|
|
69
|
+
|
|
70
|
+
### `diffimpactscout guard`
|
|
71
|
+
|
|
72
|
+
Runs every check listed in `guard.checks` over the change-set and prints any issues. Exits non-zero only when the run is in blocking mode and a blocking check found issues (see [Pre-push hook](#pre-push-hook) and [Configuration](#configuration)).
|
|
73
|
+
|
|
74
|
+
| Flag | Description |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| `--staged` | Check only staged files (`git diff --cached`). |
|
|
77
|
+
| `--all` | Check every file tracked by git. |
|
|
78
|
+
| `files...` | Check only the given paths. |
|
|
79
|
+
|
|
80
|
+
With no flags, the change-set is the diff between your branch and the closest remote ref (upstream preferred, then origin), falling back to the union of committed, staged, and worktree changes when no remote base can be resolved.
|
|
81
|
+
|
|
82
|
+
### `diffimpactscout impact`
|
|
83
|
+
|
|
84
|
+
Analyzes the blast radius of the change-set. In an interactive terminal it prints the report and asks `Proceed with push? (Y/n)`; answering `n` or `no` exits non-zero.
|
|
85
|
+
|
|
86
|
+
| Flag | Description |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| `--staged` | Analyze the staged diff (base ref becomes `HEAD`). |
|
|
89
|
+
| `--fast` | Skip template and frontend scanning; limit Python scanning to the changed files plus import-linked files. |
|
|
90
|
+
| `--json` | Emit the report as JSON (`{changed_count, rows, unresolved}`) instead of a markdown table. |
|
|
91
|
+
|
|
92
|
+
### `diffimpactscout check CHECK_ID [files...]`
|
|
93
|
+
|
|
94
|
+
Runs a single check by id (see [Checks reference](#checks-reference)). With no file arguments it runs against the change-set.
|
|
95
|
+
|
|
96
|
+
### `diffimpactscout install-hooks`
|
|
97
|
+
|
|
98
|
+
Installs a `pre-push` git hook that runs `diffimpactscout guard` on push.
|
|
99
|
+
|
|
100
|
+
| Flag | Description |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `--force` | Overwrite an existing `pre-push` hook that DiffImpactScout did not install. |
|
|
103
|
+
| `--uninstall` | Remove the DiffImpactScout `pre-push` hook. |
|
|
104
|
+
|
|
105
|
+
### `diffimpactscout --version`
|
|
106
|
+
|
|
107
|
+
Prints the installed version.
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
DiffImpactScout is configured by a `.diffimpactscout.json` file in the repository root. Without one, the defaults below are used.
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"version": 1,
|
|
116
|
+
"mode": "pre-push",
|
|
117
|
+
"ignore_paths": [
|
|
118
|
+
"**/node_modules/**",
|
|
119
|
+
"**/venv/**",
|
|
120
|
+
"**/.venv/**",
|
|
121
|
+
"**/migrations/**",
|
|
122
|
+
"**/staticfiles/**",
|
|
123
|
+
"**/dist/**",
|
|
124
|
+
"**/build/**",
|
|
125
|
+
"**/.git/**"
|
|
126
|
+
],
|
|
127
|
+
"use_gitignore": false,
|
|
128
|
+
"guard": {
|
|
129
|
+
"checks": [
|
|
130
|
+
{"id": "hygiene/mixed-line-ending"},
|
|
131
|
+
{"id": "hygiene/trailing-whitespace"},
|
|
132
|
+
{"id": "hygiene/end-of-file-fixer"},
|
|
133
|
+
{"id": "syntax/json-syntax"},
|
|
134
|
+
{"id": "syntax/ast-syntax"},
|
|
135
|
+
{"id": "syntax/merge-conflict"},
|
|
136
|
+
{"id": "repo/large-files", "args": ["--maxkb=250000"]},
|
|
137
|
+
{"id": "repo/private-key"}
|
|
138
|
+
],
|
|
139
|
+
"blocking": "warn"
|
|
140
|
+
},
|
|
141
|
+
"impact": {
|
|
142
|
+
"profile": "plain",
|
|
143
|
+
"urls_globs": [],
|
|
144
|
+
"template_globs": [],
|
|
145
|
+
"frontend_globs": [],
|
|
146
|
+
"cache_file": ".impact_analysis_cache.json",
|
|
147
|
+
"fast_mode": false,
|
|
148
|
+
"threads": 4
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
| Section | Key | Meaning |
|
|
154
|
+
| --- | --- | --- |
|
|
155
|
+
| top-level | `ignore_paths` | Glob patterns (matching any path suffix) excluded from all checks and from impact scanning. |
|
|
156
|
+
| top-level | `use_gitignore` | When `true`, also excludes paths ignored by `git check-ignore`. |
|
|
157
|
+
| `guard` | `checks` | Ordered list of check entries; each `{"id": ...}` may add `args`, `blocking`, and `always_block` overrides. |
|
|
158
|
+
| `guard` | `blocking` | `"warn"` (default) or `"strict"`. In strict mode every check marked blocking can fail the run. |
|
|
159
|
+
| `impact` | `profile` | `plain`, `django`, or `fastapi`; selects route extraction plus default globs. |
|
|
160
|
+
| `impact` | `urls_globs` / `template_globs` / `frontend_globs` | Glob patterns for route files, Django templates, and frontend sources. |
|
|
161
|
+
| `impact` | `cache_file` | Path of the symbol cache (relative to the repo root). |
|
|
162
|
+
| `impact` | `fast_mode`, `threads` | Reserved defaults from `init`; fast mode is currently selected with the `impact --fast` flag. |
|
|
163
|
+
|
|
164
|
+
A config entry can also declare a custom external check:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{"id": "my-lint", "type": "external", "command": ["make", "lint"]}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
External checks run `command` (each `{file}` placeholder is replaced with the file path) and report a failed exit status as an issue. `scoped` can be `"files"` (command runs once per file) or `"repo"` (once for the whole repository).
|
|
171
|
+
|
|
172
|
+
### Profiles
|
|
173
|
+
|
|
174
|
+
- `plain`: no route or cross-layer globs; impact analysis reports only Python references.
|
|
175
|
+
- `django`: route files `**/urls.py`, templates `**/templates/**/*.html`, frontend `**/src/**/*.ts` and `**/app/**/*.js`; guard checks append `ruff` and `ruff-format`.
|
|
176
|
+
- `fastapi`: routes extracted from `**/*.py`, same frontend globs; guard checks append `ruff` and `ruff-format`.
|
|
177
|
+
|
|
178
|
+
### Blocking behavior
|
|
179
|
+
|
|
180
|
+
- `guard.blocking` is `"warn"` by default: issues are reported but the run exits `0`. Only checks marked `always_block` (by default just `repo/private-key`) fail the run in warn mode.
|
|
181
|
+
- Set `guard.blocking` to `"strict"` (or export `IMPACT_CHECK_STRICT=1`): every check marked `{"blocking": true}` that finds issues fails the run.
|
|
182
|
+
- Override per check, for example:
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{"id": "syntax/ast-syntax", "always_block": true}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Pre-push hook
|
|
189
|
+
|
|
190
|
+
`diffimpactscout install-hooks` writes a `pre-push` hook that runs `diffimpactscout guard` on every push.
|
|
191
|
+
|
|
192
|
+
- The hook directory is resolved from `git rev-parse --git-common-dir` and honors `core.hooksPath`.
|
|
193
|
+
- The hook script is marked with `# diffimpactscout pre-push hook`. On `--uninstall` (or a re-install with `--force`), only a hook carrying this marker is touched.
|
|
194
|
+
- If a `pre-push` hook already exists without the marker, installation refuses with a `use --force to overwrite` message.
|
|
195
|
+
- The hook runs `diffimpactscout guard`; if the tool is not found, it prints a notice and exits `0`, so pushes are never blocked by a missing install.
|
|
196
|
+
- Because the guard is warn-by-default, an installed hook reports issues without blocking the push unless you enable strict mode.
|
|
197
|
+
|
|
198
|
+
### Environment variables
|
|
199
|
+
|
|
200
|
+
| Variable | Effect |
|
|
201
|
+
| --- | --- |
|
|
202
|
+
| `DIFFIMPACTSCOUT_SKIP` | When set (any value), `guard` and `impact` skip entirely and exit `0`. |
|
|
203
|
+
| `IMPACT_CHECK_SKIP` | Legacy alias with the same behavior as `DIFFIMPACTSCOUT_SKIP`. |
|
|
204
|
+
| `IMPACT_CHECK_STRICT` | Falsy values are `0`, `false`, `no`, `off`, and empty; anything else puts `guard` in strict mode and makes `impact` non-interactive runs fail on High/Medium hits. |
|
|
205
|
+
| `PRE_COMMIT_FROM_REF` / `PRE_COMMIT_TO_REF` | Override the diff range used to compute the change-set (e.g. set by a pre-commit-style wrapper). |
|
|
206
|
+
|
|
207
|
+
## Impact analysis
|
|
208
|
+
|
|
209
|
+
The `impact` command answers: if I push these changes, what else could break?
|
|
210
|
+
|
|
211
|
+
1. It parses the change-set (`git diff --find-renames`) and extracts the Python entities you added, removed, or renamed: classes, functions, methods, module fields, and class fields.
|
|
212
|
+
2. It builds an AST symbol map of every tracked Python file in the repo (skipping excluded paths), cached in `impact.cache_file`, and finds every reference to the changed symbols. Lookup kind follows the entity: class fields match attribute loads, methods match name and attribute loads, functions/classes also match imports.
|
|
213
|
+
3. Depending on the profile it extracts routes:
|
|
214
|
+
- `django`: `path()`/`re_path()`/`url()` entries in `urls_globs` that carry a `name=`.
|
|
215
|
+
- `fastapi`: `get`/`post`/`put`/`delete`/`patch`/`options` decorators on `router`/`app`-style objects in `urls_globs`.
|
|
216
|
+
4. It links layers: Django templates using `{% url 'name' %}` resolve through the route to its handler; frontend calls (`http.get(...)`, `HttpClient.post(...)`, `$http.delete(...)`) resolve the quoted endpoint to a route by path. References that cannot be resolved to a route land in an "unresolved" bucket flagged for manual checking.
|
|
217
|
+
5. Every impacted reference gets a severity:
|
|
218
|
+
|
|
219
|
+
| Condition | Severity |
|
|
220
|
+
| --- | --- |
|
|
221
|
+
| Symbol was deleted or renamed | High |
|
|
222
|
+
| Symbol is referenced across 2+ layers (python / template / frontend) | High |
|
|
223
|
+
| Reference is inside a file that is itself in the change-set | Low |
|
|
224
|
+
| No reference path or no changed paths to compare | Low |
|
|
225
|
+
| Otherwise | Medium |
|
|
226
|
+
|
|
227
|
+
Actions follow severity: High rows say `review/verify`, Medium rows `verify`, Low rows `ok`, and deleted symbols `verify dangling references`.
|
|
228
|
+
|
|
229
|
+
### Example report excerpt
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
# Impact Analysis Report
|
|
233
|
+
| # | Impacted File Path | Module / Subsystem | Category | Detected Reference / Usage | Severity | Action Required |
|
|
234
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
235
|
+
| 1 | app/views/dashboard.py | app/views | python | render_dashboard (import at dashboard.py:12) | Medium | verify |
|
|
236
|
+
| 2 | app/templates/dashboard.html | app/templates | template | {% url 'dashboard' %} at app/templates/dashboard.html:8 | High | review/verify |
|
|
237
|
+
| 3 | frontend/src/api.ts | frontend/src | frontend | http.get("/dashboard/") at api.ts:41 | Medium | verify |
|
|
238
|
+
|
|
239
|
+
## Unresolved references (manual check required)
|
|
240
|
+
- {'file': 'frontend/src/api.ts', 'path': '/account/settings', 'dynamic': False}
|
|
241
|
+
|
|
242
|
+
Summary: 3 changed file(s); High: 1, Medium: 2, Low: 0
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
In a terminal, `impact` then asks `Proceed with push? (Y/n)`. On a non-interactive run (for example after a CI trigger), the report is shown as a warning and the push is not blocked unless `IMPACT_CHECK_STRICT` is set.
|
|
246
|
+
|
|
247
|
+
## Checks reference
|
|
248
|
+
|
|
249
|
+
All checks are incremental: file-scoped checks run on files in the change-set, and line-scoped checks flag only changed lines.
|
|
250
|
+
|
|
251
|
+
| id | What it checks | Blocks by default? |
|
|
252
|
+
| --- | --- | --- |
|
|
253
|
+
| `hygiene/mixed-line-ending` | Converts CRLF/CR to LF (`--fix=lf` only); auto-fixes files in place. | No (fixes) |
|
|
254
|
+
| `hygiene/trailing-whitespace` | Strips trailing spaces/tabs and trailing blank lines; auto-fixes files in place. | No (fixes) |
|
|
255
|
+
| `hygiene/end-of-file-fixer` | Ensures files end with exactly one newline; auto-fixes files in place. | No (fixes) |
|
|
256
|
+
| `syntax/json-syntax` | Validates that `.json` files parse. | No |
|
|
257
|
+
| `syntax/ast-syntax` | Validates that `.py` files parse as Python. | No |
|
|
258
|
+
| `syntax/merge-conflict` | Flags `<<<<<<<` / `=======` / `>>>>>>>` conflict markers on changed lines. | No |
|
|
259
|
+
| `repo/large-files` | Flags files over `--maxkb` (default 250000). | No |
|
|
260
|
+
| `repo/private-key` | Flags files containing private-key material (RSA, EC, OpenSSH, DSA, PGP blocks). | **Always** |
|
|
261
|
+
| `ruff` | Runs `ruff check` on changed lines of `.py` files (needs `ruff`). | No |
|
|
262
|
+
| `ruff-format` | Runs `ruff format --check` on changed lines (needs `ruff`). | No |
|
|
263
|
+
| `eslint` | Runs `npx eslint` on changed files, honoring a baseline (needs `eslint`). | No |
|
|
264
|
+
| `prettier` | Runs `npx prettier --check` on changed files under `src/` or `app/`, honoring a baseline (needs `prettier`). | No |
|
|
265
|
+
|
|
266
|
+
"Always" means the check has `always_block: true`, so `repo/private-key` fails the guard even in warn mode: a private key should never be pushed. Every other blocking check only fails the run in strict mode (`guard.blocking: "strict"` or `IMPACT_CHECK_STRICT=1`).
|
|
267
|
+
|
|
268
|
+
## Requirements
|
|
269
|
+
|
|
270
|
+
- Python 3.6 or newer.
|
|
271
|
+
- Standard library only at runtime; no third-party dependencies.
|
|
272
|
+
- The `git` CLI on `PATH`.
|
|
273
|
+
- Optional: `ruff`, `eslint`, and `prettier` for their respective checks (missing tools degrade to warnings).
|
|
274
|
+
|
|
275
|
+
## FAQ
|
|
276
|
+
|
|
277
|
+
**Why is nothing failing?**
|
|
278
|
+
DiffImpactScout is warn-by-default. `guard` reports issues but exists `0` unless a `repo/private-key` hit, or strict mode is active. The same applies to `impact` in non-interactive contexts.
|
|
279
|
+
|
|
280
|
+
**How do I make it block?**
|
|
281
|
+
Set `IMPACT_CHECK_STRICT=1` (or any truthy value), or set `"blocking": "strict"` in the `guard` section of `.diffimpactscout.json`. You can also force specific checks to always block with `"always_block": true` on a check entry.
|
|
282
|
+
|
|
283
|
+
**I want to skip the guard, how?**
|
|
284
|
+
Export either `DIFFIMPACTSCOUT_SKIP` or `IMPACT_CHECK_SKIP` (any value); both `guard` and `impact` will exit `0` without doing anything.
|
|
285
|
+
|
|
286
|
+
**I already have a pre-push hook.**
|
|
287
|
+
`install-hooks` refuses to overwrite a hook it did not install. Review the existing hook and re-run with `--force` to replace it, or call `guard` from your own hook.
|
|
288
|
+
|
|
289
|
+
**My templates are in nested or unusual locations.**
|
|
290
|
+
Set `impact.template_globs` (and `frontend_globs`) in the config to cover your layout; use the `django` profile for a sensible starting point.
|
|
291
|
+
|
|
292
|
+
**The hygiene checks modified my files.**
|
|
293
|
+
That is by design: mixed line endings, trailing whitespace, and missing final newlines are fixed in place. Checks that can fix do so rather than failing; run `diffimpactscout check hygiene/end-of-file-fixer <path>` to apply one fixer to specific files.
|
|
294
|
+
|
|
295
|
+
## License
|
|
296
|
+
|
|
297
|
+
MIT. See [LICENSE](LICENSE).
|