churn_vs_complexity 1.6.1 → 1.6.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/churn_vs_complexity/focus/checker.rb +4 -3
- data/lib/churn_vs_complexity/focus/config.rb +1 -0
- data/lib/churn_vs_complexity/hotspots/checker.rb +3 -2
- data/lib/churn_vs_complexity/hotspots/config.rb +1 -1
- data/lib/churn_vs_complexity/hotspots/serializer.rb +2 -2
- data/lib/churn_vs_complexity/risk_annotator.rb +1 -1
- data/lib/churn_vs_complexity/risk_classifier.rb +27 -3
- data/lib/churn_vs_complexity/triage/checker.rb +1 -1
- data/lib/churn_vs_complexity/triage/serializer.rb +1 -1
- data/lib/churn_vs_complexity/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d0895177148a918e4aab91d71e25978c2f0429d8f46920851d3aee7eb8a9525
|
|
4
|
+
data.tar.gz: 32df8627394b7f85cdf7aa81093ba560bef79309efa10a606941f298e176bf34
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1bdb515d824ab01dcd9f279f072d430c9dbb5a386bde4c569e5821b63895eefb256e26d22c79f1da65c59e1c0508057487db5008155de946ea54652ca1947687
|
|
7
|
+
data.tar.gz: d3c5c601f54b852d5ed47d683e1e9c8d88526fce57191caab66f553c3326d9166008f3c409a7e1178535d290908b95f1780599af763c8bb1d995ff1117e38457
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [1.6.2] - 2026-02-19
|
|
2
|
+
|
|
3
|
+
### Changed
|
|
4
|
+
- Risk classification in triage, hotspots, and focus modes is now language-aware
|
|
5
|
+
- Ruby thresholds raised to low=30/high=70 (Flog scores 3-7x higher than cyclomatic tools)
|
|
6
|
+
- Go thresholds raised to low=15/high=40 (cognitive complexity penalises nesting)
|
|
7
|
+
- Java, Python, and JavaScript/TypeScript thresholds unchanged (low=10/high=25)
|
|
8
|
+
|
|
1
9
|
## [1.6.1] - 2026-02-19
|
|
2
10
|
|
|
3
11
|
### Changed
|
|
@@ -5,11 +5,12 @@ require 'json'
|
|
|
5
5
|
module ChurnVsComplexity
|
|
6
6
|
module Focus
|
|
7
7
|
class Checker
|
|
8
|
-
def initialize(engine:, subcommand:, serializer:, baseline_path:)
|
|
8
|
+
def initialize(engine:, subcommand:, serializer:, baseline_path:, language: nil)
|
|
9
9
|
@engine = engine
|
|
10
10
|
@subcommand = subcommand
|
|
11
11
|
@serializer = serializer
|
|
12
12
|
@baseline_path = baseline_path
|
|
13
|
+
@language = language
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def check(folder:)
|
|
@@ -24,7 +25,7 @@ module ChurnVsComplexity
|
|
|
24
25
|
|
|
25
26
|
def run_start(folder, baseline_path)
|
|
26
27
|
raw_result = @engine.check(folder:)
|
|
27
|
-
entries = RiskAnnotator.annotate(raw_result[:values_by_file])
|
|
28
|
+
entries = RiskAnnotator.annotate(raw_result[:values_by_file], language: @language)
|
|
28
29
|
|
|
29
30
|
baseline = {
|
|
30
31
|
timestamp: Time.now.utc.iso8601,
|
|
@@ -37,7 +38,7 @@ module ChurnVsComplexity
|
|
|
37
38
|
|
|
38
39
|
def run_end(folder, baseline_path)
|
|
39
40
|
raw_result = @engine.check(folder:)
|
|
40
|
-
current_entries = RiskAnnotator.annotate(raw_result[:values_by_file])
|
|
41
|
+
current_entries = RiskAnnotator.annotate(raw_result[:values_by_file], language: @language)
|
|
41
42
|
|
|
42
43
|
baseline = load_baseline(baseline_path)
|
|
43
44
|
@serializer.serialize(baseline:, current: current_entries)
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
module ChurnVsComplexity
|
|
4
4
|
module Hotspots
|
|
5
5
|
class Checker
|
|
6
|
-
def initialize(engine:, serializer:)
|
|
6
|
+
def initialize(engine:, serializer:, language: nil)
|
|
7
7
|
@engine = engine
|
|
8
8
|
@serializer = serializer
|
|
9
|
+
@language = language
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def check(folder:)
|
|
12
13
|
raw_result = @engine.check(folder:)
|
|
13
|
-
@serializer.serialize(raw_result)
|
|
14
|
+
@serializer.serialize(raw_result.merge(language: @language))
|
|
14
15
|
end
|
|
15
16
|
end
|
|
16
17
|
end
|
|
@@ -34,7 +34,7 @@ module ChurnVsComplexity
|
|
|
34
34
|
since: @since,
|
|
35
35
|
excluded: @excluded,
|
|
36
36
|
)
|
|
37
|
-
Checker.new(engine: normal_config.checker, serializer: hotspots_serializer)
|
|
37
|
+
Checker.new(engine: normal_config.checker, serializer: hotspots_serializer, language: @language)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
private
|
|
@@ -7,7 +7,7 @@ module ChurnVsComplexity
|
|
|
7
7
|
module Serializer
|
|
8
8
|
module Json
|
|
9
9
|
def self.serialize(result)
|
|
10
|
-
entries = RiskAnnotator.annotate(result[:values_by_file])
|
|
10
|
+
entries = RiskAnnotator.annotate(result[:values_by_file], language: result[:language])
|
|
11
11
|
entries.sort_by! { |e| -e[:gamma_score] }
|
|
12
12
|
|
|
13
13
|
JSON.generate({ generated: Time.now.utc.iso8601, files: entries,
|
|
@@ -23,7 +23,7 @@ module ChurnVsComplexity
|
|
|
23
23
|
}.freeze
|
|
24
24
|
|
|
25
25
|
def self.serialize(result)
|
|
26
|
-
entries = RiskAnnotator.annotate(result[:values_by_file])
|
|
26
|
+
entries = RiskAnnotator.annotate(result[:values_by_file], language: result[:language])
|
|
27
27
|
entries.sort_by! { |e| -e[:gamma_score] }
|
|
28
28
|
grouped = entries.group_by { |e| e[:risk] }
|
|
29
29
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module ChurnVsComplexity
|
|
4
4
|
module RiskAnnotator
|
|
5
|
-
def self.annotate(values_by_file, classifier: RiskClassifier.new)
|
|
5
|
+
def self.annotate(values_by_file, language: nil, classifier: RiskClassifier.new(language: language))
|
|
6
6
|
values_by_file.map { |file, values| build_entry(file, values, classifier) }
|
|
7
7
|
end
|
|
8
8
|
|
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ChurnVsComplexity
|
|
4
|
+
# Classifies files into low/medium/high risk based on gamma score
|
|
5
|
+
# (harmonic mean of churn and complexity).
|
|
6
|
+
#
|
|
7
|
+
# IMPORTANT: Complexity scores are NOT comparable across languages.
|
|
8
|
+
# Each language uses a different tool with a different numeric scale.
|
|
9
|
+
# All tools sum per-function scores across the file, but the magnitude differs:
|
|
10
|
+
#
|
|
11
|
+
# Language Tool Metric Trivial Complex (~100 LOC) Real-world range
|
|
12
|
+
# Ruby Flog Code pain (weighted) 0 ~286 0-88
|
|
13
|
+
# JavaScript/TS ESLint Cyclomatic complexity 3 ~62 10-50
|
|
14
|
+
# Python Radon Cyclomatic complexity 1 ~44 5-50
|
|
15
|
+
# Java PMD Cyclomatic complexity 1 ~39 1-40
|
|
16
|
+
# Go gocognit Cognitive complexity 0 ~87 0-50
|
|
17
|
+
#
|
|
18
|
+
# The DEFAULT_LOW and DEFAULT_HIGH thresholds below are rough midpoints
|
|
19
|
+
# suitable for Java/Python/JS. They are too aggressive for Ruby (Flog
|
|
20
|
+
# scores 3-7x higher) and Go (cognitive complexity penalises nesting).
|
|
21
|
+
# Use the constructor to pass language-appropriate thresholds.
|
|
4
22
|
class RiskClassifier
|
|
5
23
|
DEFAULT_LOW = 10
|
|
6
24
|
DEFAULT_HIGH = 25
|
|
7
25
|
|
|
26
|
+
LANGUAGE_DEFAULTS = {
|
|
27
|
+
ruby: { low: 30, high: 70 },
|
|
28
|
+
go: { low: 15, high: 40 },
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
8
31
|
RECOMMENDATIONS = {
|
|
9
32
|
'low' => 'Safe for quick changes.',
|
|
10
33
|
'medium' => 'Exercise judgement; consider tests for non-trivial changes.',
|
|
11
34
|
'high' => 'Write tests before modifying. Consider multi-agent review.',
|
|
12
35
|
}.freeze
|
|
13
36
|
|
|
14
|
-
def initialize(low: DEFAULT_LOW, high: DEFAULT_HIGH)
|
|
15
|
-
|
|
16
|
-
@
|
|
37
|
+
def initialize(low: DEFAULT_LOW, high: DEFAULT_HIGH, language: nil)
|
|
38
|
+
defaults = LANGUAGE_DEFAULTS.fetch(language, {})
|
|
39
|
+
@low = defaults.fetch(:low, low)
|
|
40
|
+
@high = defaults.fetch(:high, high)
|
|
17
41
|
end
|
|
18
42
|
|
|
19
43
|
def classify(gamma_score:)
|
|
@@ -16,7 +16,7 @@ module ChurnVsComplexity
|
|
|
16
16
|
folder = folder || dirs.first || '.'
|
|
17
17
|
engine = build_engine(files, folder)
|
|
18
18
|
raw_result = engine.check(folder:)
|
|
19
|
-
@serializer.serialize(raw_result)
|
|
19
|
+
@serializer.serialize(raw_result.merge(language: @language))
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
private
|
|
@@ -7,7 +7,7 @@ module ChurnVsComplexity
|
|
|
7
7
|
module Serializer
|
|
8
8
|
module Json
|
|
9
9
|
def self.serialize(result)
|
|
10
|
-
entries = RiskAnnotator.annotate(result[:values_by_file])
|
|
10
|
+
entries = RiskAnnotator.annotate(result[:values_by_file], language: result[:language])
|
|
11
11
|
JSON.generate({ files: entries, summary: RiskAnnotator.risk_summary(entries) })
|
|
12
12
|
end
|
|
13
13
|
end
|