ask-rails-harness 0.1.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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +24 -0
  3. data/LICENSE +21 -0
  4. data/README.md +122 -0
  5. data/app/controllers/ask/rails/harness/chat_controller.rb +310 -0
  6. data/app/models/ask/rails/harness/session.rb +25 -0
  7. data/app/views/ask/rails/harness/chat/index.html.erb +6 -0
  8. data/app/views/layouts/ask/rails/harness/application.html.erb +521 -0
  9. data/config/routes.rb +17 -0
  10. data/lib/ask/rails/harness/audit_log.rb +183 -0
  11. data/lib/ask/rails/harness/auth.rb +24 -0
  12. data/lib/ask/rails/harness/configuration.rb +73 -0
  13. data/lib/ask/rails/harness/engine.rb +13 -0
  14. data/lib/ask/rails/harness/environment_permissions.rb +41 -0
  15. data/lib/ask/rails/harness/persistence.rb +54 -0
  16. data/lib/ask/rails/harness/railtie.rb +34 -0
  17. data/lib/ask/rails/harness/service_discovery.rb +50 -0
  18. data/lib/ask/rails/harness/tool.rb +42 -0
  19. data/lib/ask/rails/harness/tools/query_database.rb +74 -0
  20. data/lib/ask/rails/harness/tools/read_file.rb +23 -0
  21. data/lib/ask/rails/harness/tools/read_log.rb +120 -0
  22. data/lib/ask/rails/harness/tools/read_model.rb +75 -0
  23. data/lib/ask/rails/harness/tools/read_routes.rb +20 -0
  24. data/lib/ask/rails/harness/tools/route_inspector.rb +71 -0
  25. data/lib/ask/rails/harness/tools/run_command.rb +60 -0
  26. data/lib/ask/rails/harness/tools/schema_graph.rb +176 -0
  27. data/lib/ask/rails/harness/tools/search_codebase.rb +23 -0
  28. data/lib/ask/rails/harness/version.rb +9 -0
  29. data/lib/ask/rails/harness.rb +189 -0
  30. data/lib/ask/skills/rails.db_debug/SKILL.md +118 -0
  31. data/lib/ask/skills/rails.deploy_pipeline/SKILL.md +125 -0
  32. data/lib/ask/skills/rails.route_trouble/SKILL.md +127 -0
  33. data/lib/ask-rails-harness.rb +3 -0
  34. data/lib/generators/ask/rails/harness/install/install_generator.rb +36 -0
  35. data/lib/generators/ask/rails/harness/install/templates/audit_log_migration.rb +22 -0
  36. data/lib/generators/ask/rails/harness/install/templates/initializer.rb +5 -0
  37. data/lib/generators/ask/rails/harness/install/templates/migration.rb +12 -0
  38. metadata +208 -0
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+ require "ask/agent"
5
+ require "ask/auth"
6
+
7
+ module Ask
8
+ module Rails
9
+ module Harness
10
+ class << self
11
+ def configure
12
+ yield configuration
13
+ end
14
+
15
+ def configuration
16
+ @configuration ||= Configuration.new
17
+ end
18
+
19
+ def agent_session(**extra)
20
+ # Auto-prune if configured
21
+ cleanup! if configuration.max_session_age || configuration.max_sessions
22
+
23
+ tools = configuration.tools.map { |t| t.is_a?(Class) ? t.new : t }
24
+ prompt = extra.delete(:system_prompt) || configuration.system_prompt || default_system_prompt
25
+
26
+ # Resolve environment-specific permissions and wire into agent hooks
27
+ hooks = build_environment_hooks
28
+
29
+ Ask::Agent::Session.new(
30
+ model: configuration.default_model,
31
+ max_turns: configuration.max_turns,
32
+ system_prompt: prompt,
33
+ tools: tools,
34
+ state: configuration.persistence_adapter,
35
+ hooks: hooks,
36
+ **extra
37
+ )
38
+ end
39
+
40
+ def discover_tools!
41
+ self.configuration.tools = Ask::Tools::Shell::TOOLS.map(&:new) + core_rails_tools + discovered_user_tools
42
+ end
43
+
44
+ # Prune old sessions and audit logs based on configuration limits.
45
+ #
46
+ # Removes sessions older than +max_session_age+ seconds, and limits the
47
+ # total number of sessions to +max_sessions+ (deleting the oldest first).
48
+ # Audit log entries older than the oldest kept session are also removed.
49
+ #
50
+ # Call manually via rake task or cron, or configure limits to auto-prune
51
+ # on agent_session creation.
52
+ def cleanup!
53
+ prune_old_sessions
54
+ limit_session_count
55
+ end
56
+
57
+ def root
58
+ @root ||= Pathname.new(File.expand_path("../..", __dir__))
59
+ end
60
+
61
+ private
62
+
63
+ def build_environment_hooks
64
+ env_mode = configuration.effective_mode
65
+ return {} unless env_mode
66
+
67
+ perms = Ask::Agent::Extensions::Permissions.new(mode: env_mode)
68
+ { before_tool: [perms.method(:before_tool_call)] }
69
+ rescue ArgumentError => e
70
+ warn "[ask-rails-harness] Invalid environment mode: #{e.message}"
71
+ {}
72
+ end
73
+
74
+ def prune_old_sessions
75
+ age = configuration.max_session_age
76
+ return unless age&.> 0
77
+ return unless persistence_available?
78
+
79
+ cutoff = age.seconds.ago
80
+ count = 0
81
+
82
+ configuration.persistence_adapter.list.each do |id|
83
+ data = configuration.persistence_adapter.load(id)
84
+ created = data&.dig(:metadata, :created_at)
85
+ if created && Time.parse(created) < cutoff
86
+ configuration.persistence_adapter.delete(id)
87
+ count += 1
88
+ end
89
+ end
90
+
91
+ count
92
+ rescue StandardError
93
+ nil
94
+ end
95
+
96
+ def limit_session_count
97
+ max = configuration.max_sessions
98
+ return unless max&.> 0
99
+ return unless persistence_available?
100
+
101
+ sessions = configuration.persistence_adapter.list
102
+ excess = sessions.size - max
103
+ return unless excess > 0
104
+
105
+ # Delete oldest sessions first
106
+ with_timestamps = sessions.map { |id|
107
+ data = configuration.persistence_adapter.load(id)
108
+ created = data&.dig(:metadata, :created_at)
109
+ [id, created ? Time.parse(created) : Time.at(0)]
110
+ }.sort_by(&:last)
111
+
112
+ with_timestamps.first(excess).each do |id, _|
113
+ configuration.persistence_adapter.delete(id)
114
+ end
115
+
116
+ excess
117
+ rescue StandardError
118
+ nil
119
+ end
120
+
121
+ def persistence_available?
122
+ defined?(ActiveRecord::Base) &&
123
+ ActiveRecord::Base.connection.data_source_exists?("ask_sessions")
124
+ rescue StandardError
125
+ false
126
+ end
127
+
128
+ def core_rails_tools
129
+ CORE_RAILS_TOOLS.map(&:new)
130
+ end
131
+
132
+ def discovered_user_tools
133
+ tools = []
134
+ files = Dir[::Rails.root.join("app", "tools", "*.rb")]
135
+ files.each do |f|
136
+ require f
137
+ klass = File.basename(f, ".rb").camelize.constantize rescue next
138
+ tools << klass if klass < Ask::Rails::Harness::Tool
139
+ end
140
+ tools
141
+ rescue
142
+ tools
143
+ end
144
+
145
+ def default_system_prompt
146
+ <<~PROMPT
147
+ You are a Ruby on Rails software engineer.
148
+ You have direct access to the application's code, database, and runtime.
149
+ Use your tools to inspect and modify the codebase.
150
+ Once you have enough information, stop calling tools and give your answer.
151
+ PROMPT
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ require_relative "harness/version"
159
+ require_relative "harness/engine"
160
+ require_relative "harness/configuration"
161
+ require_relative "harness/audit_log"
162
+ require_relative "harness/environment_permissions"
163
+ require_relative "harness/auth"
164
+ require_relative "harness/persistence"
165
+ require_relative "harness/service_discovery"
166
+ require_relative "harness/tool"
167
+ require_relative "harness/tools/read_file"
168
+ require_relative "harness/tools/run_command"
169
+ require_relative "harness/tools/search_codebase"
170
+ require_relative "harness/tools/read_routes"
171
+ require_relative "harness/tools/query_database"
172
+ require_relative "harness/tools/read_model"
173
+ require_relative "harness/tools/read_log"
174
+ require_relative "harness/tools/schema_graph"
175
+ require_relative "harness/tools/route_inspector"
176
+
177
+ # Railtie is loaded only when Rails is fully available
178
+ if defined?(::Rails::Railtie)
179
+ require_relative "harness/railtie"
180
+ end
181
+
182
+ # Define after all tool files are loaded so the constants resolve
183
+ Ask::Rails::Harness::CORE_RAILS_TOOLS = [
184
+ Ask::Rails::Harness::Tools::ReadFile, Ask::Rails::Harness::Tools::RunCommand,
185
+ Ask::Rails::Harness::Tools::SearchCodebase, Ask::Rails::Harness::Tools::ReadRoutes,
186
+ Ask::Rails::Harness::Tools::QueryDatabase, Ask::Rails::Harness::Tools::ReadModel,
187
+ Ask::Rails::Harness::Tools::ReadLog, Ask::Rails::Harness::Tools::SchemaGraph,
188
+ Ask::Rails::Harness::Tools::RouteInspector
189
+ ].freeze
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: rails.db_debug
3
+ description: Step-by-step methodology for debugging database performance issues in Rails
4
+ ---
5
+
6
+ Use this skill when investigating slow queries, N+1 problems, missing indexes, or
7
+ general database performance issues in a Rails application.
8
+
9
+ ## Step 1: Identify the Slow Queries
10
+
11
+ Use `ReadLog.new.call(lines: 200, search: "SELECT")` to find recent database
12
+ queries in the application log. Look for:
13
+
14
+ - Queries taking > 100ms (look for "duration:" or "↳" markers in Rails logs)
15
+ - Repetitive queries with the same structure (potential N+1)
16
+ - Queries running in loops (same query, different IDs)
17
+
18
+ If you need more detail, narrow the search: `ReadLog.new.call(lines: 500, level: "WARN")`.
19
+
20
+ ## Step 2: Understand the Schema
21
+
22
+ For each model involved in the slow queries, inspect it:
23
+
24
+ ```ruby
25
+ ReadModel.new.call(name: "User")
26
+ ReadModel.new.call(name: "Post")
27
+ ```
28
+
29
+ Focus on:
30
+ - **Columns** — are there columns that look like foreign keys without indexes?
31
+ - **Associations** — what associations exist and what `class_name` do they use?
32
+ - **Validators** — any database-level constraints that could be missing?
33
+
34
+ Run `ReadModel.new.call(name: "User", detail: "columns")` if you only need columns.
35
+
36
+ ## Step 3: Check for Missing Indexes
37
+
38
+ Query the database for actual indexes:
39
+
40
+ ```ruby
41
+ QueryDatabase.new.call(
42
+ sql: "SELECT tablename, indexname, indexdef FROM pg_indexes WHERE schemaname = 'public' ORDER BY tablename, indexname",
43
+ limit: 200
44
+ )
45
+ ```
46
+
47
+ Look for:
48
+ - Foreign key columns that lack indexes (e.g. `user_id` without index)
49
+ - Columns used in `WHERE` clauses without indexes
50
+ - Composite indexes that could cover multiple query patterns
51
+
52
+ If an index is missing, check if adding one would help:
53
+ ```ruby
54
+ QueryDatabase.new.call(
55
+ sql: "EXPLAIN ANALYZE SELECT * FROM posts WHERE user_id = 1",
56
+ limit: 10
57
+ )
58
+ ```
59
+
60
+ ## Step 4: Detect N+1 Queries
61
+
62
+ N+1 manifests as the same query repeated with different IDs:
63
+
64
+ ```ruby
65
+ # Search for repetitive query patterns
66
+ ReadLog.new.call(lines: 500, search: "WHERE.*IN")
67
+ ```
68
+
69
+ Common N+1 patterns:
70
+ - Loading `Post.all` then accessing `post.comments` individually
71
+ - Loading `User.all` then accessing `user.profile` individually
72
+ - Serializing associations in views without eager loading
73
+
74
+ Fix with `.includes(:association)`, `.eager_load(:association)`, or
75
+ `.preload(:association)`.
76
+
77
+ ## Step 5: Profile Slow Queries with EXPLAIN
78
+
79
+ For any identified slow query, get the execution plan:
80
+
81
+ ```ruby
82
+ QueryDatabase.new.call(
83
+ sql: "EXPLAIN (ANALYZE, BUFFERS) SELECT posts.* FROM posts WHERE posts.user_id = 1 ORDER BY posts.created_at DESC LIMIT 20",
84
+ limit: 10
85
+ )
86
+ ```
87
+
88
+ What to look for in explain output:
89
+ - **Sequential scans** (`Seq Scan`) — missing index
90
+ - **Sort operations** — missing index on sort column
91
+ - **Nested loop joins** with high row counts — missing composite index
92
+ - **Bitmap heap scans** with high costs — index may be suboptimal
93
+
94
+ If `ANALYZE` takes too long, use `EXPLAIN (BUFFERS)` without `ANALYZE` for cost estimates.
95
+
96
+ ## Step 6: Check Bulk Loading and Batch Operations
97
+
98
+ If the issue involves importing or updating many records:
99
+
100
+ ```ruby
101
+ QueryDatabase.new.call(
102
+ sql: "SELECT schemaname, tablename, seq_scan, seq_tup_read, idx_scan, idx_tup_fetch FROM pg_stat_user_tables ORDER BY seq_scan DESC",
103
+ limit: 20
104
+ )
105
+ ```
106
+
107
+ High `seq_scan` counts compared to `idx_scan` indicate tables where indexes
108
+ are missing and sequential scans are happening frequently.
109
+
110
+ ## Failure Mode Guide
111
+
112
+ | Symptom | Likely Cause | Action |
113
+ |---------|-------------|--------|
114
+ | Query takes >1s | Missing index on WHERE column | Check pg_indexes, add index |
115
+ | Same query 50x in log | N+1 in controller/view | Add `.includes()` |
116
+ | Query slow AFTER adding index | Wrong index type or order | Verify index column order matches query |
117
+ | `EXPLAIN ANALYZE` hanging | Table lock or very large table | Use `EXPLAIN (BUFFERS)` without ANALYZE |
118
+ | Missing `pg_indexes` output | Not Postgres | Use `SHOW INDEX FROM <table>` for MySQL |
@@ -0,0 +1,125 @@
1
+ ---
2
+ name: rails.deploy_pipeline
3
+ description: Pre-deployment checklist for Rails applications — migrations, assets, credentials, jobs, and logs
4
+ ---
5
+
6
+ Use this skill before or during a Rails deployment to ensure nothing is missed.
7
+ Follow the steps in order — each builds on the previous.
8
+
9
+ ## Step 1: Check Pending Migrations
10
+
11
+ Before deploying, always check for unapplied migrations:
12
+
13
+ ```ruby
14
+ RunCommand.new.call(command: "bin/rails db:migrate:status | grep 'down'")
15
+ ```
16
+
17
+ If there are pending migrations:
18
+ 1. Review them with `ReadFile.new.call(path: "db/migrate/<TIMESTAMP>_migration_name.rb")`
19
+ 2. Verify they're reversible: check `change`, `up`/`down`, or `reversible` blocks
20
+ 3. Check for data migrations (e.g., backfills) that should run separately
21
+ 4. Estimate execution time — large tables may need locking strategy
22
+
23
+ ## Step 2: Verify Assets Pipeline
24
+
25
+ For Rails with assets (Sprockets or Propshaft):
26
+
27
+ ```ruby
28
+ # Precompile locally to catch errors early
29
+ RunCommand.new.call(command: "bin/rails assets:precompile 2>&1")
30
+ ```
31
+
32
+ Check for:
33
+ - Missing asset references (SCSS variables, image references)
34
+ - JavaScript compilation errors
35
+ - Asset fingerprint changes (manifold fingerprints if CSS changed)
36
+
37
+ For importmap or esbuild/vite setups:
38
+
39
+ ```ruby
40
+ # Check build config
41
+ ReadFile.new.call(path: "package.json")
42
+ ReadFile.new.call(path: "vite.config.ts") if File.exist?("Rails.root.join('vite.config.ts')")
43
+ ```
44
+
45
+ ## Step 3: Review Credentials and Secrets
46
+
47
+ Verify that all required credentials exist in the target environment:
48
+
49
+ ```ruby
50
+ # For production credentials
51
+ RunCommand.new.call(command: "bin/rails credentials:show --environment production 2>&1")
52
+ ```
53
+
54
+ Check for common secrets that may need updating:
55
+ - `secret_key_base`
56
+ - `database_password`
57
+ - Third-party API keys (AWS, Stripe, SendGrid, etc.)
58
+ - JWT signing keys
59
+ - Any env vars referenced in `config/` files that aren't in credentials
60
+
61
+ ## Step 4: Check Background Jobs
62
+
63
+ Review sidekiq/active job configuration before deploy:
64
+
65
+ ```ruby
66
+ # Check job files for any that need queue configuration
67
+ Glob.new.call(pattern: "app/jobs/**/*.rb")
68
+ ```
69
+
70
+ For any new or modified jobs:
71
+ 1. Verify the queue adapter is configured in `production.rb`
72
+ 2. Check for job retry logic that might affect rollback
73
+ 3. Review `ReadFile.new.call(path: "config/sidekiq.yml")` if using Sidekiq
74
+ 4. Verify scheduled/cron jobs if using `sidekiq-cron` or `whenever`
75
+
76
+ ## Step 5: Review Production Log for Pre-deploy Issues
77
+
78
+ Check that the current production environment is healthy before deploying:
79
+
80
+ ```ruby
81
+ ReadLog.new.call(lines: 100, level: "ERROR")
82
+ ```
83
+
84
+ If there are recent errors, investigate before deploying more changes.
85
+
86
+ ## Step 6: Verify Dependencies and Gem Versions
87
+
88
+ Check for critical gem updates or changes:
89
+
90
+ ```ruby
91
+ # Review Gemfile for new/modified gems
92
+ RunCommand.new.call(command: "git diff HEAD -- Gemfile")
93
+ ```
94
+
95
+ If adding a gem that needs system dependencies or native extensions:
96
+ ```ruby
97
+ RunCommand.new.call(command: "bundle platform")
98
+ ```
99
+
100
+ ## Step 7: Config File Checklist
101
+
102
+ Verify configuration files for the target environment:
103
+
104
+ ```ruby
105
+ ReadFile.new.call(path: "config/environments/production.rb")
106
+ ReadFile.new.call(path: "config/database.yml")
107
+ ReadFile.new.call(path: "config/cable.yml") if File.exist?("config/cable.yml")
108
+ ReadFile.new.call(path: "config/storage.yml") if File.exist?("config/storage.yml")
109
+ ```
110
+
111
+ Key production checks:
112
+ - `config.force_ssl = true`
113
+ - `config.consider_all_requests_local = false`
114
+ - Proper cache store configured (`:mem_cache_store`, `:redis_cache_store`)
115
+ - Active Storage service configured for production
116
+
117
+ ## Step 8: Quick Rollback Checklist
118
+
119
+ Before deploying, know how to roll back:
120
+
121
+ 1. **Database**: `RunCommand.new.call(command: "bin/rails db:migrate:down VERSION=<previous>")`
122
+ 2. **Code**: Git revert the deploy commit
123
+ 3. **Assets**: Previous version's assets should still be cached
124
+ 4. **Jobs**: Check if backward-incompatible changes won't replay failed jobs
125
+ 5. **Feature flags**: If using flipper or similar, toggle off new features first
@@ -0,0 +1,127 @@
1
+ ---
2
+ name: rails.route_trouble
3
+ description: Step-by-step methodology for debugging routing issues in Rails
4
+ ---
5
+
6
+ Use this skill when a route is returning 404, you're getting route matching errors,
7
+ or routes aren't behaving as expected in a Rails application.
8
+
9
+ ## Step 1: Read the Routes File
10
+
11
+ Start by examining the routes configuration:
12
+
13
+ ```ruby
14
+ ReadRoutes.new.call
15
+ ```
16
+
17
+ This reads `config/routes.rb` — the source of truth for all route definitions.
18
+
19
+ Look for:
20
+ - The overall structure (namespaced, nested, shallow routes?)
21
+ - Resources that might be missing
22
+ - Constraints or conditions that could block matching
23
+ - Route ordering (more specific routes should come before less specific)
24
+
25
+ ## Step 2: Check the Compiled Routes
26
+
27
+ Rails routes have a specific matching order. Use `RunCommand` to inspect the live routes:
28
+
29
+ ```ruby
30
+ RunCommand.new.call(command: "bin/rails routes")
31
+ ```
32
+
33
+ If you know the path you're trying to match, grep for it:
34
+
35
+ ```ruby
36
+ RunCommand.new.call(command: "bin/rails routes | grep users")
37
+ ```
38
+
39
+ For a specific route name:
40
+
41
+ ```ruby
42
+ RunCommand.new.call(command: "bin/rails routes --grep user_profile")
43
+ ```
44
+
45
+ ## Step 3: Check Route Parameters and Constraints
46
+
47
+ Routes often fail because of parameter mismatches or constraints. Check for:
48
+
49
+ **Required parameters:** Does the route define `:id` but the URL doesn't include it?
50
+
51
+ **Format constraints:** Routes with `:format` (like `.json`, `.html`) constraints
52
+ can fail silently. Check if there's a default format:
53
+
54
+ ```ruby
55
+ # In routes.rb:
56
+ resources :posts, defaults: { format: :json }
57
+ ```
58
+
59
+ **Constraint classes:** Custom route constraints like `subdomain` or request-based
60
+ constraints can prevent matching:
61
+
62
+ ```ruby
63
+ # In routes.rb:
64
+ get "admin", to: "admin/dashboard#show", constraints: ->(req) { req.subdomain == "admin" }
65
+ ```
66
+
67
+ Check the constraints against the actual request parameters.
68
+
69
+ ## Step 4: Trace the Match
70
+
71
+ For a failing request, trace how Rails would match it:
72
+
73
+ ```ruby
74
+ RunCommand.new.call(
75
+ command: "bin/rails runner \"puts Rails.application.routes.recognize_path('/users/1/edit', method: :get)\""
76
+ )
77
+ ```
78
+
79
+ This will raise `ActionController::RoutingError` if no route matches — the
80
+ error message tells you what routes were tried before failing.
81
+
82
+ If the route exists but the controller isn't found:
83
+
84
+ ```ruby
85
+ ReadFile.new.call(path: "app/controllers/users_controller.rb")
86
+ ```
87
+
88
+ ## Step 5: Check Namespace and Module Nesting
89
+
90
+ Routes in namespaced controllers fail when the controller file is in the wrong
91
+ directory or the module is misnamed:
92
+
93
+ ```ruby
94
+ # Route:
95
+ namespace :admin do
96
+ resources :users
97
+ end
98
+
99
+ # Expects:
100
+ # app/controllers/admin/users_controller.rb
101
+ # class Admin::UsersController < ApplicationController
102
+ ```
103
+
104
+ Use `Glob` to verify the controller exists:
105
+
106
+ ```ruby
107
+ # Via RunCommand:
108
+ RunCommand.new.call(command: "ls app/controllers/admin/")
109
+ ```
110
+
111
+ ## Step 6: Verify Helper Paths and Named Routes
112
+
113
+ If you're debugging a `No route matches` error from a view (link_to, form_with):
114
+
115
+ 1. Check the route name: `RunCommand.new.call(command: "bin/rails routes | grep user_path")`
116
+ 2. Verify the route helper: `RunCommand.new.call(command: "bin/rails routes --grep user")`
117
+ 3. Check for route helper overrides in custom constraints or defaults
118
+
119
+ ## Failure Mode Guide
120
+
121
+ | Symptom | Likely Cause | Action |
122
+ |---------|-------------|--------|
123
+ | 404 for existing route | Route constraint blocking | Check `constraints:` block in routes.rb |
124
+ | `No route matches` | Missing resource definition | Add `resources :model_name` |
125
+ | Route works in dev, not prod | Different route ordering | Check routes file isn't conditionally loaded |
126
+ | `Missing controller` | Wrong namespace or filename | Verify file path matches module structure |
127
+ | Path helper not responding | Wrong route name or params mismatch | Check `bin/rails routes --grep helper_name` |
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ask/rails/harness"
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Ask
6
+ module Rails
7
+ module Harness
8
+ module Generators
9
+ class InstallGenerator < ::Rails::Generators::Base
10
+ source_root File.expand_path("templates", __dir__)
11
+
12
+ desc "Creates ask-rails-harness configuration and migration"
13
+
14
+ def create_initializer
15
+ template "initializer.rb", "config/initializers/ask_rails_harness.rb"
16
+ end
17
+
18
+ def create_migration
19
+ timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
20
+ template "migration.rb", "db/migrate/#{timestamp}_create_ask_sessions.rb"
21
+ end
22
+
23
+ def create_audit_log_migration
24
+ timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
25
+ template "audit_log_migration.rb", "db/migrate/#{timestamp}_create_ask_audit_logs.rb"
26
+ end
27
+
28
+ def create_tools_directory
29
+ empty_directory "app/tools"
30
+ create_file "app/tools/.keep", ""
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ class CreateAskAuditLogs < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :ask_audit_logs do |t|
4
+ t.string :session_id, null: false
5
+ t.string :tool_name, null: false
6
+ t.jsonb :params
7
+ t.jsonb :result_summary
8
+ t.string :status, null: false, default: "success"
9
+ t.text :error_message
10
+ t.integer :duration_ms
11
+ t.jsonb :user_context
12
+ t.string :environment
13
+ t.datetime :recorded_at, null: false
14
+
15
+ t.timestamps
16
+ end
17
+
18
+ add_index :ask_audit_logs, :session_id
19
+ add_index :ask_audit_logs, [:recorded_at, :tool_name]
20
+ add_index :ask_audit_logs, :status
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ Ask::Rails::Harness.configure do |config|
2
+ config.default_model = ENV.fetch("ASK_DEFAULT_MODEL", "gpt-4o")
3
+ config.max_turns = ENV.fetch("ASK_MAX_TURNS", 25).to_i
4
+ config.persistence_adapter = Ask::Rails::Harness::Persistence.new
5
+ end
@@ -0,0 +1,12 @@
1
+ class CreateAskSessions < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :ask_sessions do |t|
4
+ t.string :session_id, null: false
5
+ t.string :model
6
+ t.jsonb :data, default: {}
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :ask_sessions, :session_id, unique: true
11
+ end
12
+ end