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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6dfa844a5bcd2fe116001652410d39f802c64623be5ec588afcc6ed331b9aac9
4
- data.tar.gz: 42c5d56c7b4c6640bd4040f7f5d8b35ea6acac0aae9285be4c6cb1aaf860f0fb
3
+ metadata.gz: 3d0895177148a918e4aab91d71e25978c2f0429d8f46920851d3aee7eb8a9525
4
+ data.tar.gz: 32df8627394b7f85cdf7aa81093ba560bef79309efa10a606941f298e176bf34
5
5
  SHA512:
6
- metadata.gz: 0ad833af0897a784aa0499d277884e011e9be71142d343c441ef0a12b1185c27f18c09b0b7780a3f8493e3ef0399faff5f97b252f51fb4713f3dd98d11b666a7
7
- data.tar.gz: 19cf09a9777aa06a4772fae21fee5a4043ba034efde9d2af13618461b20a602270879438c4aa626463c3ea14054bf1dc8a7b9a25cde09c9636439d9d567a30f3
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)
@@ -40,6 +40,7 @@ module ChurnVsComplexity
40
40
  subcommand: @subcommand,
41
41
  serializer: focus_serializer,
42
42
  baseline_path: @baseline_path,
43
+ language: @language,
43
44
  )
44
45
  end
45
46
 
@@ -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
- @low = low
16
- @high = high
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ChurnVsComplexity
4
- VERSION = '1.6.1'
4
+ VERSION = '1.6.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: churn_vs_complexity
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.1
4
+ version: 1.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik T. Madsen