You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add cognitive complexity as a per-function metric, and introduce the metric registry it needs.
Why
Cyclomatic complexity counts branches; it cannot tell a 30-arm dispatch from a 5-level nest. Two real cases from the prova-clara scan (details in #6):
src/lib/server/env.ts:107validateProductionServerEnv — complexity 54, a flat sequence of guard clauses.
src/lib/assessment-context/diff.ts:48summarizeProfileField — complexity 30, a switch with independent arms.
Both are readable and both fail today. Cognitive complexity penalizes nesting and continuity breaks instead, and would score both near zero. Same reason Rust consumers need per-repo complexity overrides for large match expressions. Already listed as a v2 candidate in docs/spec.md.
Spec first
AGENTS.md makes docs/spec.md the contract, and cognitive complexity is underspecified by name alone — without one pinned algorithm the golden fixtures become arbitrary. PR 1 is spec-only and carries all the judgment:
Which nodes increment, which nest, and by how much.
else / elif / catch chain handling.
Sequences of like logical operators (one increment per sequence, not per operator).
Recursion.
One explicit rule table per language: js_decision, dart_decision, rust_decision, python_decision, go_decision equivalents.
Svelte template rules, defined independently — they cannot inherit the TS rules.
Metric registry
Limits is a fixed struct of four usize fields and add_violations walks a hard-coded array, so there is no way to add a metric that exists for only some languages. Building the registry alongside cognitive complexity is cheaper than retrofitting it, and it unblocks later per-language checks (explicit any / as assertions for TS, unwrap() for Rust, bare except: for Python, ignored errors for Go, ! assertions for Dart). Those are follow-ups, not scope here.
Implementation notes
Add a separate traversal beside measure_node in crates/core/src/language.rs. Do not extend it — cyclomatic and cognitive deliberately count different things.
New field on FunctionMetrics; extend Score and measure_function.
crates/core/src/config.rs: Limits, LimitOverrides, validate_keys, validate_language_keys, Config::limits_for. The strict validator must admit the new key.
Every tests/fixtures/<language>/expected.json gains a cognitive value, including the negative cases AGENTS.md requires (operator inside a string literal, unbraced else <loop>, _ when guard).
Output format, JSON shape (Violation.metric), and exit codes are unchanged.
Checklist
PR 1: docs/spec.md — cognitive complexity section, algorithm pinned per language
Parent: #6
Add cognitive complexity as a per-function metric, and introduce the metric registry it needs.
Why
Cyclomatic complexity counts branches; it cannot tell a 30-arm dispatch from a 5-level nest. Two real cases from the prova-clara scan (details in #6):
src/lib/server/env.ts:107validateProductionServerEnv— complexity 54, a flat sequence of guard clauses.src/lib/assessment-context/diff.ts:48summarizeProfileField— complexity 30, aswitchwith independent arms.Both are readable and both fail today. Cognitive complexity penalizes nesting and continuity breaks instead, and would score both near zero. Same reason Rust consumers need per-repo
complexityoverrides for largematchexpressions. Already listed as a v2 candidate indocs/spec.md.Spec first
AGENTS.mdmakesdocs/spec.mdthe contract, and cognitive complexity is underspecified by name alone — without one pinned algorithm the golden fixtures become arbitrary. PR 1 is spec-only and carries all the judgment:else/elif/catchchain handling.js_decision,dart_decision,rust_decision,python_decision,go_decisionequivalents.Metric registry
Limitsis a fixed struct of fourusizefields andadd_violationswalks a hard-coded array, so there is no way to add a metric that exists for only some languages. Building the registry alongside cognitive complexity is cheaper than retrofitting it, and it unblocks later per-language checks (explicitany/asassertions for TS,unwrap()for Rust, bareexcept:for Python, ignored errors for Go,!assertions for Dart). Those are follow-ups, not scope here.Implementation notes
measure_nodeincrates/core/src/language.rs. Do not extend it — cyclomatic and cognitive deliberately count different things.FunctionMetrics; extendScoreandmeasure_function.crates/core/src/config.rs:Limits,LimitOverrides,validate_keys,validate_language_keys,Config::limits_for. The strict validator must admit the new key.crates/core/src/scan.rs: emit inadd_violations.config.default.json:limits.cognitive(default 15, pending the rollout decision in Metric expansion: cognitive complexity and Svelte template scoping #6).tests/fixtures/<language>/expected.jsongains acognitivevalue, including the negative casesAGENTS.mdrequires (operator inside a string literal, unbracedelse <loop>,_ whenguard).Violation.metric), and exit codes are unchanged.Checklist
docs/spec.md— cognitive complexity section, algorithm pinned per languageFunctionMetricsfieldvalidateProductionServerEnvandsummarizeProfileFieldpass on defaultsmatchcases no longer need per-repo overridesValidation
Risks
Status: Planned
Next action: write the spec section