ask-rails-harness 0.3.0 → 0.4.0

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: 70e5e797b91144992c18866c357d73811226b3281955a208437eed7ae02b0b63
4
- data.tar.gz: c110d660dbd193597f1bfcbae2ea5b3184f676984d4e9dc1ce9401df899d36e4
3
+ metadata.gz: 3138db7c116e0f0742a091cad7c5ecfdbe8263de2b27c892830e56d32e259691
4
+ data.tar.gz: b0a87afaed52e3d66313fea3c1d1536d19934876e1e52dd6503b4e3ce112aaed
5
5
  SHA512:
6
- metadata.gz: ce9d990603fed30ed9bdf9ee20cd3087694dc7739f3751d03343f9f4d0b6a957bd9b8ba7b7146a58a45c002ef8931d26766548ed3e50aed2233acbfe16595746
7
- data.tar.gz: 353fbc3c92ba503b3d6bb9732d9030c4d967279584814810768c32556b98c6584eaa5465b33eef3e0e00425f163e5a6466136aa040d8bb0342847a068c6a4959
6
+ metadata.gz: 1e3e71b34c262d91350dd2ccd213bdbca3915d75302307dbc3e94b89b1e803b98d0568ef7ea5b94b6a197b0859b53276f337a7d4885d8cf496cddf9f5ea65904
7
+ data.tar.gz: a6db27db6dfc1b050c45aa5bea45d6c1862937e45998f265ccb5226a5a09df94b4f73d0be486468a421854c55b7483ff655ed554d80e880979b90f9996d122cf
data/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## [0.4.0] — 2026-08-10
2
+
3
+ ### Changed
4
+
5
+ - **Generic tools moved to the new `ask-ruby-harness` gem** — `RunCommand`,
6
+ `QueryDatabase`, `ReadModel`, `ReadLog`, `SchemaGraph`, and `RunTests` now
7
+ live in the language-agnostic harness (`Ask::Ruby::Harness`), which this
8
+ gem depends on. This gem keeps the Rails-native surface: the Engine
9
+ (mount at /ask), railtie, generators, auth, persistence, and
10
+ `RouteInspector`.
11
+ - **Backward-compatible aliases** — `Ask::Rails::Harness::Tools::*`,
12
+ `Ask::Rails::Harness::AuditLog`, `Ask::Rails::Harness::Configuration`, and
13
+ `Ask::Rails::Harness::MinitestJsonReporter` still resolve (to the generic
14
+ implementations), and `Ask::Rails::Harness::Tool` subclasses the generic
15
+ base. `app_root` is pinned to `::Rails.root` by the railtie.
16
+ - **Audit notification renamed** — the generic audit log now fires
17
+ `audit_log.ask_ruby_harness` (was `audit_log.ask_rails_harness`).
18
+
19
+ ### Removed
20
+
21
+ - `lib/minitest/ask_rails_harness_plugin.rb` — superseded by
22
+ `minitest/ask_ruby_harness_plugin.rb` in ask-ruby-harness.
23
+
1
24
  ## [0.3.0] — 2026-08-10
2
25
 
3
26
  ### Removed
@@ -17,6 +17,7 @@ module Ask
17
17
  end
18
18
 
19
19
  initializer "ask_rails_harness.configure" do |app|
20
+ Ask::Ruby::Harness.app_root = ::Rails.root
20
21
  Ask::Rails::Harness.configuration.default_model ||= ENV["ASK_DEFAULT_MODEL"] || "gpt-4o"
21
22
  Ask::Rails::Harness.configuration.max_turns ||= (ENV["ASK_MAX_TURNS"] || 25).to_i
22
23
  end
@@ -3,39 +3,10 @@
3
3
  module Ask
4
4
  module Rails
5
5
  module Harness
6
- class Tool < Ask::Tool
7
- def rails_root
8
- ::Rails.root
9
- end
10
-
11
- # Override call to add audit logging around every tool execution.
12
- # Logs the intent (sanitized params) and outcome (status, timing),
13
- # but not the returned data.
14
- def call(args = {}, abort_controller = nil)
15
- start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
16
- result = super
17
- duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round
18
-
19
- AuditLog.log(
20
- session_id: Thread.current[:ask_session_id],
21
- tool_name: name,
22
- params: args,
23
- result: result,
24
- duration_ms: duration_ms
25
- )
26
-
27
- result
28
- end
29
-
30
- # Allow the session to set its ID for audit log correlation.
31
- # Called by the agent loop before executing a tool.
32
- def self.session_id=(id)
33
- Thread.current[:ask_session_id] = id
34
- end
35
-
36
- def self.session_id
37
- Thread.current[:ask_session_id]
38
- end
6
+ # The Rails edition's tool base. app_root is pinned to ::Rails.root by
7
+ # the railtie; everything else (audit logging, session correlation)
8
+ # comes from the generic ask-ruby-harness base.
9
+ class Tool < Ask::Ruby::Harness::Tool
39
10
  end
40
11
  end
41
12
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Backward-compatible constant aliases: the generic tools moved to
4
+ # ask-ruby-harness (Ask::Ruby::Harness::Tools::*). Existing references to
5
+ # Ask::Rails::Harness::Tools::*, AuditLog, and Configuration keep working.
6
+ # (MinitestJsonReporter is intentionally not aliased — it lives behind the
7
+ # minitest plugin and must not load minitest at app boot.)
8
+ module Ask
9
+ module Rails
10
+ module Harness
11
+ Configuration = Ask::Ruby::Harness::Configuration
12
+ AuditLog = Ask::Ruby::Harness::AuditLog
13
+
14
+ module Tools
15
+ RunCommand = Ask::Ruby::Harness::Tools::RunCommand
16
+ QueryDatabase = Ask::Ruby::Harness::Tools::QueryDatabase
17
+ ReadModel = Ask::Ruby::Harness::Tools::ReadModel
18
+ ReadLog = Ask::Ruby::Harness::Tools::ReadLog
19
+ SchemaGraph = Ask::Ruby::Harness::Tools::SchemaGraph
20
+ RunTests = Ask::Ruby::Harness::Tools::RunTests
21
+ end
22
+ end
23
+ end
24
+ end
@@ -3,7 +3,7 @@
3
3
  module Ask
4
4
  module Rails
5
5
  module Harness
6
- VERSION = "0.3.0"
6
+ VERSION = "0.4.0"
7
7
  end
8
8
  end
9
9
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails"
4
- require "ask/agent"
4
+ require "ask/ruby/harness"
5
5
  require "ask/auth"
6
6
  require "time"
7
7
 
@@ -9,148 +9,31 @@ module Ask
9
9
  module Rails
10
10
  module Harness
11
11
  class << self
12
+ # The Rails edition shares the generic configuration — the generic
13
+ # gem owns the runtime, this gem adapts it to Rails.
12
14
  def configure
13
15
  yield configuration
14
16
  end
15
17
 
16
18
  def configuration
17
- @configuration ||= Configuration.new
19
+ Ask::Ruby::Harness.configuration
18
20
  end
19
21
 
20
22
  def agent_session(**extra)
21
- # Auto-prune if configured
22
- cleanup! if configuration.max_session_age || configuration.max_sessions
23
-
24
- tools = configuration.tools.map { |t| t.is_a?(Class) ? t.new : t }
25
- prompt = extra.delete(:system_prompt) || configuration.system_prompt || default_system_prompt
26
-
27
- # Resolve environment-specific permissions and wire into agent hooks
28
- hooks = build_environment_hooks
29
-
30
- Ask::Agent::Session.new(
31
- model: configuration.default_model,
32
- max_turns: configuration.max_turns,
33
- system_prompt: prompt,
34
- tools: tools,
35
- state: configuration.persistence_adapter,
36
- hooks: hooks,
37
- **extra
38
- )
23
+ Ask::Ruby::Harness.agent_session(**extra)
39
24
  end
40
25
 
41
26
  def discover_tools!
42
- self.configuration.tools = Ask::Tools::Shell::TOOLS.map(&:new) + core_rails_tools + discovered_user_tools
27
+ Ask::Ruby::Harness.discover_tools!
43
28
  end
44
29
 
45
- # Prune old sessions and audit logs based on configuration limits.
46
- #
47
- # Removes sessions older than +max_session_age+ seconds, and limits the
48
- # total number of sessions to +max_sessions+ (deleting the oldest first).
49
- # Audit log entries older than the oldest kept session are also removed.
50
- #
51
- # Call manually via rake task or cron, or configure limits to auto-prune
52
- # on agent_session creation.
53
30
  def cleanup!
54
- prune_old_sessions
55
- limit_session_count
31
+ Ask::Ruby::Harness.cleanup!
56
32
  end
57
33
 
58
34
  def root
59
35
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
60
36
  end
61
-
62
- private
63
-
64
- def build_environment_hooks
65
- env_mode = configuration.effective_mode
66
- return {} unless env_mode
67
-
68
- perms = Ask::Agent::Policies::Permissions.new(mode: env_mode)
69
- { before_tool: [perms.method(:before_tool_call)] }
70
- rescue ArgumentError => e
71
- warn "[ask-rails-harness] Invalid environment mode: #{e.message}"
72
- {}
73
- end
74
-
75
- def prune_old_sessions
76
- age = configuration.max_session_age
77
- return unless age&.> 0
78
- return unless persistence_available?
79
-
80
- cutoff = age.seconds.ago
81
- count = 0
82
-
83
- configuration.persistence_adapter.list.each do |id|
84
- data = configuration.persistence_adapter.load(id)
85
- created = data&.dig(:metadata, :created_at)
86
- if created && Time.parse(created) < cutoff
87
- configuration.persistence_adapter.delete(id)
88
- count += 1
89
- end
90
- end
91
-
92
- count
93
- rescue StandardError
94
- nil
95
- end
96
-
97
- def limit_session_count
98
- max = configuration.max_sessions
99
- return unless max&.> 0
100
- return unless persistence_available?
101
-
102
- sessions = configuration.persistence_adapter.list
103
- excess = sessions.size - max
104
- return unless excess > 0
105
-
106
- # Delete oldest sessions first
107
- with_timestamps = sessions.map { |id|
108
- data = configuration.persistence_adapter.load(id)
109
- created = data&.dig(:metadata, :created_at)
110
- [id, created ? Time.parse(created) : Time.at(0)]
111
- }.sort_by(&:last)
112
-
113
- with_timestamps.first(excess).each do |id, _|
114
- configuration.persistence_adapter.delete(id)
115
- end
116
-
117
- excess
118
- rescue StandardError
119
- nil
120
- end
121
-
122
- def persistence_available?
123
- defined?(ActiveRecord::Base) &&
124
- ActiveRecord::Base.connection.data_source_exists?("ask_sessions")
125
- rescue StandardError
126
- false
127
- end
128
-
129
- def core_rails_tools
130
- CORE_RAILS_TOOLS.map(&:new)
131
- end
132
-
133
- def discovered_user_tools
134
- tools = []
135
- files = Dir[::Rails.root.join("app", "tools", "*.rb")]
136
- files.each do |f|
137
- require f
138
- klass = File.basename(f, ".rb").camelize.constantize rescue next
139
- tools << klass if klass < Ask::Rails::Harness::Tool
140
- end
141
- tools
142
- rescue
143
- tools
144
- end
145
-
146
- def default_system_prompt
147
- <<~PROMPT
148
- You are a Ruby on Rails software engineer.
149
- You have direct access to the application's code, database, and runtime.
150
- Use your tools to inspect and modify the codebase.
151
- Once you have enough information, stop calling tools and give your answer.
152
- PROMPT
153
- end
154
37
  end
155
38
  end
156
39
  end
@@ -158,31 +41,22 @@ end
158
41
 
159
42
  require_relative "harness/version"
160
43
  require_relative "harness/engine"
161
- require_relative "harness/configuration"
162
- require_relative "harness/audit_log"
163
- require_relative "harness/environment_permissions"
164
44
  require_relative "harness/auth"
165
45
  require_relative "harness/persistence"
166
46
  require_relative "harness/service_discovery"
167
47
  require_relative "harness/tool"
168
- require_relative "harness/tools/run_command"
169
- require_relative "harness/tools/query_database"
170
- require_relative "harness/tools/read_model"
171
- require_relative "harness/tools/read_log"
172
- require_relative "harness/tools/schema_graph"
48
+ require_relative "harness/tools" # backward-compat constant aliases
173
49
  require_relative "harness/tools/route_inspector"
174
- require_relative "harness/tools/run_tests"
175
- require_relative "harness/minitest_json_reporter"
176
50
 
177
51
  # Railtie is loaded only when Rails is fully available
178
52
  if defined?(::Rails::Railtie)
179
53
  require_relative "harness/railtie"
180
54
  end
181
55
 
182
- # Define after all tool files are loaded so the constants resolve
56
+ # Define after all tool files are loaded so the constants resolve. The
57
+ # generic Rails-aware tools come from ask-ruby-harness; this gem adds the
58
+ # Rails-native ones (RouteInspector) on top.
183
59
  Ask::Rails::Harness::CORE_RAILS_TOOLS = [
184
- Ask::Rails::Harness::Tools::RunCommand,
185
- Ask::Rails::Harness::Tools::QueryDatabase, Ask::Rails::Harness::Tools::ReadModel,
186
- Ask::Rails::Harness::Tools::ReadLog, Ask::Rails::Harness::Tools::SchemaGraph,
187
- Ask::Rails::Harness::Tools::RouteInspector, Ask::Rails::Harness::Tools::RunTests
60
+ *Ask::Ruby::Harness::HARNESS_TOOLS,
61
+ Ask::Rails::Harness::Tools::RouteInspector
188
62
  ].freeze
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-rails-harness
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -24,7 +24,7 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '7.1'
26
26
  - !ruby/object:Gem::Dependency
27
- name: ask-tools
27
+ name: ask-ruby-harness
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - ">="
@@ -37,34 +37,6 @@ dependencies:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0.1'
40
- - !ruby/object:Gem::Dependency
41
- name: ask-tools-shell
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: '0.1'
47
- type: :runtime
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: '0.1'
54
- - !ruby/object:Gem::Dependency
55
- name: ask-agent
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: 0.28.0
61
- type: :runtime
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: 0.28.0
68
40
  - !ruby/object:Gem::Dependency
69
41
  name: ask-auth
70
42
  requirement: !ruby/object:Gem::Requirement
@@ -155,23 +127,14 @@ files:
155
127
  - config/routes.rb
156
128
  - lib/ask-rails-harness.rb
157
129
  - lib/ask/rails/harness.rb
158
- - lib/ask/rails/harness/audit_log.rb
159
130
  - lib/ask/rails/harness/auth.rb
160
- - lib/ask/rails/harness/configuration.rb
161
131
  - lib/ask/rails/harness/engine.rb
162
- - lib/ask/rails/harness/environment_permissions.rb
163
- - lib/ask/rails/harness/minitest_json_reporter.rb
164
132
  - lib/ask/rails/harness/persistence.rb
165
133
  - lib/ask/rails/harness/railtie.rb
166
134
  - lib/ask/rails/harness/service_discovery.rb
167
135
  - lib/ask/rails/harness/tool.rb
168
- - lib/ask/rails/harness/tools/query_database.rb
169
- - lib/ask/rails/harness/tools/read_log.rb
170
- - lib/ask/rails/harness/tools/read_model.rb
136
+ - lib/ask/rails/harness/tools.rb
171
137
  - lib/ask/rails/harness/tools/route_inspector.rb
172
- - lib/ask/rails/harness/tools/run_command.rb
173
- - lib/ask/rails/harness/tools/run_tests.rb
174
- - lib/ask/rails/harness/tools/schema_graph.rb
175
138
  - lib/ask/rails/harness/version.rb
176
139
  - lib/ask/skills/rails.db_debug/SKILL.md
177
140
  - lib/ask/skills/rails.deploy_pipeline/SKILL.md
@@ -180,7 +143,6 @@ files:
180
143
  - lib/generators/ask/rails/harness/install/templates/audit_log_migration.rb
181
144
  - lib/generators/ask/rails/harness/install/templates/initializer.rb
182
145
  - lib/generators/ask/rails/harness/install/templates/migration.rb
183
- - lib/minitest/ask_rails_harness_plugin.rb
184
146
  homepage: https://github.com/ask-rb/ask-rails-harness
185
147
  licenses:
186
148
  - MIT
@@ -1,185 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
-
5
- module Ask
6
- module Rails
7
- module Harness
8
- # Append-only audit log for tool executions.
9
- #
10
- # Every tool call made by an agent is recorded in the +ask_audit_logs+
11
- # table with the intent (sanitized params) and outcome (status, timing),
12
- # but not the data returned. This gives a trustworthy, queryable record
13
- # of what the agent did without becoming a PII liability.
14
- #
15
- # Sensitive param values (keys matching +password+, +secret+, +token+,
16
- # +api_key+, +key+) are automatically redacted before logging.
17
- module AuditLog
18
- SENSITIVE_KEYS = /\A(password|secret|token|api_key|key|auth_token|access_token)\z/i
19
-
20
- class << self
21
- # Log a tool execution event.
22
- #
23
- # @param session_id [String] The agent session that triggered this call
24
- # @param tool_name [String] Name of the tool that ran
25
- # @param params [Hash] The parameters passed to the tool (sanitized automatically)
26
- # @param result [Ask::Result, Hash, nil] The result returned by the tool
27
- # @param error [StandardError, nil] The exception if the tool raised
28
- # @param duration_ms [Integer] Wall-clock time for the tool execution
29
- # @param user_context [Hash, nil] Who initiated the session (from config)
30
- def log(session_id:, tool_name:, params:, result: nil, error: nil, duration_ms:)
31
- now = Time.now.utc
32
- entry = {
33
- session_id: session_id,
34
- tool_name: tool_name,
35
- params: sanitize_params(params),
36
- result_summary: build_summary(tool_name, result, error),
37
- status: determine_status(result, error),
38
- error_message: determine_error(result, error),
39
- duration_ms: duration_ms,
40
- user_context: resolve_user_context,
41
- environment: environment_name,
42
- recorded_at: now,
43
- created_at: now,
44
- updated_at: now
45
- }
46
-
47
- if table_exists?
48
- write_entry(entry)
49
- end
50
-
51
- # Fire an ActiveSupport notification so host apps can subscribe
52
- ActiveSupport::Notifications.instrument("audit_log.ask_rails_harness", entry)
53
-
54
- entry
55
- end
56
-
57
- private
58
-
59
- def sanitize_params(params)
60
- return {} unless params.is_a?(Hash)
61
-
62
- params.each_with_object({}) do |(key, value), sanitized|
63
- if SENSITIVE_KEYS.match?(key.to_s)
64
- sanitized[key] = "[REDACTED]"
65
- else
66
- sanitized[key] = value
67
- end
68
- end
69
- end
70
-
71
- def determine_status(result, error)
72
- return "error" if error
73
- return "rejected" if result.is_a?(Ask::Result) && (result.error? || result.blocked?)
74
- "success"
75
- end
76
-
77
- def determine_error(result, error)
78
- return error.message if error
79
- if result.is_a?(Ask::Result)
80
- return result.error.to_s if result.error?
81
- return result.content.to_s if result.blocked?
82
- end
83
- nil
84
- end
85
-
86
- def extract_data(result)
87
- return nil unless result
88
-
89
- if result.is_a?(Ask::Result)
90
- result.content.is_a?(Hash) ? result.content : nil
91
- elsif result.is_a?(Hash)
92
- result
93
- else
94
- nil
95
- end
96
- end
97
-
98
- def build_summary(tool_name, result, error)
99
- if error
100
- return { error: error.class.name }
101
- end
102
-
103
- if result.is_a?(Ask::Result)
104
- if result.error?
105
- return { error: "rejected: #{result.error.to_s.truncate(200)}" }
106
- end
107
- if result.blocked?
108
- return { error: "blocked: #{result.content.to_s.truncate(200)}" }
109
- end
110
- end
111
-
112
- data = extract_data(result)
113
- return {} unless data
114
-
115
- summary = {}
116
- summary[:rows] = data[:rows]&.length if data.key?(:rows)
117
- summary[:columns] = data[:columns]&.length if data.key?(:columns)
118
- summary[:exit_status] = data[:exit_status] if data.key?(:exit_status)
119
- summary[:size] = data[:size] if data.key?(:size)
120
- summary[:matched_lines] = data[:matched_lines] if data.key?(:matched_lines)
121
- summary[:results] = data[:results]&.length if data.key?(:results)
122
- summary[:model] = data[:name] if data.key?(:name)
123
- summary
124
- end
125
-
126
- def resolve_user_context
127
- proc = Ask::Rails::Harness.configuration.current_user
128
- return nil unless proc.respond_to?(:call)
129
-
130
- result = proc.call
131
- result.is_a?(Hash) ? result : nil
132
- rescue StandardError
133
- nil
134
- end
135
-
136
- def environment_name
137
- defined?(::Rails) && ::Rails.respond_to?(:env) ? ::Rails.env.to_s : "unknown"
138
- end
139
-
140
- def table_exists?
141
- return false unless defined?(ActiveRecord::Base)
142
-
143
- # Only cache the true result — recheck if it was false
144
- return @table_exists if @table_exists
145
-
146
- @table_exists = begin
147
- conn = ActiveRecord::Base.connection
148
- conn.data_source_exists?("ask_audit_logs")
149
- rescue StandardError
150
- false
151
- end
152
- end
153
-
154
- def write_entry(entry)
155
- # Serialize JSON fields for database storage
156
- serialized = entry.dup
157
- %i[params result_summary user_context].each do |key|
158
- serialized[key] = ::JSON.generate(serialized[key]) if serialized[key].is_a?(Hash)
159
- end
160
-
161
- # Use raw SQL to avoid requiring a model class
162
- columns = serialized.keys
163
- values = columns.map { |col| ActiveRecord::Base.connection.quote(serialized[col]) }
164
- ActiveRecord::Base.connection.execute(
165
- "INSERT INTO ask_audit_logs (#{columns.join(', ')})
166
- VALUES (#{values.join(', ')})"
167
- )
168
- rescue StandardError => e
169
- # Silently fail — audit log should never crash the caller.
170
- # ::Rails avoids the bare-`Rails` constant resolving to Ask::Rails;
171
- # `&.` guards against Rails.logger returning nil (no app booted).
172
- ::Rails.logger&.warn("[ask-rails-harness] Audit log write failed: #{e.message}") if defined?(::Rails.logger)
173
- end
174
-
175
- # Reset cached table check (useful in tests)
176
- public
177
-
178
- def reset_table_check!
179
- @table_exists = nil
180
- end
181
- end
182
- end
183
- end
184
- end
185
- end
@@ -1,73 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ask
4
- module Rails
5
- module Harness
6
- class Configuration
7
- attr_accessor :default_model, :max_turns, :system_prompt,
8
- :tool_concurrency, :persistence_adapter, :tools,
9
- :current_user, :allowed_commands, :denied_commands,
10
- :max_session_age, :max_sessions
11
-
12
- # @return [Hash{Symbol => EnvironmentPermissions}] per-environment permission rules
13
- attr_reader :environments
14
-
15
- def initialize
16
- @default_model = "gpt-4o"
17
- @max_turns = 25
18
- @system_prompt = nil
19
- @tool_concurrency = 5
20
- @persistence_adapter = nil
21
- @tools = []
22
- @current_user = nil
23
- @allowed_commands = nil
24
- @denied_commands = nil
25
- @max_session_age = nil
26
- @max_sessions = nil
27
- @environments = {}
28
- end
29
-
30
- # Configure permissions for a specific Rails environment.
31
- #
32
- # config.environment :production do |env|
33
- # env.mode = :read_only
34
- # env.allowed_commands = [/^rails routes/]
35
- # env.denied_commands = [/rm/, /dropdb/]
36
- # end
37
- #
38
- # @param name [Symbol, String] environment name (:production, :development, :staging, etc.)
39
- def environment(name)
40
- env = EnvironmentPermissions.new
41
- yield env
42
- @environments[name.to_sym] = env
43
- end
44
-
45
- # Resolved allowed commands for the current Rails environment.
46
- # Falls back to the global +allowed_commands+ if no per-env config.
47
- #
48
- # @return [Array<Regexp>, nil]
49
- def effective_allowed_commands
50
- env = @environments[::Rails.env.to_sym]
51
- env&.allowed_commands || @allowed_commands
52
- end
53
-
54
- # Resolved denied commands for the current Rails environment.
55
- # Falls back to the global +denied_commands+ if no per-env config.
56
- #
57
- # @return [Array<Regexp>, nil]
58
- def effective_denied_commands
59
- env = @environments[::Rails.env.to_sym]
60
- env&.denied_commands || @denied_commands
61
- end
62
-
63
- # Resolved access mode for the current Rails environment.
64
- #
65
- # @return [Symbol, nil] +:full_access+, +:read_only+, +:ask_before_changes+, or nil
66
- def effective_mode
67
- env = @environments[::Rails.env.to_sym]
68
- env&.mode || nil
69
- end
70
- end
71
- end
72
- end
73
- end