complexipy 6.0.1__tar.gz → 6.2.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.
Files changed (75) hide show
  1. {complexipy-6.0.1 → complexipy-6.2.0}/.pre-commit-config.yaml +1 -1
  2. complexipy-6.2.0/AGENTS.md +132 -0
  3. {complexipy-6.0.1 → complexipy-6.2.0}/Cargo.lock +1 -1
  4. {complexipy-6.0.1 → complexipy-6.2.0}/Cargo.toml +1 -1
  5. {complexipy-6.0.1 → complexipy-6.2.0}/PKG-INFO +39 -45
  6. {complexipy-6.0.1 → complexipy-6.2.0}/README.md +38 -44
  7. complexipy-6.2.0/complexipy/main.py +392 -0
  8. complexipy-6.2.0/complexipy/types.py +84 -0
  9. complexipy-6.2.0/complexipy/utils/config.py +163 -0
  10. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/utils/constants.py +2 -0
  11. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/utils/csv.py +2 -0
  12. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/utils/diff.py +149 -43
  13. complexipy-6.2.0/complexipy/utils/ignored.py +58 -0
  14. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/utils/json.py +2 -0
  15. complexipy-6.2.0/complexipy/utils/output.py +502 -0
  16. complexipy-6.2.0/complexipy/utils/paths.py +79 -0
  17. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/utils/snapshot.py +81 -2
  18. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/utils/toml.py +72 -135
  19. {complexipy-6.0.1 → complexipy-6.2.0}/docs/es/index.md +46 -35
  20. {complexipy-6.0.1 → complexipy-6.2.0}/docs/es/usage-guide.md +17 -16
  21. {complexipy-6.0.1 → complexipy-6.2.0}/docs/index.md +46 -35
  22. {complexipy-6.0.1 → complexipy-6.2.0}/docs/usage-guide.md +17 -15
  23. {complexipy-6.0.1 → complexipy-6.2.0}/pyproject.toml +3 -1
  24. {complexipy-6.0.1 → complexipy-6.2.0}/src/runner.rs +26 -15
  25. complexipy-6.0.1/complexipy/main.py +0 -771
  26. complexipy-6.0.1/complexipy/types.py +0 -43
  27. complexipy-6.0.1/complexipy/utils/output.py +0 -312
  28. {complexipy-6.0.1 → complexipy-6.2.0}/.mdformat.toml +0 -0
  29. {complexipy-6.0.1 → complexipy-6.2.0}/LICENSE +0 -0
  30. {complexipy-6.0.1 → complexipy-6.2.0}/build-wasm.sh +0 -0
  31. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/__init__.py +0 -0
  32. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/_complexipy.pyi +0 -0
  33. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/api.py +0 -0
  34. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/py.typed +0 -0
  35. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/utils/__init__.py +0 -0
  36. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/utils/cache.py +0 -0
  37. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/utils/dataclasses.py +0 -0
  38. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/utils/gitlab.py +0 -0
  39. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy/utils/sarif.py +0 -0
  40. {complexipy-6.0.1 → complexipy-6.2.0}/complexipy.toml +0 -0
  41. {complexipy-6.0.1 → complexipy-6.2.0}/docs/about.md +0 -0
  42. {complexipy-6.0.1 → complexipy-6.2.0}/docs/comparison-with-ruff.md +0 -0
  43. {complexipy-6.0.1 → complexipy-6.2.0}/docs/es/about.md +0 -0
  44. {complexipy-6.0.1 → complexipy-6.2.0}/docs/es/comparison-with-ruff.md +0 -0
  45. {complexipy-6.0.1 → complexipy-6.2.0}/docs/es/img/complexipy_icon.svg +0 -0
  46. {complexipy-6.0.1 → complexipy-6.2.0}/docs/es/understanding-scores.md +0 -0
  47. {complexipy-6.0.1 → complexipy-6.2.0}/docs/img/complexipy_icon.svg +0 -0
  48. {complexipy-6.0.1 → complexipy-6.2.0}/docs/understanding-scores.md +0 -0
  49. {complexipy-6.0.1 → complexipy-6.2.0}/mkdocs.es.yml +0 -0
  50. {complexipy-6.0.1 → complexipy-6.2.0}/mkdocs.yml +0 -0
  51. {complexipy-6.0.1 → complexipy-6.2.0}/serve-web-version.sh +0 -0
  52. {complexipy-6.0.1 → complexipy-6.2.0}/src/classes.rs +0 -0
  53. {complexipy-6.0.1 → complexipy-6.2.0}/src/cognitive_complexity.rs +0 -0
  54. {complexipy-6.0.1 → complexipy-6.2.0}/src/helpers/exclude.rs +0 -0
  55. {complexipy-6.0.1 → complexipy-6.2.0}/src/helpers.rs +0 -0
  56. {complexipy-6.0.1 → complexipy-6.2.0}/src/lib.rs +0 -0
  57. {complexipy-6.0.1 → complexipy-6.2.0}/src/refactor_plans.rs +0 -0
  58. {complexipy-6.0.1 → complexipy-6.2.0}/src/utils.rs +0 -0
  59. {complexipy-6.0.1 → complexipy-6.2.0}/src/wasm.rs +0 -0
  60. {complexipy-6.0.1 → complexipy-6.2.0}/uv.lock +0 -0
  61. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/.vscode-test.mjs +0 -0
  62. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/.vscodeignore +0 -0
  63. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/CHANGELOG.md +0 -0
  64. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/LICENSE +0 -0
  65. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/README.md +0 -0
  66. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/eslint.config.mjs +0 -0
  67. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/extension.js +0 -0
  68. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/img/complexipy.png +0 -0
  69. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/img/complexipy_icon.png +0 -0
  70. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/img/complexipy_icon.svg +0 -0
  71. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/jsconfig.json +0 -0
  72. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/package-lock.json +0 -0
  73. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/package.json +0 -0
  74. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/test/extension.test.js +0 -0
  75. {complexipy-6.0.1 → complexipy-6.2.0}/vscode/complexipy/test/statusbar.test.js +0 -0
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  repos:
3
3
  - repo: https://github.com/rohaquinlop/complexipy-pre-commit
4
- rev: v5.1.0
4
+ rev: v6.1.0
5
5
  hooks:
6
6
  - id: complexipy
7
7
 
@@ -0,0 +1,132 @@
1
+ # complexipy
2
+
3
+ > Cognitive complexity analyzer for Python — measures how hard code is for humans to understand.
4
+
5
+ ## Tech Stack
6
+
7
+ - **Language:** Python 3.8+ (CLI/API) + Rust (core engine via PyO3/maturin)
8
+ - **Framework:** Typer (CLI), Rich (terminal output)
9
+ - **Package Manager:** uv (Python), Cargo (Rust)
10
+ - **Build:** maturin (Rust → Python extension), wasm-pack (Rust → WASM)
11
+ - **Docs:** MkDocs Material (EN + ES)
12
+
13
+ ## Project Structure
14
+
15
+ ```
16
+ complexipy/
17
+ ├── src/ # Rust core engine
18
+ │ ├── cognitive_complexity.rs # AST walking + complexity scoring algorithm
19
+ │ ├── classes.rs # Data types (FunctionComplexity, RefactorPlan, etc.)
20
+ │ ├── refactor_plans.rs # Deterministic refactor plan generation
21
+ │ ├── runner.rs # File/dir/URL processing, git clone
22
+ │ ├── lib.rs # PyO3 module entry
23
+ │ └── helpers/exclude.rs # Glob-based file exclusion
24
+
25
+ ├── complexipy/ # Python CLI + API wrapper
26
+ │ ├── main.py # Typer CLI app (entrypoint, pure orchestrator)
27
+ │ ├── api.py # Python API (code_complexity, file_complexity)
28
+ │ ├── types.py # Enums + dataclasses (RunConfig, ExitReport)
29
+ │ ├── utils/ # Utilities organized by domain
30
+ │ │ ├── config.py # CLI + TOML config resolution (resolve_config)
31
+ │ │ ├── output.py # Rich console display + formatting
32
+ │ │ ├── paths.py # Output path resolution for export formats
33
+ │ │ ├── diff.py # Git diff computation + flag resolution
34
+ │ │ ├── snapshot.py # Snapshot evaluation + watermark
35
+ │ │ ├── ignored.py # Ignored-location reporting
36
+ │ │ ├── toml.py # TOML loading + argument resolution
37
+ │ │ ├── cache.py # Previous-run caching for delta reporting
38
+ │ │ ├── constants.py # Output filenames, legacy flag maps
39
+ │ │ ├── csv.py, json.py, # Export format writers
40
+ │ │ │ gitlab.py, sarif.py
41
+ │ │ └── dataclasses.py # FunctionRow, FileEntry display types
42
+ │ └── _complexipy.pyi # Type stubs for Rust extension
43
+
44
+ ├── tests/ # pytest test suite
45
+ │ ├── main.py # Core tests + paper conformance
46
+ │ ├── src/ # Test fixture .py files
47
+ │ └── test_*.py # Utility module tests
48
+
49
+ ├── docs/ # MkDocs content (EN + es/)
50
+ ├── web/ # Browser demo (WASM + CodeMirror)
51
+ └── .github/workflows/ # CI, PR title check, release
52
+ ```
53
+
54
+ ## Commands
55
+
56
+ ### Setup
57
+
58
+ ```bash
59
+ uv sync
60
+ ```
61
+
62
+ ### Build (Rust extension)
63
+
64
+ ```bash
65
+ uv run maturin develop
66
+ ```
67
+
68
+ ### Test
69
+
70
+ ```bash
71
+ uv run pytest tests/
72
+ ```
73
+
74
+ ### Lint, Format & Type Check
75
+
76
+ ```bash
77
+ uv run ruff check .
78
+ uv run ruff format .
79
+ uv run ty check .
80
+ ```
81
+
82
+ ### Run
83
+
84
+ ```bash
85
+ uv run complexipy <path>
86
+ uv run complexipy . --diff main --max-complexity-allowed 15
87
+ ```
88
+
89
+ ### Docs
90
+
91
+ ```bash
92
+ uv run mkdocs serve
93
+ ```
94
+
95
+ ## Code Style
96
+
97
+ - No comments in code. The code must speak for itself.
98
+ - Docstrings only when necessary, and only about what the function does — never changelog or history notes.
99
+ - Conventional Commits: `type(scope): description` (e.g., `fix(diff): resolve path for nested invocation`).
100
+ - Pre-commit hooks: complexipy (self-dogfooding), mdformat, yamlfix.
101
+ - Ruff for linting and formatting (line-length 80, indent-width 4).
102
+
103
+ ## Key Files
104
+
105
+ - `src/cognitive_complexity.rs` — Core algorithm: parses Python AST via ruff, computes cognitive complexity with nesting/structural/boolean increments
106
+ - `complexipy/main.py` — CLI entrypoint: pure orchestrator that delegates to utils modules
107
+ - `complexipy/types.py` — `RunConfig`, `ExitReport` dataclasses + enums (`ColorTypes`, `Sort`, `OutputFormat`)
108
+ - `complexipy/utils/config.py` — `resolve_config()`: merges CLI args + TOML into `RunConfig`
109
+ - `complexipy/utils/output.py` — Rich console display, `handle_display`, `handle_results_storage`
110
+ - `complexipy/utils/paths.py` — Output path resolution for CSV/JSON/GitLab/SARIF exports
111
+ - `complexipy/utils/diff.py` — Git diff computation, `resolve_diff_flags`, `handle_diff_output`
112
+ - `complexipy/utils/snapshot.py` — `evaluate_snapshot()`, `SnapshotEvaluation`, watermark logic
113
+ - `complexipy/utils/toml.py` — TOML loading, data-driven `get_arguments_value`
114
+ - `complexipy/_complexipy.pyi` — Type stubs for the Rust extension module
115
+ - `tests/main.py` — Core test suite including SonarSource paper conformance tests
116
+
117
+ ## Conventions
118
+
119
+ - **Package manager:** Always use `uv` — `uv run pytest`, `uv run ruff`, `uv run complexipy`
120
+ - **Commits:** Only commit when explicitly asked. Never auto-commit.
121
+ - **PR titles:** Must follow Conventional Commits (enforced by CI).
122
+ - **Tests:** pytest with `CliRunner` for CLI tests, `unittest.mock.patch` for git operations in diff tests.
123
+ - **Dual-target Rust:** `#[cfg(feature = "python")]` for PyO3, `#[cfg(feature = "wasm")]` for wasm-bindgen. Shared logic uses `#[cfg(any(feature = "python", feature = "wasm"))]`.
124
+ - **GitHub CLI:** When available, use `gh` to retrieve context before making changes — check linked issues for requirements (`gh issue view <number>`), review open PRs for related work (`gh pr list`, `gh pr view <number>`), inspect CI status (`gh run list`, `gh run view <id>`), and search the repo (`gh search issues`, `gh search prs`). Always check the relevant issue or PR before implementing to understand the full scope and any prior discussion. If `gh` is not installed, skip these checks and work from the code and local context only.
125
+
126
+ ## Anti-Patterns
127
+
128
+ - Do not add comments to code. Use descriptive variable/function names instead.
129
+ - Do not add docstrings that describe changelog or history. Docstrings describe what a function does.
130
+ - Do not commit without explicit user instruction.
131
+ - Do not use `pip` or `python -m` — use `uv run` for all commands.
132
+ - Do not modify `src/` (Rust) without understanding the PyO3/wasm-bindgen dual-target setup.
@@ -107,7 +107,7 @@ dependencies = [
107
107
 
108
108
  [[package]]
109
109
  name = "complexipy"
110
- version = "6.0.1"
110
+ version = "6.2.0"
111
111
  dependencies = [
112
112
  "console_error_panic_hook",
113
113
  "csv",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "complexipy"
3
- version = "6.0.1"
3
+ version = "6.2.0"
4
4
  edition = "2024"
5
5
  authors = ["Robin Quintero <rohaquinlop301@gmail.com>"]
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: complexipy
3
- Version: 6.0.1
3
+ Version: 6.2.0
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -119,7 +119,7 @@ complexipy . --output-format gitlab --output complexipy-code-quality.json
119
119
  complexipy . --diff HEAD~1
120
120
 
121
121
  # Fail only on threshold-breaking regressions in a diff
122
- complexipy . --diff main --ratchet
122
+ complexipy . --diff main
123
123
  # Analyze current directory while excluding files or directories with glob patterns
124
124
  complexipy . --exclude "tests/**" --exclude "path/to/exclude.py"
125
125
 
@@ -285,32 +285,33 @@ Legacy TOML keys such as `output-json = true` and CLI flags such as
285
285
 
286
286
  ### CLI Options
287
287
 
288
- | Flag | Description | Default |
289
- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
290
- | `--exclude` | Exclude glob patterns relative to each provided path. Use patterns like `tests/**` for directories or `src/legacy/file.py` for specific files. | |
291
- | `--max-complexity-allowed` | Complexity threshold | `15` |
292
- | `--snapshot-create` | Save the current violations above the threshold into `complexipy-snapshot.json` | `false` |
293
- | `--snapshot-ignore` | Skip comparing against the snapshot even if it exists | `false` |
294
- | `--failed` | Show only functions above the complexity threshold | `false` |
295
- | `--suggest-refactors` | Show deterministic Rust AST-based refactor plans in rich CLI output. Ignored by `--plain` | `false` |
296
- | `--color <auto\|yes\|no>` | Use color | `auto` |
297
- | `--sort <asc\|desc\|file_name>` | Sort results | `asc` |
298
- | `--quiet` | Suppress output | `false` |
299
- | `--ignore-complexity` | Don't exit with error on threshold breach | `false` |
300
- | `--version` | Show installed complexipy version and exit | - |
301
- | `--top <n>` | Show only the `n` most complex functions, globally sorted by complexity descending | — |
302
- | `--plain` | Emit plain text lines as `<path> <function> <complexity>`. Cannot be combined with `--quiet` | `false` |
303
- | `--output-format <format>` | Select a machine-readable output format. Repeat the flag to request multiple formats (`json`, `csv`, `gitlab`, `sarif`) | — |
304
- | `--output <path>` | Write machine-readable output to a file or directory. Use a directory when emitting multiple formats | — |
305
- | `--diff <ref>` | Show a complexity diff against a git reference (e.g. `HEAD~1`, `main`) | — |
306
- | `--ratchet` | With `--diff`, fail only when a change pushes a function above `--max-complexity-allowed` or makes an already-over function worse | `false` |
307
- | `--check-script` | Report module-level (script) complexity as a synthetic `<module>` entry | `false` |
308
- | `--no-ignore` | Analyze every function, disregarding inline ignore comments (`# complexipy: ignore`, `# noqa: complexipy`) | `false` |
309
- | `--report-ignored` | List every file:line where an ignore comment suppresses a function. Prints even under `--quiet` | `false` |
310
- | `--output-json` | Deprecated alias for `--output-format json` | `false` |
311
- | `--output-csv` | Deprecated alias for `--output-format csv` | `false` |
312
- | `--output-gitlab` | Deprecated alias for `--output-format gitlab` | `false` |
313
- | `--output-sarif` | Deprecated alias for `--output-format sarif` | `false` |
288
+ | Flag | Description | Default |
289
+ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
290
+ | `--exclude` | Exclude glob patterns relative to each provided path. Use patterns like `tests/**` for directories or `src/legacy/file.py` for specific files. | |
291
+ | `--max-complexity-allowed` | Complexity threshold | `15` |
292
+ | `--snapshot-create` | Save the current violations above the threshold into `complexipy-snapshot.json` | `false` |
293
+ | `--snapshot-ignore` | Skip comparing against the snapshot even if it exists | `false` |
294
+ | `--failed` | Show only functions above the complexity threshold | `false` |
295
+ | `--suggest-refactors` | Show deterministic Rust AST-based refactor plans in rich CLI output. Ignored by `--plain` | `false` |
296
+ | `--color <auto\|yes\|no>` | Use color | `auto` |
297
+ | `--sort <asc\|desc\|file_name>` | Sort results | `asc` |
298
+ | `--quiet` | Suppress output | `false` |
299
+ | `--ignore-complexity` | Don't exit with error on threshold breach | `false` |
300
+ | `--version` | Show installed complexipy version and exit | - |
301
+ | `--top <n>` | Show only the `n` most complex functions, globally sorted by complexity descending | — |
302
+ | `--plain` | Emit plain text lines as `<path> <function> <complexity>`. Cannot be combined with `--quiet` | `false` |
303
+ | `--output-format <format>` | Select a machine-readable output format. Repeat the flag to request multiple formats (`json`, `csv`, `gitlab`, `sarif`) | — |
304
+ | `--output <path>` | Write machine-readable output to a file or directory. Use a directory when emitting multiple formats | — |
305
+ | `--diff <ref>` | Show a complexity diff against a git reference and enforce the threshold. Fails on regressions above `--max-complexity-allowed` (see [Complexity Diff](#complexity-diff)) | — |
306
+ | `--diff-only <ref>` | Show a complexity diff visually without affecting the exit code (see [Complexity Diff](#complexity-diff)) | |
307
+ | `--ratchet`, `-R` | **Deprecated.** Use `--diff` instead, which now enforces by default | `false` |
308
+ | `--check-script` | Report module-level (script) complexity as a synthetic `<module>` entry | `false` |
309
+ | `--no-ignore` | Analyze every function, disregarding inline ignore comments (`# complexipy: ignore`, `# noqa: complexipy`) | `false` |
310
+ | `--report-ignored` | List every file:line where an ignore comment suppresses a function. Prints even under `--quiet` | `false` |
311
+ | `--output-json` | Deprecated alias for `--output-format json` | `false` |
312
+ | `--output-csv` | Deprecated alias for `--output-format csv` | `false` |
313
+ | `--output-gitlab` | Deprecated alias for `--output-format gitlab` | `false` |
314
+ | `--output-sarif` | Deprecated alias for `--output-format sarif` | `false` |
314
315
 
315
316
  Example:
316
317
 
@@ -364,7 +365,7 @@ Use `--snapshot-ignore` if you need to temporarily bypass the snapshot gate (for
364
365
 
365
366
  ### Complexity Diff
366
367
 
367
- Compare complexity against any git reference to see at a glance whether a branch or commit made things better or worse:
368
+ Compare complexity against any git reference to see whether a branch or commit made things better or worse:
368
369
 
369
370
  ```bash
370
371
  # Compare the working tree against the previous commit
@@ -372,29 +373,22 @@ complexipy . --diff HEAD~1
372
373
 
373
374
  # Compare against a named branch
374
375
  complexipy . --diff main
376
+ ```
375
377
 
376
- # Fail only on threshold-breaking regressions
377
- complexipy . --diff main --ratchet
378
+ By default `--diff` **enforces** the complexity threshold: the run exits with code `1` only when a change breaches the contract relative to `--max-complexity-allowed`:
378
379
 
379
- # Combine with other flags
380
- complexipy src/ --max-complexity-allowed 10 --diff HEAD~1
381
- ```
380
+ - A new function is introduced above the threshold, or
381
+ - an existing function's complexity increases **and** ends above the threshold (already-over functions getting worse also fail).
382
382
 
383
- `--ratchet` requires `--diff`. It exits with code `1` only when a new or modified function breaches `--max-complexity-allowed`; regressions that stay within the threshold are allowed.
383
+ Functions that regress but stay at or below the threshold (e.g. `3 → 4` with `-mx 15`) are **not** flagged the threshold is still the main contract, and `--diff` only catches regressions that actually break it.
384
384
 
385
- Sample output:
385
+ To see the diff visually without affecting the exit code, use `--diff-only` instead:
386
386
 
387
- ```
388
- Complexity diff (vs HEAD~1)
389
- ────────────────────────────────────────────────────────────────────────
390
- REGRESSED src/api.py::handle_request 12 → 19 (+7)
391
- IMPROVED src/utils.py::flatten_tree 22 → 14 (-8)
392
- NEW src/auth.py::validate_token 17 (new)
393
- ────────────────────────────────────────────────────────────────────────
394
- Net: 1 regressed, 1 improved, 1 new
387
+ ```bash
388
+ complexipy . --diff-only HEAD~1
395
389
  ```
396
390
 
397
- The diff is appended after the normal analysis output and does not affect the exit code. Requires `git` to be available and the analysed paths to be inside a git repository.
391
+ Requires `git` to be available and the analysed paths to be inside a git repository.
398
392
 
399
393
  ### Script Complexity
400
394
 
@@ -87,7 +87,7 @@ complexipy . --output-format gitlab --output complexipy-code-quality.json
87
87
  complexipy . --diff HEAD~1
88
88
 
89
89
  # Fail only on threshold-breaking regressions in a diff
90
- complexipy . --diff main --ratchet
90
+ complexipy . --diff main
91
91
  # Analyze current directory while excluding files or directories with glob patterns
92
92
  complexipy . --exclude "tests/**" --exclude "path/to/exclude.py"
93
93
 
@@ -253,32 +253,33 @@ Legacy TOML keys such as `output-json = true` and CLI flags such as
253
253
 
254
254
  ### CLI Options
255
255
 
256
- | Flag | Description | Default |
257
- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
258
- | `--exclude` | Exclude glob patterns relative to each provided path. Use patterns like `tests/**` for directories or `src/legacy/file.py` for specific files. | |
259
- | `--max-complexity-allowed` | Complexity threshold | `15` |
260
- | `--snapshot-create` | Save the current violations above the threshold into `complexipy-snapshot.json` | `false` |
261
- | `--snapshot-ignore` | Skip comparing against the snapshot even if it exists | `false` |
262
- | `--failed` | Show only functions above the complexity threshold | `false` |
263
- | `--suggest-refactors` | Show deterministic Rust AST-based refactor plans in rich CLI output. Ignored by `--plain` | `false` |
264
- | `--color <auto\|yes\|no>` | Use color | `auto` |
265
- | `--sort <asc\|desc\|file_name>` | Sort results | `asc` |
266
- | `--quiet` | Suppress output | `false` |
267
- | `--ignore-complexity` | Don't exit with error on threshold breach | `false` |
268
- | `--version` | Show installed complexipy version and exit | - |
269
- | `--top <n>` | Show only the `n` most complex functions, globally sorted by complexity descending | — |
270
- | `--plain` | Emit plain text lines as `<path> <function> <complexity>`. Cannot be combined with `--quiet` | `false` |
271
- | `--output-format <format>` | Select a machine-readable output format. Repeat the flag to request multiple formats (`json`, `csv`, `gitlab`, `sarif`) | — |
272
- | `--output <path>` | Write machine-readable output to a file or directory. Use a directory when emitting multiple formats | — |
273
- | `--diff <ref>` | Show a complexity diff against a git reference (e.g. `HEAD~1`, `main`) | — |
274
- | `--ratchet` | With `--diff`, fail only when a change pushes a function above `--max-complexity-allowed` or makes an already-over function worse | `false` |
275
- | `--check-script` | Report module-level (script) complexity as a synthetic `<module>` entry | `false` |
276
- | `--no-ignore` | Analyze every function, disregarding inline ignore comments (`# complexipy: ignore`, `# noqa: complexipy`) | `false` |
277
- | `--report-ignored` | List every file:line where an ignore comment suppresses a function. Prints even under `--quiet` | `false` |
278
- | `--output-json` | Deprecated alias for `--output-format json` | `false` |
279
- | `--output-csv` | Deprecated alias for `--output-format csv` | `false` |
280
- | `--output-gitlab` | Deprecated alias for `--output-format gitlab` | `false` |
281
- | `--output-sarif` | Deprecated alias for `--output-format sarif` | `false` |
256
+ | Flag | Description | Default |
257
+ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
258
+ | `--exclude` | Exclude glob patterns relative to each provided path. Use patterns like `tests/**` for directories or `src/legacy/file.py` for specific files. | |
259
+ | `--max-complexity-allowed` | Complexity threshold | `15` |
260
+ | `--snapshot-create` | Save the current violations above the threshold into `complexipy-snapshot.json` | `false` |
261
+ | `--snapshot-ignore` | Skip comparing against the snapshot even if it exists | `false` |
262
+ | `--failed` | Show only functions above the complexity threshold | `false` |
263
+ | `--suggest-refactors` | Show deterministic Rust AST-based refactor plans in rich CLI output. Ignored by `--plain` | `false` |
264
+ | `--color <auto\|yes\|no>` | Use color | `auto` |
265
+ | `--sort <asc\|desc\|file_name>` | Sort results | `asc` |
266
+ | `--quiet` | Suppress output | `false` |
267
+ | `--ignore-complexity` | Don't exit with error on threshold breach | `false` |
268
+ | `--version` | Show installed complexipy version and exit | - |
269
+ | `--top <n>` | Show only the `n` most complex functions, globally sorted by complexity descending | — |
270
+ | `--plain` | Emit plain text lines as `<path> <function> <complexity>`. Cannot be combined with `--quiet` | `false` |
271
+ | `--output-format <format>` | Select a machine-readable output format. Repeat the flag to request multiple formats (`json`, `csv`, `gitlab`, `sarif`) | — |
272
+ | `--output <path>` | Write machine-readable output to a file or directory. Use a directory when emitting multiple formats | — |
273
+ | `--diff <ref>` | Show a complexity diff against a git reference and enforce the threshold. Fails on regressions above `--max-complexity-allowed` (see [Complexity Diff](#complexity-diff)) | — |
274
+ | `--diff-only <ref>` | Show a complexity diff visually without affecting the exit code (see [Complexity Diff](#complexity-diff)) | |
275
+ | `--ratchet`, `-R` | **Deprecated.** Use `--diff` instead, which now enforces by default | `false` |
276
+ | `--check-script` | Report module-level (script) complexity as a synthetic `<module>` entry | `false` |
277
+ | `--no-ignore` | Analyze every function, disregarding inline ignore comments (`# complexipy: ignore`, `# noqa: complexipy`) | `false` |
278
+ | `--report-ignored` | List every file:line where an ignore comment suppresses a function. Prints even under `--quiet` | `false` |
279
+ | `--output-json` | Deprecated alias for `--output-format json` | `false` |
280
+ | `--output-csv` | Deprecated alias for `--output-format csv` | `false` |
281
+ | `--output-gitlab` | Deprecated alias for `--output-format gitlab` | `false` |
282
+ | `--output-sarif` | Deprecated alias for `--output-format sarif` | `false` |
282
283
 
283
284
  Example:
284
285
 
@@ -332,7 +333,7 @@ Use `--snapshot-ignore` if you need to temporarily bypass the snapshot gate (for
332
333
 
333
334
  ### Complexity Diff
334
335
 
335
- Compare complexity against any git reference to see at a glance whether a branch or commit made things better or worse:
336
+ Compare complexity against any git reference to see whether a branch or commit made things better or worse:
336
337
 
337
338
  ```bash
338
339
  # Compare the working tree against the previous commit
@@ -340,29 +341,22 @@ complexipy . --diff HEAD~1
340
341
 
341
342
  # Compare against a named branch
342
343
  complexipy . --diff main
344
+ ```
343
345
 
344
- # Fail only on threshold-breaking regressions
345
- complexipy . --diff main --ratchet
346
+ By default `--diff` **enforces** the complexity threshold: the run exits with code `1` only when a change breaches the contract relative to `--max-complexity-allowed`:
346
347
 
347
- # Combine with other flags
348
- complexipy src/ --max-complexity-allowed 10 --diff HEAD~1
349
- ```
348
+ - A new function is introduced above the threshold, or
349
+ - an existing function's complexity increases **and** ends above the threshold (already-over functions getting worse also fail).
350
350
 
351
- `--ratchet` requires `--diff`. It exits with code `1` only when a new or modified function breaches `--max-complexity-allowed`; regressions that stay within the threshold are allowed.
351
+ Functions that regress but stay at or below the threshold (e.g. `3 → 4` with `-mx 15`) are **not** flagged the threshold is still the main contract, and `--diff` only catches regressions that actually break it.
352
352
 
353
- Sample output:
353
+ To see the diff visually without affecting the exit code, use `--diff-only` instead:
354
354
 
355
- ```
356
- Complexity diff (vs HEAD~1)
357
- ────────────────────────────────────────────────────────────────────────
358
- REGRESSED src/api.py::handle_request 12 → 19 (+7)
359
- IMPROVED src/utils.py::flatten_tree 22 → 14 (-8)
360
- NEW src/auth.py::validate_token 17 (new)
361
- ────────────────────────────────────────────────────────────────────────
362
- Net: 1 regressed, 1 improved, 1 new
355
+ ```bash
356
+ complexipy . --diff-only HEAD~1
363
357
  ```
364
358
 
365
- The diff is appended after the normal analysis output and does not affect the exit code. Requires `git` to be available and the analysed paths to be inside a git repository.
359
+ Requires `git` to be available and the analysed paths to be inside a git repository.
366
360
 
367
361
  ### Script Complexity
368
362