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 +4 -4
- data/.rubocop.yml +173 -0
- data/CHANGELOG.md +16 -0
- data/Rakefile +111 -3
- data/docs/guides/rails-integration.md +11 -4
- data/lib/robot_lab/rails/version.rb +1 -1
- data/lib/robot_lab/rails.rb +5 -1
- data/lib/robot_lab/rails_integration/job.rb +4 -3
- data/lib/robot_lab/rails_integration/turbo_stream_callbacks.rb +2 -2
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aea941ab401ce28705914a98694a577399c78cff4cbebb6513b6a3fba02a4525
|
|
4
|
+
data.tar.gz: aeb31e16dec1408a62bd4fb3424e2b09b7096f845084e6a7ed664ccc253cac29
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
4
|
-
require
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rake/testtask'
|
|
5
5
|
|
|
6
|
-
|
|
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.
|
|
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
|
|
88
|
+
### Initializer
|
|
89
89
|
|
|
90
|
-
|
|
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
|
-
|
|
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
|
|
data/lib/robot_lab/rails.rb
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
require_relative "rails/version"
|
|
4
4
|
require_relative "rails_integration/turbo_stream_callbacks"
|
|
5
5
|
|
|
6
|
-
if defined?(
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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
|