Skip to content

feat(ci): add compare workflow for PR code quality diff#9

Merged
aspala merged 10 commits intomainfrom
compare-workflow
Mar 14, 2026
Merged

feat(ci): add compare workflow for PR code quality diff#9
aspala merged 10 commits intomainfrom
compare-workflow

Conversation

@aspala
Copy link
Member

@aspala aspala commented Mar 14, 2026

Summary

  • Adds .github/workflows/compare.yml to run codeqa compare on every PR
  • Computes the fork point via git merge-base HEAD <base.sha> and compares it against PR HEAD
  • Posts result as a sticky PR comment

Test plan

  • Open a PR and verify the compare workflow runs
  • Verify the sticky comment appears with the code quality diff

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Mar 14, 2026

Code Quality: PR Comparison

13 files compared (5 added, 8 modified)

%%{init: {'theme': 'neutral'}}%%
xychart-beta
    title "Code Health After PR"
    x-axis ["Readability", "Complexity", "Structure", "Duplication", "Naming", "Magic Numbers"]
    y-axis "Score" 0 --> 100
    bar [100, 17, 69, 57, 90, 81]
Loading
Readability    ████████████████████ 100 → ████████████████████ 100  🟢 +0
Complexity     ███░░░░░░░░░░░░░░░░░ 14 → ███░░░░░░░░░░░░░░░░░ 17  🔴 +3
Structure      ███████████████░░░░░ 76 → ██████████████░░░░░░ 69  🟡 -7
Duplication    ███████████░░░░░░░░░ 56 → ███████████░░░░░░░░░ 57  🟠 +1
Naming         ██████████████████░░ 88 → ██████████████████░░ 90  🟢 +2
Magic Numbers  ████████████████░░░░ 78 → ████████████████░░░░ 81  🟡 +3
File changes — no changes — all metrics stable

No metric data available.

Aggregate metrics

Aggregate Metrics

Metric Base Head Delta
branching.max_branching_density 0.47 2.87 +2.40
branching.mean_branching_density 0.15 0.37 🔴 +0.21
branching.min_branching_density 0.00 0.00 0.00
branching.std_branching_density 0.14 0.74 +0.60
casing_entropy.max_camel_case_count 1655.00 1659.00 +4.00
casing_entropy.max_entropy 1.67 1.67 +0.00
casing_entropy.max_macro_case_count 85.00 86.00 +1.00
casing_entropy.max_other_count 21.00 21.00 0.00
casing_entropy.max_pascal_case_count 519.00 522.00 +3.00
casing_entropy.max_snake_case_count 573.00 577.00 +4.00
casing_entropy.mean_camel_case_count 368.25 291.54 -76.71
casing_entropy.mean_entropy 1.40 1.32 🟢 -0.08
casing_entropy.mean_macro_case_count 85.00 86.00 +1.00
casing_entropy.mean_other_count 10.83 9.63 -1.21
casing_entropy.mean_pascal_case_count 112.13 88.00 -24.13
casing_entropy.mean_snake_case_count 136.71 99.08 -37.63
casing_entropy.min_camel_case_count 87.00 71.00 -16.00
casing_entropy.min_entropy 0.80 0.80 +0.01
casing_entropy.min_macro_case_count 85.00 86.00 +1.00
casing_entropy.min_other_count 2.00 1.00 -1.00
casing_entropy.min_pascal_case_count 23.00 15.00 -8.00
casing_entropy.min_snake_case_count 18.00 6.00 -12.00
casing_entropy.std_camel_case_count 492.82 405.89 -86.93
casing_entropy.std_entropy 0.25 0.26 +0.02
casing_entropy.std_macro_case_count 0.00 0.00 0.00
casing_entropy.std_other_count 5.90 6.85 +0.95
casing_entropy.std_pascal_case_count 156.07 129.16 -26.91
casing_entropy.std_snake_case_count 181.71 152.81 -28.90
compression.max_raw_bytes 33089.00 33228.00 +139.00
compression.max_redundancy 0.80 0.82 +0.02
compression.max_zlib_bytes 7253.00 7264.00 +11.00
compression.max_zlib_ratio 4.97 5.41 +0.43
compression.mean_raw_bytes 7990.88 6594.38 -1396.49
compression.mean_redundancy 0.69 0.67 🟢 -0.02
compression.mean_zlib_bytes 2099.38 1824.31 -275.07
compression.mean_zlib_ratio 3.40 3.27 -0.14
compression.min_raw_bytes 2265.00 1106.00 -1159.00
compression.min_redundancy 0.55 0.54 -0.01
compression.min_zlib_bytes 1016.00 491.00 -525.00
compression.min_zlib_ratio 2.23 2.16 -0.06
compression.std_raw_bytes 9572.03 8002.31 -1569.72
compression.std_redundancy 0.08 0.09 +0.01
compression.std_zlib_bytes 1967.49 1662.09 -305.39
compression.std_zlib_ratio 0.90 0.96 +0.05
entropy.max_char_entropy 5.49 5.47 -0.01
entropy.max_char_max_entropy 6.49 6.49 0.00
entropy.max_char_normalized 0.85 0.84 -0.00
entropy.max_token_entropy 8.91 8.91 +0.00
entropy.max_token_max_entropy 10.04 10.05 +0.00
entropy.max_token_normalized 0.97 0.98 +0.01
entropy.max_total_tokens 3042.00 3050.00 +8.00
entropy.max_vocab_size 1056.00 1058.00 +2.00
entropy.mean_char_entropy 4.73 4.70 -0.03
entropy.mean_char_max_entropy 6.25 6.22 -0.04
entropy.mean_char_normalized 0.76 0.76 -0.00
entropy.mean_token_entropy 7.31 7.17 -0.14
entropy.mean_token_max_entropy 8.02 7.86 -0.16
entropy.mean_token_normalized 0.91 0.91 0.00
entropy.mean_total_tokens 768.38 647.77 -120.61
entropy.mean_vocab_size 324.38 288.23 -36.14
entropy.min_char_entropy 3.85 3.84 -0.02
entropy.min_char_max_entropy 5.98 5.91 -0.07
entropy.min_char_normalized 0.64 0.64 -0.00
entropy.min_token_entropy 6.40 6.03 -0.37
entropy.min_token_max_entropy 7.31 6.36 -0.96
entropy.min_token_normalized 0.85 0.84 -0.02
entropy.min_total_tokens 241.00 131.00 -110.00
entropy.min_vocab_size 159.00 82.00 -77.00
entropy.std_char_entropy 0.42 0.35 -0.07
entropy.std_char_max_entropy 0.17 0.18 +0.02
entropy.std_char_normalized 0.05 0.05 -0.01
entropy.std_token_entropy 0.74 0.82 +0.09
entropy.std_token_max_entropy 0.83 0.87 +0.04
entropy.std_token_normalized 0.03 0.04 +0.01
entropy.std_total_tokens 872.94 727.66 -145.28
entropy.std_vocab_size 282.58 238.80 -43.78
function_metrics.max_avg_function_lines 98.00 115.50 +17.50
function_metrics.max_avg_param_count 1.95 2.00 +0.05
function_metrics.max_max_function_lines 174.00 209.00 +35.00
function_metrics.max_max_param_count 6.00 6.00 0.00
function_metrics.mean_avg_function_lines 24.66 21.12 🟢 -3.54
function_metrics.mean_avg_param_count 1.25 0.94 🟢 -0.31
function_metrics.mean_max_function_lines 61.25 51.92 🟢 -9.33
function_metrics.mean_max_param_count 3.13 2.31 🟢 -0.82
function_metrics.min_avg_function_lines 0.00 0.00 0.00
function_metrics.min_avg_param_count 0.00 0.00 0.00
function_metrics.min_max_function_lines 0.00 0.00 0.00
function_metrics.min_max_param_count 0.00 0.00 0.00
function_metrics.std_avg_function_lines 28.54 28.43 -0.11
function_metrics.std_avg_param_count 0.76 0.71 -0.05
function_metrics.std_max_function_lines 50.33 52.11 +1.77
function_metrics.std_max_param_count 2.31 2.13 -0.19
halstead.max_N1_total_operators 4014.00 4029.00 +15.00
halstead.max_N2_total_operands 2183.00 2194.00 +11.00
halstead.max_difficulty 106.75 109.02 +2.27
halstead.max_effort 6022292.76 6067671.69 +45378.93
halstead.max_estimated_bugs 18.80 18.89 +0.08
halstead.max_length 6197.00 6223.00 +26.00
halstead.max_n1_unique_operators 74.00 137.00 +63.00
halstead.max_n2_unique_operands 501.00 502.00 +1.00
halstead.max_time_to_implement_seconds 334571.82 337092.87 +2521.05
halstead.max_vocabulary 550.00 551.00 +1.00
halstead.max_volume 56413.07 56666.07 +252.99
halstead.mean_N1_total_operators 1032.13 840.38 -191.74
halstead.mean_N2_total_operands 550.25 439.92 -110.33
halstead.mean_difficulty 57.00 56.52 🟢 -0.48
halstead.mean_effort 1061219.82 812109.75 🟢 -249110.07
halstead.mean_estimated_bugs 4.36 3.48 🟢 -0.88
halstead.mean_length 1582.38 1280.31 -302.07
halstead.mean_n1_unique_operators 45.38 51.85 +6.47
halstead.mean_n2_unique_operands 174.75 152.54 -22.21
halstead.mean_time_to_implement_seconds 58956.66 45117.21 -13839.45
halstead.mean_vocabulary 220.13 204.38 -15.74
halstead.mean_volume 13068.10 10441.74 🟢 -2626.35
halstead.min_N1_total_operators 256.00 119.00 -137.00
halstead.min_N2_total_operands 98.00 66.00 -32.00
halstead.min_difficulty 26.83 26.05 -0.77
halstead.min_effort 177708.43 29339.97 -148368.46
halstead.min_estimated_bugs 1.11 0.38 -0.73
halstead.min_length 442.00 185.00 -257.00
halstead.min_n1_unique_operators 19.00 19.00 0.00
halstead.min_n2_unique_operands 57.00 38.00 -19.00
halstead.min_time_to_implement_seconds 9872.69 1630.00 -8242.69
halstead.min_vocabulary 131.00 68.00 -63.00
halstead.min_volume 3318.45 1126.18 -2192.26
halstead.std_N1_total_operators 1140.98 979.60 -161.38
halstead.std_N2_total_operands 632.65 542.77 -89.89
halstead.std_difficulty 25.16 28.11 +2.95
halstead.std_effort 1888515.59 1549698.68 -338816.91
halstead.std_estimated_bugs 5.52 4.67 -0.85
halstead.std_length 1770.17 1516.91 -253.26
halstead.std_n1_unique_operators 17.48 33.73 +16.25
halstead.std_n2_unique_operands 127.20 116.06 -11.14
halstead.std_time_to_implement_seconds 104917.53 86094.37 -18823.16
halstead.std_vocabulary 126.66 119.82 -6.84
halstead.std_volume 16549.48 14001.63 -2547.85
heaps.max_beta 0.99 1.02 +0.03
heaps.max_k 3.52 3.90 +0.39
heaps.max_r_squared 1.00 1.00 +0.00
heaps.mean_beta 0.81 0.81 +0.00
heaps.mean_k 1.87 1.81 -0.06
heaps.mean_r_squared 0.99 0.99 0.00
heaps.min_beta 0.65 0.63 -0.02
heaps.min_k 0.91 0.82 -0.10
heaps.min_r_squared 0.96 0.97 +0.00
heaps.std_beta 0.09 0.10 +0.00
heaps.std_k 0.75 0.78 +0.03
heaps.std_r_squared 0.01 0.01 -0.00
identifier_length_variance.max_max 28.00 28.00 0.00
identifier_length_variance.max_mean 7.65 8.48 +0.83
identifier_length_variance.max_variance 25.19 25.66 +0.48
identifier_length_variance.mean_max 23.50 22.69 -0.81
identifier_length_variance.mean_mean 6.76 6.72 🟢 -0.04
identifier_length_variance.mean_variance 18.65 18.28 🟢 -0.37
identifier_length_variance.min_max 18.00 18.00 0.00
identifier_length_variance.min_mean 5.20 5.27 +0.07
identifier_length_variance.min_variance 11.89 11.63 -0.26
identifier_length_variance.std_max 3.39 3.54 +0.15
identifier_length_variance.std_mean 0.78 0.81 +0.03
identifier_length_variance.std_variance 4.55 4.34 -0.21
indentation.max_max_depth 14.00 14.00 0.00
indentation.max_mean_depth 9.59 9.86 +0.28
indentation.max_variance 9.02 9.91 +0.90
indentation.mean_max_depth 10.50 9.85 🟢 -0.65
indentation.mean_mean_depth 5.28 5.03 🟢 -0.25
indentation.mean_variance 6.05 5.75 🟢 -0.30
indentation.min_max_depth 6.00 6.00 0.00
indentation.min_mean_depth 2.46 2.55 +0.08
indentation.min_variance 2.65 2.07 -0.58
indentation.std_max_depth 2.60 2.41 -0.19
indentation.std_mean_depth 1.90 1.86 -0.05
indentation.std_variance 1.96 2.12 +0.17
magic_number_density.max_density 0.21 0.22 +0.01
magic_number_density.max_magic_number_count 105.00 130.00 +25.00
magic_number_density.mean_density 0.04 0.03 🟢 -0.00
magic_number_density.mean_magic_number_count 22.38 19.46 -2.91
magic_number_density.min_density 0.00 0.00 0.00
magic_number_density.min_magic_number_count 0.00 0.00 0.00
magic_number_density.std_density 0.07 0.06 -0.01
magic_number_density.std_magic_number_count 33.38 34.37 +0.99
ngram.max_bigram_hapax_fraction 0.97 0.98 +0.01
ngram.max_bigram_repetition_rate 0.51 0.51 +0.00
ngram.max_bigram_total 3041.00 3049.00 +8.00
ngram.max_bigram_unique 1956.00 1960.00 +4.00
ngram.max_trigram_hapax_fraction 0.99 0.99 +0.00
ngram.max_trigram_repetition_rate 0.38 0.38 0.00
ngram.max_trigram_total 3040.00 3048.00 +8.00
ngram.max_trigram_unique 2262.00 2268.00 +6.00
ngram.mean_bigram_hapax_fraction 0.86 0.86 +0.00
ngram.mean_bigram_repetition_rate 0.31 0.31 🟢 -0.00
ngram.mean_bigram_total 767.38 646.77 -120.61
ngram.mean_bigram_unique 551.88 472.15 -79.72
ngram.mean_trigram_hapax_fraction 0.91 0.91 +0.00
ngram.mean_trigram_repetition_rate 0.19 0.19 🟢 -0.00
ngram.mean_trigram_total 766.38 645.77 -120.61
ngram.mean_trigram_unique 624.63 533.08 -91.55
ngram.min_bigram_hapax_fraction 0.76 0.75 -0.00
ngram.min_bigram_repetition_rate 0.06 0.04 -0.02
ngram.min_bigram_total 240.00 130.00 -110.00
ngram.min_bigram_unique 231.00 115.00 -116.00
ngram.min_trigram_hapax_fraction 0.83 0.83 -0.00
ngram.min_trigram_repetition_rate 0.02 0.01 -0.01
ngram.min_trigram_total 239.00 129.00 -110.00
ngram.min_trigram_unique 237.00 122.00 -115.00
ngram.std_bigram_hapax_fraction 0.07 0.07 +0.00
ngram.std_bigram_repetition_rate 0.15 0.15 +0.00
ngram.std_bigram_total 872.94 727.66 -145.28
ngram.std_bigram_unique 541.55 456.24 -85.31
ngram.std_trigram_hapax_fraction 0.05 0.05 -0.00
ngram.std_trigram_repetition_rate 0.12 0.12 -0.00
ngram.std_trigram_total 872.94 727.66 -145.28
ngram.std_trigram_unique 631.05 530.75 -100.30
readability.max_avg_line_length 39.32 41.92 +2.60
readability.max_avg_sub_words_per_id 1.28 1.32 +0.03
readability.max_avg_tokens_per_line 4.27 7.85 +3.58
readability.max_flesch_adapted 110.99 109.85 -1.14
readability.max_fog_adapted 3.53 4.43 +0.91
readability.max_total_lines 803.00 806.00 +3.00
readability.mean_avg_line_length 33.88 35.70 🔴 +1.83
readability.mean_avg_sub_words_per_id 1.22 1.19 🟢 -0.03
readability.mean_avg_tokens_per_line 3.55 4.20 🔴 +0.64
readability.mean_flesch_adapted 100.24 101.86 🟢 +1.62
readability.mean_fog_adapted 2.78 3.07 🔴 +0.28
readability.mean_total_lines 212.50 171.69 -40.81
readability.min_avg_line_length 24.71 25.28 +0.57
readability.min_avg_sub_words_per_id 1.10 1.07 -0.03
readability.min_avg_tokens_per_line 2.49 2.52 +0.03
readability.min_flesch_adapted 95.01 92.12 -2.89
readability.min_fog_adapted 2.15 2.12 -0.03
readability.min_total_lines 62.00 28.00 -34.00
readability.std_avg_line_length 4.20 4.55 +0.35
readability.std_avg_sub_words_per_id 0.05 0.07 +0.02
readability.std_avg_tokens_per_line 0.50 1.23 +0.73
readability.std_flesch_adapted 4.85 5.57 +0.72
readability.std_fog_adapted 0.56 0.73 +0.18
readability.std_total_lines 227.30 196.71 -30.60
symbol_density.max_density 0.21 0.22 +0.00
symbol_density.max_symbol_count 4134.00 4148.00 +14.00
symbol_density.mean_density 0.15 0.13 -0.02
symbol_density.mean_symbol_count 1114.13 901.85 -212.28
symbol_density.min_density 0.10 0.07 -0.03
symbol_density.min_symbol_count 219.00 125.00 -94.00
symbol_density.std_density 0.03 0.04 +0.00
symbol_density.std_symbol_count 1162.55 1024.42 -138.13
vocabulary.max_mattr 0.57 0.72 +0.15
vocabulary.max_raw_ttr 0.49 0.67 +0.17
vocabulary.max_total_identifiers 2768.00 2779.00 +11.00
vocabulary.max_unique_identifiers 541.00 542.00 +1.00
vocabulary.mean_mattr 0.48 0.52 +0.04
vocabulary.mean_raw_ttr 0.36 0.41 +0.05
vocabulary.mean_total_identifiers 618.75 483.54 -135.21
vocabulary.mean_unique_identifiers 166.88 143.77 -23.11
vocabulary.min_mattr 0.31 0.30 -0.01
vocabulary.min_raw_ttr 0.20 0.20 -0.00
vocabulary.min_total_identifiers 150.00 101.00 -49.00
vocabulary.min_unique_identifiers 74.00 51.00 -23.00
vocabulary.std_mattr 0.08 0.10 +0.02
vocabulary.std_raw_ttr 0.10 0.13 +0.03
vocabulary.std_total_identifiers 820.36 682.11 -138.25
vocabulary.std_unique_identifiers 144.56 121.41 -23.15
vowel_density.max_density 0.39 0.39 +0.00
vowel_density.mean_density 0.36 0.36 +0.00
vowel_density.min_density 0.34 0.34 -0.00
vowel_density.std_density 0.01 0.01 +0.00
zipf.max_exponent 0.82 0.85 +0.03
zipf.max_r_squared 0.95 0.95 0.00
zipf.max_total_tokens 3042.00 3050.00 +8.00
zipf.max_vocab_size 1056.00 1058.00 +2.00
zipf.mean_exponent 0.63 0.62 -0.01
zipf.mean_r_squared 0.86 0.87 +0.01
zipf.mean_total_tokens 768.38 647.77 -120.61
zipf.mean_vocab_size 324.38 288.23 -36.14
zipf.min_exponent 0.28 0.25 -0.03
zipf.min_r_squared 0.60 0.61 +0.01
zipf.min_total_tokens 241.00 131.00 -110.00
zipf.min_vocab_size 159.00 82.00 -77.00
zipf.std_exponent 0.16 0.15 -0.01
zipf.std_r_squared 0.11 0.08 -0.02
zipf.std_total_tokens 872.94 727.66 -145.28
zipf.std_vocab_size 282.58 238.80 -43.78

@github-actions
Copy link

github-actions bot commented Mar 14, 2026

🟡 Code Health: B+ (78/100)

55 files · codeqa-action · 2026-03-14

%%{init: {'theme': 'neutral'}}%%
xychart-beta
    title "Code Health Scores"
    x-axis ["Readability", "Complexity", "Structure", "Duplication", "Naming", "Magic Numbers"]
    y-axis "Score" 0 --> 100
    bar [100, 32, 84, 72, 94, 90]
Loading
Readability    ████████████████████  100  🟢 A
Complexity     ██████░░░░░░░░░░░░░░   32  🔴 D-
Structure      █████████████████░░░   84  🟡 B+
Duplication    ██████████████░░░░░░   72  🟡 B
Naming         ███████████████████░   94  🟢 A
Magic Numbers  ██████████████████░░   90  🟢 A-
🟢 Readability — A (100/100)

Codebase averages: flesch_adapted=103.06, fog_adapted=2.31, avg_tokens_per_line=3.93, avg_line_length=34.30

Metric Value Score
readability.flesch_adapted 103.06 100
readability.fog_adapted 2.31 100
readability.avg_tokens_per_line 3.93 100
readability.avg_line_length 34.30 100

Worst Offenders

File Grade Issues
lib/codeqa/health_report/formatter.ex
11 lines · 501 B
A (95) ↑ flesch_adapted=104.67 (avg: 103.06)
↓ fog_adapted=2.55 (avg: 2.31)
↓ avg_tokens_per_line=6.38 (avg: 3.93)
↓ avg_line_length=61.25 (avg: 34.30)
lib/codeqa/metrics/branching.ex
65 lines · 2.4 KB
A (96) ↑ flesch_adapted=108.19 (avg: 103.06)
↓ fog_adapted=3.52 (avg: 2.31)
↓ avg_tokens_per_line=7.85 (avg: 3.93)
↓ avg_line_length=35.30 (avg: 34.30)
lib/codeqa/cli/ui.ex
39 lines · 1.4 KB
A (96) ↑ flesch_adapted=102.63 (avg: 103.06)
↓ fog_adapted=3.91 (avg: 2.31)
↓ avg_tokens_per_line=7.26 (avg: 3.93)
↓ avg_line_length=41.44 (avg: 34.30)
lib/codeqa/metrics/token_normalizer.ex
32 lines · 1.2 KB
A (97) ↑ flesch_adapted=110.61 (avg: 103.06)
↓ fog_adapted=2.86 (avg: 2.31)
↓ avg_tokens_per_line=7.15 (avg: 3.93)
↓ avg_line_length=34.25 (avg: 34.30)
lib/codeqa/metrics/codebase_metric.ex
14 lines · 504 B
A (99) ↑ flesch_adapted=111.77 (avg: 103.06)
↓ fog_adapted=2.07 (avg: 2.31)
↓ avg_tokens_per_line=5.18 (avg: 3.93)
↓ avg_line_length=44.55 (avg: 34.30)
🔴 Complexity — D- (32/100)

Codebase averages: difficulty=36.52, effort=297806.01, volume=4860.41, estimated_bugs=1.62

Metric Value Score
halstead.difficulty 36.52 48
halstead.effort 297806.01 0
halstead.volume 4860.41 43
halstead.estimated_bugs 1.62 44

Worst Offenders

File Grade Issues
lib/codeqa/cli.ex
998 lines · 32.4 KB
F (0) ↓ difficulty=107.08 (avg: 36.52)
↓ effort=6067671.69 (avg: 297806.01)
↓ volume=56666.07 (avg: 4860.41)
↓ estimated_bugs=18.89 (avg: 1.62)
lib/codeqa/metrics/similarity.ex
344 lines · 10.4 KB
F (4) ↓ difficulty=82.68 (avg: 36.52)
↓ effort=1488938.06 (avg: 297806.01)
↓ volume=18007.72 (avg: 4860.41)
↓ estimated_bugs=6.00 (avg: 1.62)
lib/codeqa/formatter.ex
344 lines · 10.0 KB
E- (8) ↓ difficulty=60.10 (avg: 36.52)
↓ effort=1077767.59 (avg: 297806.01)
↓ volume=17931.43 (avg: 4860.41)
↓ estimated_bugs=5.98 (avg: 1.62)
lib/codeqa/health_report/grader.ex
209 lines · 6.6 KB
E- (11) ↓ difficulty=80.40 (avg: 36.52)
↓ effort=919519.55 (avg: 297806.01)
↓ volume=11437.51 (avg: 4860.41)
↓ estimated_bugs=3.81 (avg: 1.62)
lib/codeqa/metrics/halstead.ex
122 lines · 4.4 KB
E (13) ↓ difficulty=109.02 (avg: 36.52)
↓ effort=721979.13 (avg: 297806.01)
↓ volume=6622.38 (avg: 4860.41)
↓ estimated_bugs=2.21 (avg: 1.62)
🟡 Structure — B+ (84/100)

Codebase averages: branching_density=0.14, mean_depth=4.14, avg_function_lines=11.96, max_depth=9.67, max_function_lines=26.20, variance=5.99, avg_param_count=1.12, max_param_count=2.02

Metric Value Score
branching.branching_density 0.14 76
indentation.mean_depth 4.14 86
function_metrics.avg_function_lines 11.96 79
indentation.max_depth 9.67 86
function_metrics.max_function_lines 26.20 86
indentation.variance 5.99 100
function_metrics.avg_param_count 1.12 100
function_metrics.max_param_count 2.02 100

Worst Offenders

File Grade Issues
lib/codeqa/health_report/categories.ex
236 lines · 6.0 KB
C (61) ↓ branching_density=0.00 (avg: 0.14)
↓ mean_depth=9.86 (avg: 4.14)
↓ avg_function_lines=115.50 (avg: 11.96)
↓ max_depth=12 (avg: 9.67)
↓ max_function_lines=209 (avg: 26.20)
↓ variance=8.22 (avg: 5.99)
↓ avg_param_count=0.00 (avg: 1.12)
↓ max_param_count=0 (avg: 2.02)
lib/codeqa/comparator.ex
109 lines · 3.5 KB
B- (69) ↓ branching_density=0.08 (avg: 0.14)
↓ mean_depth=6.47 (avg: 4.14)
↓ avg_function_lines=15.14 (avg: 11.96)
↓ max_depth=81 (avg: 9.67)
↓ max_function_lines=42 (avg: 26.20)
↓ variance=67.27 (avg: 5.99)
↓ avg_param_count=2.00 (avg: 1.12)
↓ max_param_count=3 (avg: 2.02)
lib/codeqa/metrics/branching.ex
65 lines · 2.4 KB
B (72) ↓ branching_density=2.87 (avg: 0.14)
↓ mean_depth=3.04 (avg: 4.14)
↓ avg_function_lines=8.33 (avg: 11.96)
↓ max_depth=8 (avg: 9.67)
↓ max_function_lines=19 (avg: 26.20)
↓ variance=3.22 (avg: 5.99)
↓ avg_param_count=0.67 (avg: 1.12)
↓ max_param_count=2 (avg: 2.02)
lib/codeqa/metrics/halstead.ex
122 lines · 4.4 KB
B (73) ↓ branching_density=0.34 (avg: 0.14)
↓ mean_depth=4.32 (avg: 4.14)
↓ avg_function_lines=15.43 (avg: 11.96)
↓ max_depth=8 (avg: 9.67)
↓ max_function_lines=40 (avg: 26.20)
↓ variance=4.28 (avg: 5.99)
↓ avg_param_count=1.57 (avg: 1.12)
↓ max_param_count=6 (avg: 2.02)
lib/codeqa/cli.ex
998 lines · 32.4 KB
B (75) ↓ branching_density=0.10 (avg: 0.14)
↓ mean_depth=5.94 (avg: 4.14)
↓ avg_function_lines=22.57 (avg: 11.96)
↓ max_depth=14 (avg: 9.67)
↓ max_function_lines=103 (avg: 26.20)
↓ variance=6.49 (avg: 5.99)
↓ avg_param_count=1.98 (avg: 1.12)
↓ max_param_count=6 (avg: 2.02)
🟡 Duplication — B (72/100)

Codebase averages: redundancy=0.56, bigram_repetition_rate=0.20, trigram_repetition_rate=0.10

Metric Value Score
compression.redundancy 0.56 62
ngram.bigram_repetition_rate 0.20 83
ngram.trigram_repetition_rate 0.10 79

Worst Offenders

File Grade Issues
lib/codeqa/cli.ex
998 lines · 32.4 KB
D (36) ↓ redundancy=0.78 (avg: 0.56)
↓ bigram_repetition_rate=0.51 (avg: 0.20)
↓ trigram_repetition_rate=0.38 (avg: 0.10)
lib/codeqa/health_report/categories.ex
236 lines · 6.0 KB
D (37) ↓ redundancy=0.82 (avg: 0.56)
↓ bigram_repetition_rate=0.50 (avg: 0.20)
↓ trigram_repetition_rate=0.34 (avg: 0.10)
test/codeqa/health_report/formatter_test.exs
140 lines · 4.8 KB
D (40) ↓ redundancy=0.76 (avg: 0.56)
↓ bigram_repetition_rate=0.48 (avg: 0.20)
↓ trigram_repetition_rate=0.35 (avg: 0.10)
lib/codeqa/formatter.ex
344 lines · 10.0 KB
D+ (43) ↓ redundancy=0.74 (avg: 0.56)
↓ bigram_repetition_rate=0.46 (avg: 0.20)
↓ trigram_repetition_rate=0.31 (avg: 0.10)
test/codeqa/formatter_test.exs
137 lines · 4.0 KB
D+ (44) ↓ redundancy=0.75 (avg: 0.56)
↓ bigram_repetition_rate=0.46 (avg: 0.20)
↓ trigram_repetition_rate=0.27 (avg: 0.10)
🟢 Naming — A (94/100)

Codebase averages: entropy=1.28, mean=6.33, variance=14.48, avg_sub_words_per_id=1.18

Metric Value Score
casing_entropy.entropy 1.28 79
identifier_length_variance.mean 6.33 100
identifier_length_variance.variance 14.48 100
readability.avg_sub_words_per_id 1.18 100

Worst Offenders

File Grade Issues
lib/codeqa/analyzer.ex
130 lines · 4.2 KB
B+ (83) ↓ entropy=1.67 (avg: 1.28)
↓ mean=7.75 (avg: 6.33)
↓ variance=25.66 (avg: 14.48)
↓ avg_sub_words_per_id=1.29 (avg: 1.18)
lib/codeqa/registry.ex
65 lines · 2.0 KB
B+ (83) ↓ entropy=1.52 (avg: 1.28)
↓ mean=6.96 (avg: 6.33)
↓ variance=30.14 (avg: 14.48)
↓ avg_sub_words_per_id=1.24 (avg: 1.18)
lib/codeqa/telemetry.ex
68 lines · 1.8 KB
A- (85) ↓ entropy=1.74 (avg: 1.28)
↓ mean=7.05 (avg: 6.33)
↓ variance=17.87 (avg: 14.48)
↓ avg_sub_words_per_id=1.24 (avg: 1.18)
lib/codeqa/metrics/similarity.ex
344 lines · 10.4 KB
A- (85) ↓ entropy=1.50 (avg: 1.28)
↓ mean=7.14 (avg: 6.33)
↓ variance=26.45 (avg: 14.48)
↓ avg_sub_words_per_id=1.31 (avg: 1.18)
lib/codeqa/metrics/halstead.ex
122 lines · 4.4 KB
A- (86) ↓ entropy=1.49 (avg: 1.28)
↓ mean=7.14 (avg: 6.33)
↓ variance=22.83 (avg: 14.48)
↓ avg_sub_words_per_id=1.16 (avg: 1.18)
🟢 Magic Numbers — A- (90/100)

Codebase averages: density=0.02

Metric Value Score
magic_number_density.density 0.02 90

Worst Offenders

File Grade Issues
lib/codeqa/health_report/categories.ex
236 lines · 6.0 KB
D- (27) ↓ density=0.22 (avg: 0.02)
mix.exs
35 lines · 730 B
C- (50) ↓ density=0.10 (avg: 0.02)
lib/codeqa/metrics/inflector.ex
18 lines · 592 B
C- (51) ↓ density=0.10 (avg: 0.02)
test/codeqa/formatter_test.exs
137 lines · 4.0 KB
C- (54) ↓ density=0.09 (avg: 0.02)
test/codeqa/health_report/formatter_test.exs
140 lines · 4.8 KB
C+ (64) ↓ density=0.07 (avg: 0.02)

aspala and others added 9 commits March 14, 2026 18:34
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends the structure health category with five new metrics beyond
indentation depth:

- branching_density: control-flow keywords / non-blank lines, a
  language-agnostic proxy for cyclomatic complexity
- avg_function_lines / max_function_lines: estimated via function-keyword
  detection at line start, catches god-function smells
- avg_param_count / max_param_count: comma-count in function signatures,
  signals missing abstractions

Covers Python, Ruby, JavaScript, Elixir, C#, Java, C++, Go, Rust,
PHP, Swift, Shell, and Kotlin via keyword patterns and an access-modifier
regex for C#/Java.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updates the keyword blocklist in Pipeline and the operator keyword set
in Halstead to cover Python, Ruby, JavaScript, Elixir, C#, Java, C++,
Go, Rust, PHP, Swift, Shell, and Kotlin.

Previously the list was Python-focused with a mix of Rust/Go words
that leaked into identifier counts for the five primary target languages.
Now keywords are organised by language with clear comments, and
language-specific words (synchronized, defer, suspend, willSet, etc.)
no longer pollute identifier or operand analysis.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests for Branching and FunctionMetrics are generated directly from
the modules' public keyword lists, so adding a keyword automatically
produces a new test case on the next run.

- branching_keywords/0 exposes the MapSet for test generation
- func_keywords/0 and access_modifiers/0 expose the function-detection
  keyword and C#/Java access modifier lists

54 tests total: 31 per-keyword branching tests, 10 func-keyword tests,
4 access-modifier tests, and 9 hand-written behaviour tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Based on observed codebase averages, the A thresholds were too generous —
most metrics scored A despite averages that indicate room for improvement.

Changes (A threshold → new A threshold):
  branching_density  0.10 → 0.08
  mean_depth         4    → 3.5
  avg_function_lines 10   → 8
  max_depth          12   → 8
  max_function_lines 25   → 20
  variance           10   → 7

B/C/D thresholds tightened proportionally. avg_param_count and
max_param_count unchanged — codebase average well within A.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@aspala aspala merged commit c806538 into main Mar 14, 2026
3 checks passed
@aspala aspala deleted the compare-workflow branch March 14, 2026 18:35
aspala added a commit that referenced this pull request Mar 15, 2026
* feat(ci): add compare workflow for PR code quality diff

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(grader): use pattern matching in score_metric/2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(formatter): guard against nil files in format_markdown/2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(compare): add github format with mermaid chart and progress bars

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(structure): add branching density and function metrics

Extends the structure health category with five new metrics beyond
indentation depth:

- branching_density: control-flow keywords / non-blank lines, a
  language-agnostic proxy for cyclomatic complexity
- avg_function_lines / max_function_lines: estimated via function-keyword
  detection at line start, catches god-function smells
- avg_param_count / max_param_count: comma-count in function signatures,
  signals missing abstractions

Covers Python, Ruby, JavaScript, Elixir, C#, Java, C++, Go, Rust,
PHP, Swift, Shell, and Kotlin via keyword patterns and an access-modifier
regex for C#/Java.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(keywords): expand keyword coverage to 13 languages

Updates the keyword blocklist in Pipeline and the operator keyword set
in Halstead to cover Python, Ruby, JavaScript, Elixir, C#, Java, C++,
Go, Rust, PHP, Swift, Shell, and Kotlin.

Previously the list was Python-focused with a mix of Rust/Go words
that leaked into identifier counts for the five primary target languages.
Now keywords are organised by language with clear comments, and
language-specific words (synchronized, defer, suspend, willSet, etc.)
no longer pollute identifier or operand analysis.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(metrics): add auto-generated keyword coverage tests

Tests for Branching and FunctionMetrics are generated directly from
the modules' public keyword lists, so adding a keyword automatically
produces a new test case on the next run.

- branching_keywords/0 exposes the MapSet for test generation
- func_keywords/0 and access_modifiers/0 expose the function-detection
  keyword and C#/Java access modifier lists

54 tests total: 31 per-keyword branching tests, 10 func-keyword tests,
4 access-modifier tests, and 9 hand-written behaviour tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(compare): add 🟢/🔴 direction emoji to aggregate metric deltas

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(structure): tighten structure category thresholds

Based on observed codebase averages, the A thresholds were too generous —
most metrics scored A despite averages that indicate room for improvement.

Changes (A threshold → new A threshold):
  branching_density  0.10 → 0.08
  mean_depth         4    → 3.5
  avg_function_lines 10   → 8
  max_depth          12   → 8
  max_function_lines 25   → 20
  variance           10   → 7

B/C/D thresholds tightened proportionally. avg_param_count and
max_param_count unchanged — codebase average well within A.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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