rubocop-legion 0.1.8 → 0.1.9
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/lib/rubocop/cop/legion/llm/no_loop_do.rb +41 -0
- data/lib/rubocop/cop/legion/llm/rescue_log_level.rb +61 -0
- data/lib/rubocop/cop/legion/llm/settings_access_path.rb +62 -0
- data/lib/rubocop/cop/legion/llm/taxonomy_enum.rb +77 -0
- data/lib/rubocop/legion/version.rb +1 -1
- data/lib/rubocop-legion.rb +6 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 88034e8168ad8e5634a580f3a07ca2f030bd46be31b6c9607dbc64a28935bcd0
|
|
4
|
+
data.tar.gz: 353647b902a3271fe17809769674778b528eb3e97dda8704934addf80b69b397
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0b7d05703e9bd6762628d25f082a738fba9972f66eda2b650ab5e3c0dace6b3569df16c754f935f8f62caf861eaf3984018c3a4f3f73de01cc0871fcf91cfebd
|
|
7
|
+
data.tar.gz: d598e5090c4ba1242ec371fd2d3b935412f9fe8c4e24fe907a9e18bd6052999568bed0c252a983559e4b33e1c0502a724e02df5244db46b98c5cdad899f62e93
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Legion
|
|
6
|
+
module Llm
|
|
7
|
+
# Flags `loop do` in legion-llm production code. Unbounded loops have caused
|
|
8
|
+
# runaway threads and hard-to-debug hangs. Use `N.times`, `each`, or a
|
|
9
|
+
# `while` with an explicit counter instead.
|
|
10
|
+
#
|
|
11
|
+
# No auto-correct is provided — the fix requires choosing the correct bound.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# # bad
|
|
15
|
+
# loop do
|
|
16
|
+
# attempt = dispatch_request
|
|
17
|
+
# break if attempt.success?
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# # good
|
|
21
|
+
# MAX_ATTEMPTS.times do
|
|
22
|
+
# attempt = dispatch_request
|
|
23
|
+
# break if attempt.success?
|
|
24
|
+
# end
|
|
25
|
+
class NoLoopDo < Base
|
|
26
|
+
MSG = '`loop do` is prohibited — use bounded iteration (`N.times`, `each`, ' \
|
|
27
|
+
'or `while` with an explicit decrement) instead.'
|
|
28
|
+
|
|
29
|
+
def on_block(node)
|
|
30
|
+
return unless node.send_node.method_name == :loop
|
|
31
|
+
return unless node.send_node.receiver.nil?
|
|
32
|
+
|
|
33
|
+
add_offense(node.send_node)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
alias on_numblock on_block
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Legion
|
|
6
|
+
module Llm
|
|
7
|
+
# Flags `handle_exception(e, level: :debug)` in rescue blocks. Exception
|
|
8
|
+
# handlers must use at least `:warn` so that errors are visible in
|
|
9
|
+
# production logs without enabling debug mode.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# # bad
|
|
13
|
+
# rescue StandardError => e
|
|
14
|
+
# handle_exception(e, level: :debug, operation: 'foo')
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# # good
|
|
18
|
+
# rescue StandardError => e
|
|
19
|
+
# handle_exception(e, level: :warn, operation: 'foo')
|
|
20
|
+
# end
|
|
21
|
+
class RescueLogLevel < Base
|
|
22
|
+
MSG = '`handle_exception` called with `level: :debug` in a rescue. ' \
|
|
23
|
+
'Minimum log level for exceptions is `:warn`.'
|
|
24
|
+
|
|
25
|
+
DEBUG_LEVELS = %i[debug trace verbose].freeze
|
|
26
|
+
|
|
27
|
+
def on_send(node)
|
|
28
|
+
return unless node.method_name == :handle_exception
|
|
29
|
+
return unless inside_rescue?(node)
|
|
30
|
+
return unless debug_level?(node)
|
|
31
|
+
|
|
32
|
+
add_offense(node)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def debug_level?(node)
|
|
38
|
+
pairs = node.arguments.flat_map do |arg|
|
|
39
|
+
if arg.hash_type?
|
|
40
|
+
arg.children
|
|
41
|
+
elsif arg.pair_type?
|
|
42
|
+
[arg]
|
|
43
|
+
else
|
|
44
|
+
[]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
pairs.any? do |pair|
|
|
48
|
+
key, val = pair.children
|
|
49
|
+
key.sym_type? && key.value == :level &&
|
|
50
|
+
val.sym_type? && DEBUG_LEVELS.include?(val.value)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def inside_rescue?(node)
|
|
55
|
+
node.each_ancestor(:resbody).any?
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Legion
|
|
6
|
+
module Llm
|
|
7
|
+
# Flags direct write-path access to `Legion::Settings.loader.settings[...][...]`
|
|
8
|
+
# outside test files. Production code must use `Legion::Settings.dig(...)` so all
|
|
9
|
+
# reads go through the typed, validated accessor.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# # bad
|
|
13
|
+
# Legion::Settings.loader.settings[:extensions][:llm][:default_model] = 'gemma-12b'
|
|
14
|
+
# Legion::Settings.loader.settings[:llm][:timeout]
|
|
15
|
+
#
|
|
16
|
+
# # good
|
|
17
|
+
# Legion::Settings.dig(:extensions, :llm, :default_model)
|
|
18
|
+
class SettingsAccessPath < Base
|
|
19
|
+
MSG = 'Use `Legion::Settings.dig(...)` instead of ' \
|
|
20
|
+
'`Legion::Settings.loader.settings[...][...]`.'
|
|
21
|
+
|
|
22
|
+
def on_send(node)
|
|
23
|
+
return unless loader_settings_chain?(node)
|
|
24
|
+
|
|
25
|
+
add_offense(node)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def loader_settings_chain?(node)
|
|
31
|
+
return false unless %i[[] []=].include?(node.method_name)
|
|
32
|
+
|
|
33
|
+
# For []=, the second index arg and the value are children[1] and [2]
|
|
34
|
+
# The receiver of the outer []= is the inner [] node
|
|
35
|
+
receiver = node.receiver
|
|
36
|
+
return false unless receiver&.send_type? && receiver.method_name == :[]
|
|
37
|
+
|
|
38
|
+
# receiver.receiver should be Legion::Settings.loader.settings
|
|
39
|
+
inner = receiver.receiver
|
|
40
|
+
return false unless inner&.send_type? && inner.method_name == :settings
|
|
41
|
+
|
|
42
|
+
loader_recv = inner.receiver
|
|
43
|
+
return false unless loader_recv&.send_type? && loader_recv.method_name == :loader
|
|
44
|
+
|
|
45
|
+
settings_const?(loader_recv.receiver)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def settings_const?(node)
|
|
49
|
+
return false unless node&.const_type?
|
|
50
|
+
return false unless node.children.last == :Settings
|
|
51
|
+
|
|
52
|
+
parent = node.children.first
|
|
53
|
+
return true if parent.nil?
|
|
54
|
+
return false unless parent.const_type? && parent.children.last == :Legion
|
|
55
|
+
|
|
56
|
+
parent.children.first.nil?
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Legion
|
|
6
|
+
module Llm
|
|
7
|
+
# Validates lane field literals against the LegionIO taxonomy enums.
|
|
8
|
+
# Flags `:tier`, `:type`, and `:circuit_state` literals that are not in
|
|
9
|
+
# the known-valid sets, catching typos at lint time.
|
|
10
|
+
#
|
|
11
|
+
# Valid values (from Inventory::DEFAULT_PROVIDER_TIERS and the SSOT design):
|
|
12
|
+
# tier: :local, :fleet, :cloud, :frontier, :direct
|
|
13
|
+
# type: :inference, :embedding, :moderation, :rerank, :image
|
|
14
|
+
# circuit_state: :closed, :open, :half_open
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# # bad (typo)
|
|
18
|
+
# { tier: :fronteir }
|
|
19
|
+
# health[:circuit_state] = :half_opened
|
|
20
|
+
#
|
|
21
|
+
# # good
|
|
22
|
+
# { tier: :frontier }
|
|
23
|
+
# health[:circuit_state] = :half_open
|
|
24
|
+
class TaxonomyEnum < Base
|
|
25
|
+
VALID_TIERS = %i[local fleet cloud frontier direct].freeze
|
|
26
|
+
VALID_TYPES = %i[inference embedding moderation rerank image].freeze
|
|
27
|
+
VALID_CIRCUIT_STATES = %i[closed open half_open].freeze
|
|
28
|
+
|
|
29
|
+
MSG_TIER = 'Unknown tier `%<val>s`. Valid: %<valid>s.'
|
|
30
|
+
MSG_TYPE = 'Unknown type `%<val>s`. Valid: %<valid>s.'
|
|
31
|
+
MSG_CIRCUIT = 'Unknown circuit_state `%<val>s`. Valid: %<valid>s.'
|
|
32
|
+
|
|
33
|
+
def on_pair(node)
|
|
34
|
+
key, value = node.children
|
|
35
|
+
return unless key.sym_type? && value.sym_type?
|
|
36
|
+
|
|
37
|
+
check_taxonomy(key.value, value, node)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def on_send(node)
|
|
41
|
+
# health[:circuit_state] = :bad_value
|
|
42
|
+
return unless node.method_name == :[]=
|
|
43
|
+
return unless node.arguments.size == 2
|
|
44
|
+
|
|
45
|
+
key_node, val_node = node.arguments
|
|
46
|
+
return unless key_node.sym_type? && val_node.sym_type?
|
|
47
|
+
|
|
48
|
+
check_taxonomy(key_node.value, val_node, node)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def check_taxonomy(key, val_node, parent_node)
|
|
54
|
+
val = val_node.value
|
|
55
|
+
case key
|
|
56
|
+
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
|
|
61
|
+
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
|
|
66
|
+
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
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/rubocop-legion.rb
CHANGED
|
@@ -68,3 +68,9 @@ require 'rubocop/cop/legion/extension/absorber_missing_pattern'
|
|
|
68
68
|
require 'rubocop/cop/legion/extension/absorber_missing_absorb_method'
|
|
69
69
|
require 'rubocop/cop/legion/extension/definition_call_mismatched'
|
|
70
70
|
require 'rubocop/cop/legion/extension/actor_enabled_side_effects'
|
|
71
|
+
|
|
72
|
+
# Legion/Llm
|
|
73
|
+
require 'rubocop/cop/legion/llm/settings_access_path'
|
|
74
|
+
require 'rubocop/cop/legion/llm/no_loop_do'
|
|
75
|
+
require 'rubocop/cop/legion/llm/rescue_log_level'
|
|
76
|
+
require 'rubocop/cop/legion/llm/taxonomy_enum'
|
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.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -163,6 +163,10 @@ files:
|
|
|
163
163
|
- lib/rubocop/cop/legion/helper_migration/logging_guard.rb
|
|
164
164
|
- lib/rubocop/cop/legion/helper_migration/old_logging_methods.rb
|
|
165
165
|
- lib/rubocop/cop/legion/helper_migration/require_defined_guard.rb
|
|
166
|
+
- lib/rubocop/cop/legion/llm/no_loop_do.rb
|
|
167
|
+
- lib/rubocop/cop/legion/llm/rescue_log_level.rb
|
|
168
|
+
- lib/rubocop/cop/legion/llm/settings_access_path.rb
|
|
169
|
+
- lib/rubocop/cop/legion/llm/taxonomy_enum.rb
|
|
166
170
|
- lib/rubocop/cop/legion/rescue_logging/bare_rescue.rb
|
|
167
171
|
- lib/rubocop/cop/legion/rescue_logging/no_capture.rb
|
|
168
172
|
- lib/rubocop/cop/legion/rescue_logging/silent_capture.rb
|