Skip to content

Add an optional Cognitive Complexity gate (Campbell 2018) - #27

Merged
sagenschneider merged 3 commits into
mainfrom
cognitive-gate
Sep 22, 2026
Merged

sagenschneider merged 3 commits into
mainfrom
cognitive-gate

Conversation

@sagenschneider

Copy link
Copy Markdown
Contributor

Why

The change-impact composite (and the curve) measure how a change moves — they cannot see a method that is simply hard to read: working code with deep decision nesting. That is the shape AI code generators reliably produce, so it is the gate worth adding for AI-augmented review.

This adds an absolute, per-method gate on Cognitive Complexity (Campbell 2018), independent of the change-impact model. A small diff can pass on impact yet block here for leaving a tangled method behind. Off by default.

What

  • core/cognitive.py — a language-agnostic estimator, computed in the existing lizard pass (C-family via braces, Python via indentation, brace default otherwise). Flat/sequential code costs nothing; nesting is penalised (each if/loop/switch/catch costs 1 + its depth).
  • Unit.cognitive populated by LizardPlugin; ChangeScore carries cognitive_max + offenders.
  • Scoped to the methods the change touched — new files, or functions overlapping the added line ranges. A pre-existing, accepted complex method elsewhere in a touched file is never re-flagged.
  • GateConfig.cognitive_max (default null = off) + blocks_cognitive(); --cognitive-max N CLI flag; cognitive section in json/text/markdown naming the methods to break up; action.yml input.
  • Independent of the curve/absolute impact gate; OR'd into the block decision.

Design notes

  • The change-impact composite, the shipped seed distribution, and the WMC/GodClass definitions are unchanged — cognitive is a separate signal, not a re-weighting of the composite.
  • The estimator is ImpactGate's own implementation of the metric (parser-free, cross-language); it is calibrated in the test suite against PMD (Java) values. Documented simplifications: ternary and nested-lambda nesting are not counted (uniform slight under-count a gate threshold absorbs).

Tests

16 new tests (tests/test_cognitive.py) covering the estimator (incl. the PMD-calibrated nesting value, boolean runs, Python, flat=0), the plugin field, changed-method scoping, the gate, and output. Full suite green (106 passed).

Usage

```sh
impact-gate score --enforcement block --cognitive-max 15 # SonarSource's line
```

🤖 Generated with Claude Code

Change-impact and the curve measure how a change MOVES; they cannot see a method that is
simply hard to read (working code with deep decision nesting) — the shape AI code generators
reliably produce. This adds an absolute, per-method readability gate, independent of the
change-impact composite, so a small diff can pass on impact yet block for leaving a tangled
method behind.

- core/cognitive.py: a language-agnostic estimator of Cognitive Complexity, computed in the
  lizard pass (C-family via braces, Python via indentation, brace default otherwise). Flat
  sequential code costs nothing; nesting is penalised. Calibrated in tests against PMD (Java)
  and hand-verified values; documented simplifications (ternary/lambda).
- Unit.cognitive + LizardPlugin populates it; ChangeScore carries cognitive_max + offenders.
- Scoped to the methods the change TOUCHED (new files, or functions overlapping the added
  line ranges) — never re-flags pre-existing, accepted complexity elsewhere in a touched file.
- GateConfig.cognitive_max (default null = off) + blocks_cognitive(); --cognitive-max CLI flag;
  cognitive section in json/text/markdown output naming the methods to break up; action.yml input.
- Independent of the curve/absolute impact gate; OR'd into the block decision.

Off by default; the change-impact composite, seed distribution and WMC/GodClass definitions are
unchanged. 16 new tests; full suite green (106).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Change impact: 205,320 ⚠️ WARN

field value
files changed 8
scope vs origin/main (merge-base..HEAD)

Over the block threshold. This will fail once enforcement is set to block.

Files to consider for refactoring

cost file existing new fns changed fns new
18,434 impact_gate/report.py 11,378 7,056 3 3
2,778 impact_gate/cli.py 2,778 0 2 0
2,109 impact_gate/engine.py 576 1,533 1 2
1,392 impact_gate/config.py 468 924 1 3
774 impact_gate/core/cognitive.py 0 774 0 10

Top cost drivers. Simplify or refactor these.

cost location class CC WMC_other kind
9,625 impact_gate/report.py:render_markdown file scope 25 35 mutation
3,920 impact_gate/report.py:_cognitive_lines file scope 5 56 godclass
2,508 impact_gate/cli.py:_cmd_score file scope 11 38 mutation
2,016 impact_gate/report.py:cognitive_report file scope 3 56 godclass
1,120 impact_gate/report.py:_cognitive_offenders file scope 4 56 godclass

Methods over the cognitive-complexity limit (20)

Break these into smaller methods — reduce nesting/decision depth (sequential code is fine).

cognitive location class
24 impact_gate/report.py:render_markdown file scope

sagenschneider and others added 2 commits September 22, 2026 23:41
…he line

- .github/workflows/impact.yml: run impact-gate on itself with cognitive-max 20 (enforcement
  stays warn — report over-limit methods on every PR without failing the build).
- Simplify the gate's own hot methods so this PR's touched code is under 20:
    - _cognitive_c: dict-dispatch on token type via a small _CScan state object (27 -> 11).
    - score_change: extract the cognitive collection into _collect_cognitive (21 -> 15).
  Calibration unchanged (106 tests green). Pre-existing over-limit methods elsewhere are
  untouched here, so the changed-method scope leaves them alone; warn surfaces them when a
  future PR edits them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sagenschneider
sagenschneider merged commit 3db994e into main Sep 22, 2026
5 checks passed
@sagenschneider
sagenschneider deleted the cognitive-gate branch September 22, 2026 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant