robot_lab-rails 0.1.0 → 0.2.1

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: 93f8a8d257c9dfe599fde397b4be1cc979df53eed8d38a3adca987e493852311
4
- data.tar.gz: a41afb0b4132c1c245a10f44d3b5d9dd40ced45ad264e0a7e08c342cf9fb5711
3
+ metadata.gz: aea941ab401ce28705914a98694a577399c78cff4cbebb6513b6a3fba02a4525
4
+ data.tar.gz: aeb31e16dec1408a62bd4fb3424e2b09b7096f845084e6a7ed664ccc253cac29
5
5
  SHA512:
6
- metadata.gz: d84d9c25a0379bca316619e0268c9f727228572c09048cb2717591acf145f1459c4292617f3ba836d756a9bd0f79b4922fdec43812cf3d111ab57d8dc591d945
7
- data.tar.gz: e908c4bee35578e1ecd313b126ea87b7cc3719eba286cbe01ed11000fdd65dc09b41ba49fc069da64137d783d8c19076ae5dac4dd5418b8aac7b8b89c32b31ad
6
+ metadata.gz: e5e0201b324dc571199341a6616a22ac18fe477b0c70ac2c86a25614360f8e87074c6142646baba92e9562442c03a42155f9368a5d9bea060e508182b9c4fb5f
7
+ data.tar.gz: 57b6d986ba10d5c08eee1ea4f0ef97dccfde83b5bccf558cbba62dde9389a899fc532f9890a0c0a65c5fc4d1e2adb5320383e44f58f11eccc699ebc2c757455e
data/.rubocop.yml ADDED
@@ -0,0 +1,173 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ SuggestExtensions: false
4
+ TargetRubyVersion: 4.0
5
+ Exclude:
6
+ - 'examples/**/*'
7
+ - 'vendor/**/*'
8
+ - 'dead_code/**/*'
9
+
10
+ # ── Style: disabled cops ───────────────────────────────────────────────────
11
+ Style/StringLiterals:
12
+ Enabled: false
13
+
14
+ Style/StringLiteralsInInterpolation:
15
+ Enabled: false
16
+
17
+ Style/Documentation:
18
+ Enabled: false
19
+
20
+ # Ruby 4.0 freezes string literals by default
21
+ Style/FrozenStringLiteralComment:
22
+ Enabled: false
23
+
24
+ Style/IfUnlessModifier:
25
+ Enabled: false
26
+
27
+ Style/RescueModifier:
28
+ Enabled: false
29
+
30
+ Style/TrivialAccessors:
31
+ Enabled: false
32
+
33
+ Style/MultilineTernaryOperator:
34
+ Enabled: false
35
+
36
+ Style/SafeNavigation:
37
+ Enabled: false
38
+
39
+ Style/EmptyClassDefinition:
40
+ Enabled: false
41
+
42
+ Style/ClassAndModuleChildren:
43
+ Enabled: false
44
+
45
+ Style/RescueStandardError:
46
+ Enabled: false
47
+
48
+ Style/OneClassPerFile:
49
+ Enabled: false
50
+
51
+ # Both % and format/sprintf are acceptable
52
+ Style/FormatString:
53
+ Enabled: false
54
+
55
+ # String concatenation and interpolation are both acceptable
56
+ Style/StringConcatenation:
57
+ Enabled: false
58
+
59
+ # ── Layout ─────────────────────────────────────────────────────────────────
60
+ Layout/LineLength:
61
+ Max: 140
62
+
63
+ Layout/ExtraSpacing:
64
+ Enabled: false
65
+
66
+ Layout/HashAlignment:
67
+ Enabled: false
68
+
69
+ Layout/FirstHashElementIndentation:
70
+ Enabled: false
71
+
72
+ Layout/EmptyLineAfterGuardClause:
73
+ Enabled: false
74
+
75
+ # ── Naming ─────────────────────────────────────────────────────────────────
76
+ # Single-char params (c, e, n) are acceptable throughout
77
+ Naming/MethodParameterName:
78
+ Enabled: false
79
+
80
+ Naming/VariableNumber:
81
+ Exclude:
82
+ - 'test/**/*'
83
+
84
+ Naming/RescuedExceptionsVariableName:
85
+ Enabled: false
86
+
87
+ # set_results and similar explicit setters are clear and conventional
88
+ Naming/AccessorMethodName:
89
+ Enabled: false
90
+
91
+
92
+ # has_tool_calls? and similar are clear and conventional
93
+ Naming/PredicatePrefix:
94
+ Enabled: false
95
+
96
+ # Test helper methods don't need to follow predicate naming rules
97
+ Naming/PredicateMethod:
98
+ Exclude:
99
+ - 'test/**/*'
100
+
101
+ # ── Lint: relax noisy cops on intentional patterns ─────────────────────────
102
+ # Library and framework methods commonly accept args for API/documentation purposes
103
+ Lint/UnusedMethodArgument:
104
+ Enabled: false
105
+
106
+
107
+ Lint/EmptyBlock:
108
+ Exclude:
109
+ - 'test/**/*'
110
+
111
+ Lint/ConstantDefinitionInBlock:
112
+ Exclude:
113
+ - 'Rakefile'
114
+ - 'test/**/*'
115
+
116
+ # ── Gemspec ────────────────────────────────────────────────────────────────
117
+ Gemspec/DevelopmentDependencies:
118
+ EnforcedStyle: Gemfile
119
+
120
+ Gemspec/RequiredRubyVersion:
121
+ Enabled: false
122
+
123
+ Gemspec/OrderedDependencies:
124
+ Enabled: false
125
+
126
+ # ── Metrics ────────────────────────────────────────────────────────────────
127
+ # Framework-level code (routers, parsers, orchestrators) is inherently complex.
128
+ # Flog is the primary complexity gate — these RuboCop thresholds catch only
129
+ # egregious outliers without false-positiving every dispatch method.
130
+
131
+ Metrics/MethodLength:
132
+ Max: 35
133
+ CountAsOne:
134
+ - heredoc
135
+ - array
136
+ - hash
137
+ Exclude:
138
+ - 'test/**/*'
139
+
140
+ Metrics/AbcSize:
141
+ Max: 40
142
+ Exclude:
143
+ - 'test/**/*'
144
+
145
+ Metrics/ClassLength:
146
+ Max: 600
147
+ Exclude:
148
+ - 'test/**/*'
149
+
150
+ Metrics/ModuleLength:
151
+ Max: 200
152
+ Exclude:
153
+ - 'test/**/*'
154
+
155
+ Metrics/CyclomaticComplexity:
156
+ Max: 20
157
+ Exclude:
158
+ - 'test/**/*'
159
+
160
+ Metrics/PerceivedComplexity:
161
+ Max: 20
162
+ Exclude:
163
+ - 'test/**/*'
164
+
165
+ # Long method signatures with keyword args are a Ruby framework idiom
166
+ Metrics/ParameterLists:
167
+ Enabled: false
168
+
169
+ Metrics/BlockLength:
170
+ Exclude:
171
+ - 'Rakefile'
172
+ - '*.gemspec'
173
+ - 'test/**/*'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1] - 2026-05-19
4
+
5
+ ### Added
6
+ - Rails Engine — autoloads `app/robots/` and `app/tools/` in the host application via Zeitwerk
7
+ - Railtie — wires `RobotLab.config.logger` to `Rails.logger` on boot and applies Rails-specific defaults
8
+ - `RobotLab::Job` (`RobotLab::RailsIntegration::Job`) — ActiveJob base class handling robot-class resolution, `RobotLabThread` persistence, Turbo Stream wiring, and completion/error broadcasting
9
+ - `robot_class` DSL on `RobotLab::Job` subclasses — binds a dedicated job to a specific robot class without requiring `robot_class:` at enqueue time
10
+ - `TurboStreamCallbacks` — streams content tokens and tool-call badges to `#robot_response` and `#robot_tools` targets in real time; graceful no-op when `turbo-rails` is absent
11
+ - Generator `robot_lab:install` — creates initializer, migration, models (`RobotLabThread`, `RobotLabResult`), `RobotRunJob`, and `app/robots/` / `app/tools/` directories
12
+ - Generator `robot_lab:robot NAME` — generates a robot class stub in `app/robots/`
13
+ - Generator `robot_lab:job NAME [--queue QUEUE]` — generates a dedicated job subclass pre-bound to a robot class
14
+ - Default retry/discard policy on `RobotLab::Job`: `retry_on StandardError` (3 attempts, 5 s wait); `discard_on ActiveJob::DeserializationError`
15
+
16
+ ### Changed
17
+ - Version synchronized with robot_lab core 0.2.1
18
+
3
19
  ## [0.1.0] - 2026-05-07
4
20
 
5
21
  - Initial release
data/Rakefile CHANGED
@@ -1,8 +1,116 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "minitest/test_task"
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
5
 
6
- Minitest::TestTask.create
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['test/**/*_test.rb', 'test/**/test_*.rb'].exclude('**/*_helper.rb')
10
+ t.verbose = true
11
+ t.ruby_opts << '-rtest_helper'
12
+ end
7
13
 
8
14
  task default: :test
15
+
16
+ desc 'Run tests with verbose output'
17
+ task :test_verbose do
18
+ ENV['TESTOPTS'] = '--verbose'
19
+ Rake::Task[:test].invoke
20
+ end
21
+
22
+ desc 'Run a single test file'
23
+ task :test_file, [:file] do |_t, args|
24
+ ruby "test/#{args[:file]}"
25
+ end
26
+
27
+ desc 'Check code style with RuboCop'
28
+ task :rubocop do
29
+ sh 'bundle exec rubocop'
30
+ end
31
+
32
+ desc 'Auto-correct RuboCop offenses'
33
+ task :rubocop_fix do
34
+ sh 'bundle exec rubocop -a'
35
+ end
36
+
37
+ desc 'Check code complexity with Flog (warn >=20, fail >=50)'
38
+ task :flog_check do
39
+ require 'flog'
40
+
41
+ method_warn = 20.0
42
+ method_fail = 50.0
43
+
44
+ flogger = Flog.new(all: true)
45
+ flogger.flog(*Dir.glob('lib/**/*.rb'))
46
+
47
+ warnings = []
48
+ failures = []
49
+
50
+ flogger.each_by_score do |method, score|
51
+ next if method.end_with?('#none')
52
+
53
+ if score > method_fail
54
+ failures << "#{format('%.1f', score)}: #{method}"
55
+ elsif score > method_warn
56
+ warnings << "#{format('%.1f', score)}: #{method}"
57
+ end
58
+ end
59
+
60
+ unless warnings.empty?
61
+ puts "\nFlog warnings (#{method_warn}–#{method_fail}) — target for future refactoring:"
62
+ warnings.each { |v| puts " #{v}" }
63
+ end
64
+
65
+ if failures.empty?
66
+ puts "\nFlog: no methods exceed the failure threshold (>=#{method_fail})"
67
+ else
68
+ puts "\nFlog failures (>=#{method_fail}) — must be refactored:"
69
+ failures.each { |v| puts " #{v}" }
70
+ abort "\nFlog quality gate failed: #{failures.size} method(s) exceed #{method_fail}"
71
+ end
72
+ end
73
+
74
+ desc 'Run all quality checks: tests (with coverage), RuboCop, and Flog'
75
+ task :quality do
76
+ results = {}
77
+
78
+ puts "\n#{'=' * 60}"
79
+ puts 'Quality Gate: Tests + Coverage'
80
+ puts '=' * 60
81
+ results[:tests] = system('bundle exec rake test') ? :pass : :fail
82
+
83
+ puts "\n#{'=' * 60}"
84
+ puts 'Quality Gate: RuboCop'
85
+ puts '=' * 60
86
+ results[:rubocop] = system('bundle exec rubocop') ? :pass : :fail
87
+
88
+ puts "\n#{'=' * 60}"
89
+ puts 'Quality Gate: Flog Complexity'
90
+ puts '=' * 60
91
+ results[:flog] = system('bundle exec rake flog_check') ? :pass : :fail
92
+
93
+ puts "\n#{'=' * 60}"
94
+ puts 'Quality Summary'
95
+ puts '=' * 60
96
+ results.each do |gate, status|
97
+ icon = status == :pass ? 'PASS' : 'FAIL'
98
+ puts " [#{icon}] #{gate}"
99
+ end
100
+ puts '=' * 60
101
+
102
+ abort "\nQuality gate failed" if results.values.any?(:fail)
103
+ puts "\nAll quality gates passed."
104
+ end
105
+
106
+ namespace :docs do
107
+ desc 'Build MkDocs documentation'
108
+ task :build do
109
+ sh 'mkdocs build'
110
+ end
111
+
112
+ desc 'Serve MkDocs documentation locally on http://localhost:8000'
113
+ task :serve do
114
+ sh 'mkdocs serve'
115
+ end
116
+ end
@@ -35,7 +35,7 @@ rails db:migrate
35
35
 
36
36
  ## Configuration
37
37
 
38
- RobotLab uses [MywayConfig](https://github.com/madbomber/myway_config) for configuration. There is no `RobotLab.configure` block. Instead, settings are loaded from YAML files and environment variables in the following priority order:
38
+ RobotLab uses [MywayConfig](https://github.com/madbomber/myway_config) for static configuration. Settings are loaded from YAML files and environment variables in the following priority order:
39
39
 
40
40
  1. **Bundled defaults** (`lib/robot_lab/config/defaults.yml`)
41
41
  2. **Environment-specific overrides** (development, test, production sections)
@@ -85,14 +85,21 @@ ROBOT_LAB_RUBY_LLM__REQUEST_TIMEOUT=180
85
85
 
86
86
  RobotLab also falls back to standard provider environment variables (e.g. `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`) when the prefixed versions are not set.
87
87
 
88
- ### Initializer (Logger Only)
88
+ ### Initializer
89
89
 
90
- The only runtime-writable config attribute is the logger. The generated initializer sets it to the Rails logger:
90
+ Use `RobotLab.configure` to set runtime attributes. The generated initializer wires the logger to `Rails.logger`:
91
91
 
92
92
  ```ruby title="config/initializers/robot_lab.rb"
93
93
  # frozen_string_literal: true
94
94
 
95
- # Set the RobotLab logger to use Rails.logger
95
+ RobotLab.configure do |c|
96
+ c.logger = Rails.logger
97
+ end
98
+ ```
99
+
100
+ You can also assign it directly if you prefer a one-liner:
101
+
102
+ ```ruby
96
103
  RobotLab.config.logger = Rails.logger
97
104
  ```
98
105
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RobotLab
4
4
  module Rails
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.1"
6
6
  end
7
7
  end
@@ -3,8 +3,12 @@
3
3
  require_relative "rails/version"
4
4
  require_relative "rails_integration/turbo_stream_callbacks"
5
5
 
6
- if defined?(::Rails)
6
+ if defined?(Rails)
7
7
  require_relative "rails_integration/engine"
8
8
  require_relative "rails_integration/railtie"
9
9
  require_relative "rails_integration/job"
10
10
  end
11
+
12
+ if defined?(RobotLab) && RobotLab.respond_to?(:register_extension)
13
+ RobotLab.register_extension(:rails, RobotLab::Rails)
14
+ end
@@ -39,9 +39,10 @@ module RobotLab
39
39
 
40
40
  def resolve_robot_class(runtime_class)
41
41
  klass = runtime_class || self.class.robot_class
42
- raise ArgumentError,
43
- "No robot class specified. Pass robot_class: to perform or set robot_class on the job class." \
44
- unless klass
42
+ unless klass
43
+ raise ArgumentError,
44
+ "No robot class specified. Pass robot_class: to perform or set robot_class on the job class."
45
+ end
45
46
 
46
47
  return klass if klass.is_a?(Class)
47
48
 
@@ -13,7 +13,7 @@ module RobotLab
13
13
  end
14
14
 
15
15
  def self.build_content_callback(stream_name:, target: "robot_response")
16
- ->(chunk) {
16
+ lambda { |chunk|
17
17
  content = chunk.respond_to?(:content) ? chunk.content : chunk.to_s
18
18
  return unless content && TurboStreamCallbacks.available?
19
19
 
@@ -26,7 +26,7 @@ module RobotLab
26
26
  end
27
27
 
28
28
  def self.build_tool_call_callback(stream_name:, target: "robot_tools")
29
- ->(tool_call) {
29
+ lambda { |tool_call|
30
30
  return unless TurboStreamCallbacks.available?
31
31
 
32
32
  name = tool_call.respond_to?(:name) ? tool_call.name : tool_call.to_s
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: robot_lab-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
@@ -13,16 +13,16 @@ dependencies:
13
13
  name: robot_lab
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - ">="
16
+ - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0'
18
+ version: 0.2.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
- - - ">="
23
+ - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '0'
25
+ version: 0.2.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: railties
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -75,6 +75,7 @@ extra_rdoc_files: []
75
75
  files:
76
76
  - ".envrc"
77
77
  - ".github/workflows/deploy-github-pages.yml"
78
+ - ".rubocop.yml"
78
79
  - CHANGELOG.md
79
80
  - LICENSE.txt
80
81
  - README.md