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 +4 -4
- data/CHANGELOG.md +15 -0
- data/config/default.yml +42 -1
- data/lib/rubocop/cop/legion/framework/no_underscore_prefixed_kwargs.rb +5 -4
- data/lib/rubocop/cop/legion/framework/no_unused_arg_disable.rb +91 -0
- data/lib/rubocop/cop/legion/llm/taxonomy_enum.rb +38 -13
- data/lib/rubocop/legion/version.rb +1 -1
- data/lib/rubocop-legion.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab19d4bce0d274768b632aa0579aa8997cb503befa24d4d613442f4cc894cb09
|
|
4
|
+
data.tar.gz: 85caebdfc215b98b74febd7b59c09c90fc6574e36f9dd2c126c866ca43138cde
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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(**
|
|
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
|
|
29
|
-
'
|
|
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, '**
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
data/lib/rubocop-legion.rb
CHANGED
|
@@ -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.
|
|
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
|