rubocop-legion 0.1.9 → 0.1.11

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: 88034e8168ad8e5634a580f3a07ca2f030bd46be31b6c9607dbc64a28935bcd0
4
- data.tar.gz: 353647b902a3271fe17809769674778b528eb3e97dda8704934addf80b69b397
3
+ metadata.gz: ab19d4bce0d274768b632aa0579aa8997cb503befa24d4d613442f4cc894cb09
4
+ data.tar.gz: 85caebdfc215b98b74febd7b59c09c90fc6574e36f9dd2c126c866ca43138cde
5
5
  SHA512:
6
- metadata.gz: 0b7d05703e9bd6762628d25f082a738fba9972f66eda2b650ab5e3c0dace6b3569df16c754f935f8f62caf861eaf3984018c3a4f3f73de01cc0871fcf91cfebd
7
- data.tar.gz: d598e5090c4ba1242ec371fd2d3b935412f9fe8c4e24fe907a9e18bd6052999568bed0c252a983559e4b33e1c0502a724e02df5244db46b98c5cdad899f62e93
6
+ metadata.gz: 93425642d48e67c4897e9466b34394aa88b43048435c0841d0c6b1c05a6eee4ce022fd3211757ed6a3de084b5f154dea614a764e763b92a845be779cc717a0b9
7
+ data.tar.gz: e45f70a81e9e658b90e984a5561e027d88044450831c0a714d31f96a231e3933662128d167b60666624d3631077672898f8e5a50627c77e267423b00f9fb32f2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.11] - 2026-08-04
4
+
5
+ ### Added
6
+ - New cop `Legion/Framework/NoUnusedArgDisable` (universal): bans `# rubocop:disable`/`:todo`/`:enable Lint/UnusedMethodArgument`. An unused kwarg is a real offense, not something to hide — autocorrect removes the directive so it resurfaces and must be fixed properly (drop the arg, or `**` → `**opts` and read `opts[:key]`). Single-cop directives autocorrect; multi-cop directives are flagged without autocorrect. `# rubocop:disable all` is out of scope.
7
+
8
+ ### Fixed
9
+ - `Legion/Framework/NoUnderscorePrefixedKwargs` now autocorrects an unused splat (`**_opts`, `**_rest`, `**_`) to `**` (nameless accept-and-ignore) instead of `**opts`. The underscore signals the splat is unused; rewriting to a named `**opts` produced a named-but-unused splat that falsely claims the body reads `opts`. `**opts` is correct only when the body actually reads it.
10
+
11
+ ## [0.1.10] - 2026-07-31
12
+
13
+ ### Fixed
14
+ - `Legion/Llm/TaxonomyEnum` no longer over-matches: `:type` is only validated when it appears alongside `:tier` or `:circuit_state` in the same hash (taxonomy context). Previously any `{ type: :string }` or schema hash triggered a false positive.
15
+ - Added `Legion/Llm` department to `config/default.yml` — all four cops (`TaxonomyEnum`, `SettingsAccessPath`, `NoLoopDo`, `RescueLogLevel`) were loaded but never registered in the config, causing consumer repos to report "unrecognized cop" when referencing `Legion/Llm/TaxonomyEnum`.
16
+ - `TaxonomyEnum` is now Include-scoped to `lib/legion/llm/**/*.rb` and `lib/legion/extensions/llm/**/*.rb` so it only runs on LLM routing code.
17
+
3
18
  ## [0.1.8] - 2026-06-16
4
19
 
5
20
  ### Added
data/config/default.yml CHANGED
@@ -340,11 +340,17 @@ Legion/Extension/DefinitionCallMismatched:
340
340
  # Legion/Framework — N×N routing guard cops (Phase 6 enforcement)
341
341
 
342
342
  Legion/Framework/NoUnderscorePrefixedKwargs:
343
- Description: 'Ban underscore-prefixed kwargs and `**_rest` splats. Use plain kwarg, defaulted kwarg, or `**opts`.'
343
+ Description: 'Ban underscore-prefixed kwargs and `**_rest` splats. Use plain kwarg, defaulted kwarg, `**opts`, or `**`.'
344
344
  Enabled: true
345
345
  Severity: warning
346
346
  VersionAdded: '0.1.8'
347
347
 
348
+ Legion/Framework/NoUnusedArgDisable:
349
+ Description: 'Ban `# rubocop:disable Lint/UnusedMethodArgument`. Drop the arg or use `**opts`.'
350
+ Enabled: true
351
+ Severity: warning
352
+ VersionAdded: '0.1.11'
353
+
348
354
  Legion/Framework/NoInlineSettingDefaults:
349
355
  Description: 'Flag `Legion::Settings[...] || <literal>` and shadow-default patterns. Defaults belong in `settings.rb`.'
350
356
  Enabled: true
@@ -363,3 +369,38 @@ Legion/Framework/NoShapeDuckTyping:
363
369
  Severity: convention
364
370
  VersionAdded: '0.1.8'
365
371
  Safe: false
372
+
373
+ # Legion/Llm — cops specific to legion-llm and lex-llm-* provider pipelines
374
+
375
+ Legion/Llm:
376
+ Enabled: true
377
+ Include:
378
+ - 'lib/legion/llm/**/*.rb'
379
+ - 'lib/legion/extensions/llm/**/*.rb'
380
+
381
+ Legion/Llm/TaxonomyEnum:
382
+ Description: 'Validate :tier, :type, and :circuit_state literals against LegionIO taxonomy enums.'
383
+ Enabled: true
384
+ Severity: convention
385
+ VersionAdded: '0.1.9'
386
+ Include:
387
+ - 'lib/legion/llm/**/*.rb'
388
+ - 'lib/legion/extensions/llm/**/*.rb'
389
+
390
+ Legion/Llm/SettingsAccessPath:
391
+ Description: 'Use `Legion::Settings.dig(...)` instead of `Legion::Settings.loader.settings[...][...]`.'
392
+ Enabled: true
393
+ Severity: convention
394
+ VersionAdded: '0.1.9'
395
+
396
+ Legion/Llm/NoLoopDo:
397
+ Description: 'Ban `loop do` — use bounded iteration (`N.times`, `each`, or `while` with explicit bound).'
398
+ Enabled: true
399
+ Severity: warning
400
+ VersionAdded: '0.1.9'
401
+
402
+ Legion/Llm/RescueLogLevel:
403
+ Description: 'Minimum log level for `handle_exception` in rescue blocks is `:warn`.'
404
+ Enabled: true
405
+ Severity: warning
406
+ VersionAdded: '0.1.9'
@@ -18,15 +18,16 @@ module RuboCop
18
18
  # # good
19
19
  # def foo(bar:); end
20
20
  # def foo(bar: nil); end
21
- # def foo(**opts); end
21
+ # def foo(**); end # unused passthrough
22
+ # def foo(**opts); end # only when the body reads `opts`
22
23
  class NoUnderscorePrefixedKwargs < Base
23
24
  extend AutoCorrector
24
25
 
25
26
  MSG_KWARG = 'Underscore-prefixed kwarg `%<name>s` is not allowed. ' \
26
27
  'Use plain kwarg (required), defaulted kwarg (optional), ' \
27
28
  'or `**opts` for passthrough.'
28
- MSG_SPLAT = 'Underscore-prefixed kwarg splat `%<name>s` is not allowed. ' \
29
- 'Use `**opts` for passthrough.'
29
+ MSG_SPLAT = 'Underscore-prefixed splat `%<name>s` means unused — use `**` to ' \
30
+ 'accept-and-ignore, or `**opts` only if the body reads `opts`.'
30
31
 
31
32
  def on_def(node)
32
33
  check_params(node)
@@ -70,7 +71,7 @@ module RuboCop
70
71
  return unless name.to_s.start_with?('_')
71
72
 
72
73
  add_offense(arg, message: format(MSG_SPLAT, name: name)) do |corrector|
73
- corrector.replace(arg.source_range, '**opts')
74
+ corrector.replace(arg.source_range, '**')
74
75
  end
75
76
  end
76
77
 
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Legion
6
+ module Framework
7
+ # Bans disabling `Lint/UnusedMethodArgument` with an inline directive.
8
+ # An unused kwarg is a real offense, not something to hide — a `**` /
9
+ # `**opts` splat already swallows extras, so the arg should be dropped;
10
+ # if it is genuinely optional, change `**` to `**opts` and read
11
+ # `opts[:key]`. RuboCop's own `RedundantCopDisableDirective` never flags
12
+ # this because the offense is genuine, so the directive stays invisible.
13
+ #
14
+ # Autocorrect removes the directive so the real `Lint/UnusedMethodArgument`
15
+ # offense resurfaces and must be fixed properly. It never edits the method
16
+ # signature — dropping a load-bearing public kwarg is a human decision.
17
+ #
18
+ # A blanket "disable all" directive is intentionally out of scope; only
19
+ # directives that name `Lint/UnusedMethodArgument` explicitly are flagged.
20
+ #
21
+ # @example
22
+ # # bad — a disable directive naming Lint/UnusedMethodArgument on the def
23
+ # def foo(bar:, unused:)
24
+ # end
25
+ #
26
+ # # good — drop the unused arg (a splat already swallows it)
27
+ # def foo(bar:, **)
28
+ # end
29
+ #
30
+ # # good — keep it and actually read it
31
+ # def foo(bar:, **opts)
32
+ # opts[:unused]
33
+ # end
34
+ class NoUnusedArgDisable < Base
35
+ include RangeHelp
36
+ extend AutoCorrector
37
+
38
+ MSG = 'Do not disable `Lint/UnusedMethodArgument`. Drop the unused arg ' \
39
+ '(a `**`/`**opts` splat already swallows it) or change `**` to ' \
40
+ '`**opts` and read `opts[:key]`.'
41
+
42
+ TARGET = 'Lint/UnusedMethodArgument'
43
+
44
+ # Matches a disable / todo / enable directive and captures the cop list.
45
+ DIRECTIVE = /#\s*rubocop:(?:disable|todo|enable)\s+(?<cops>[^#]+)/
46
+
47
+ def on_new_investigation
48
+ processed_source.comments.each do |comment|
49
+ match = DIRECTIVE.match(comment.text)
50
+ next unless match
51
+
52
+ cops = match[:cops].split(',').map(&:strip)
53
+ next unless cops.include?(TARGET)
54
+
55
+ register(comment, cops)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def register(comment, cops)
62
+ add_offense(comment) do |corrector|
63
+ # Only autocorrect the unambiguous single-cop case; leave multi-cop
64
+ # directives to a human so we never strip an unrelated disable.
65
+ corrector.remove(removal_range(comment)) if cops == [TARGET]
66
+ end
67
+ end
68
+
69
+ # Trailing directive (`code # rubocop:disable ...`) → strip the comment
70
+ # and the whitespace before it. Standalone directive line → strip the
71
+ # whole line including its newline.
72
+ def removal_range(comment)
73
+ range = comment.source_range
74
+ line_range = range.source_line
75
+ leading = line_range[0...range.column]
76
+
77
+ if leading.strip.empty?
78
+ range_by_whole_lines(range, include_final_newline: true)
79
+ else
80
+ range.with(begin_pos: range.begin_pos - trailing_ws(leading))
81
+ end
82
+ end
83
+
84
+ def trailing_ws(leading)
85
+ leading.length - leading.rstrip.length
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -8,6 +8,11 @@ module RuboCop
8
8
  # Flags `:tier`, `:type`, and `:circuit_state` literals that are not in
9
9
  # the known-valid sets, catching typos at lint time.
10
10
  #
11
+ # `:tier` and `:circuit_state` are domain-specific keys and are always
12
+ # validated. `:type` is only validated when it appears in a hash that
13
+ # also contains `:tier` or `:circuit_state`, since `:type` alone is too
14
+ # common in non-taxonomy contexts.
15
+ #
11
16
  # Valid values (from Inventory::DEFAULT_PROVIDER_TIERS and the SSOT design):
12
17
  # tier: :local, :fleet, :cloud, :frontier, :direct
13
18
  # type: :inference, :embedding, :moderation, :rerank, :image
@@ -17,15 +22,24 @@ module RuboCop
17
22
  # # bad (typo)
18
23
  # { tier: :fronteir }
19
24
  # health[:circuit_state] = :half_opened
25
+ # { tier: :cloud, type: :inferencing }
20
26
  #
21
27
  # # good
22
28
  # { tier: :frontier }
23
29
  # health[:circuit_state] = :half_open
30
+ # { tier: :cloud, type: :inference }
31
+ #
32
+ # # ignored (`:type` alone, no taxonomy context)
33
+ # { type: :string }
34
+ # { type: :integer }
24
35
  class TaxonomyEnum < Base
25
36
  VALID_TIERS = %i[local fleet cloud frontier direct].freeze
26
37
  VALID_TYPES = %i[inference embedding moderation rerank image].freeze
27
38
  VALID_CIRCUIT_STATES = %i[closed open half_open].freeze
28
39
 
40
+ TAXONOMY_KEYS = %i[tier type circuit_state].freeze
41
+ CONTEXT_KEYS = %i[tier circuit_state].freeze
42
+
29
43
  MSG_TIER = 'Unknown tier `%<val>s`. Valid: %<valid>s.'
30
44
  MSG_TYPE = 'Unknown type `%<val>s`. Valid: %<valid>s.'
31
45
  MSG_CIRCUIT = 'Unknown circuit_state `%<val>s`. Valid: %<valid>s.'
@@ -38,7 +52,6 @@ module RuboCop
38
52
  end
39
53
 
40
54
  def on_send(node)
41
- # health[:circuit_state] = :bad_value
42
55
  return unless node.method_name == :[]=
43
56
  return unless node.arguments.size == 2
44
57
 
@@ -54,22 +67,34 @@ module RuboCop
54
67
  val = val_node.value
55
68
  case key
56
69
  when :tier
57
- unless VALID_TIERS.include?(val)
58
- add_offense(parent_node,
59
- message: format(MSG_TIER, val: val, valid: VALID_TIERS.join(', ')))
60
- end
70
+ return if VALID_TIERS.include?(val)
71
+
72
+ add_offense(parent_node,
73
+ message: format(MSG_TIER, val: val, valid: VALID_TIERS.join(', ')))
61
74
  when :type
62
- unless VALID_TYPES.include?(val)
63
- add_offense(parent_node,
64
- message: format(MSG_TYPE, val: val, valid: VALID_TYPES.join(', ')))
65
- end
75
+ return if VALID_TYPES.include?(val)
76
+ return unless taxonomy_context?(parent_node)
77
+
78
+ add_offense(parent_node,
79
+ message: format(MSG_TYPE, val: val, valid: VALID_TYPES.join(', ')))
66
80
  when :circuit_state
67
- unless VALID_CIRCUIT_STATES.include?(val)
68
- msg = format(MSG_CIRCUIT, val: val, valid: VALID_CIRCUIT_STATES.join(', '))
69
- add_offense(parent_node, message: msg)
70
- end
81
+ return if VALID_CIRCUIT_STATES.include?(val)
82
+
83
+ add_offense(parent_node,
84
+ message: format(MSG_CIRCUIT, val: val, valid: VALID_CIRCUIT_STATES.join(', ')))
71
85
  end
72
86
  end
87
+
88
+ def taxonomy_context?(pair_node)
89
+ hash_node = pair_node.parent
90
+ return false unless hash_node&.hash_type?
91
+
92
+ sibling_keys = hash_node.pairs.filter_map do |pair|
93
+ pair.key.value if pair.key.sym_type?
94
+ end
95
+
96
+ sibling_keys.intersect?(CONTEXT_KEYS)
97
+ end
73
98
  end
74
99
  end
75
100
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Legion
5
- VERSION = '0.1.9'
5
+ VERSION = '0.1.11'
6
6
  end
7
7
  end
@@ -45,6 +45,7 @@ require 'rubocop/cop/legion/framework/cache_time_coercion'
45
45
  require 'rubocop/cop/legion/framework/api_string_keys'
46
46
  require 'rubocop/cop/legion/framework/mutex_nested_sync'
47
47
  require 'rubocop/cop/legion/framework/no_underscore_prefixed_kwargs'
48
+ require 'rubocop/cop/legion/framework/no_unused_arg_disable'
48
49
  require 'rubocop/cop/legion/framework/no_inline_setting_defaults'
49
50
  require 'rubocop/cop/legion/framework/no_direct_dispatch'
50
51
  require 'rubocop/cop/legion/framework/no_shape_duck_typing'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-legion
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -148,6 +148,7 @@ files:
148
148
  - lib/rubocop/cop/legion/framework/no_inline_setting_defaults.rb
149
149
  - lib/rubocop/cop/legion/framework/no_shape_duck_typing.rb
150
150
  - lib/rubocop/cop/legion/framework/no_underscore_prefixed_kwargs.rb
151
+ - lib/rubocop/cop/legion/framework/no_unused_arg_disable.rb
151
152
  - lib/rubocop/cop/legion/framework/sinatra_host_auth.rb
152
153
  - lib/rubocop/cop/legion/framework/thor_reserved_run.rb
153
154
  - lib/rubocop/cop/legion/helper_migration/defined_transport_guard.rb