rails-ai-context 0.15.6 → 0.15.7
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/CHANGELOG.md +12 -0
- data/docs/GUIDE.md +8 -2
- data/lib/rails_ai_context/introspectors/config_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +48 -15
- data/lib/rails_ai_context/introspectors/model_introspector.rb +31 -3
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +19 -1
- data/lib/rails_ai_context/introspectors/test_introspector.rb +1 -1
- data/lib/rails_ai_context/tools/get_controllers.rb +7 -5
- data/lib/rails_ai_context/tools/get_model_details.rb +6 -1
- data/lib/rails_ai_context/tools/get_schema.rb +2 -1
- data/lib/rails_ai_context/tools/get_test_info.rb +3 -1
- data/lib/rails_ai_context/tools/validate.rb +31 -14
- data/lib/rails_ai_context/version.rb +1 -1
- data/server.json +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39df765c4841adb33e68870093b8f58d02ff28dfe5e245ee81655902a9b0fb78
|
|
4
|
+
data.tar.gz: 58385dad4ad55a627d854a2611ed16f951ac0409d54d80e96966efa8b0e1613b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 49755cc6de2afc6a536b470abea60b5a873a9f73419754f4ad304dd8988b34ebfa65b7d69e9878e2c7bdd3418183e9a71da16007f374161e87f44259c209f5f7
|
|
7
|
+
data.tar.gz: 83441b234d6f429d4a066ec79dece90fd5685d5ee4a3e5df882241b21b2b79cddde40d9c0c4fa3ac79322bf26764cf694a4beaf8dc9c419c82d9bc96e37009e3
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.15.7] - 2026-03-22
|
|
9
|
+
|
|
10
|
+
### Improved
|
|
11
|
+
|
|
12
|
+
- **Hybrid filter extraction** — controller filters now use reflection for complete names (handles inheritance + skips), with source parsing from the inheritance chain for only/except constraints.
|
|
13
|
+
- **Callback source fallback** — when reflection returns nothing (e.g. CI), falls back to parsing callback declarations from model source files.
|
|
14
|
+
- **ERB validation accuracy** — in-process compilation with `<%=` → `<%` pre-processing and yield wrapper eliminates false positives from block-form helpers.
|
|
15
|
+
- **Schema static parser** — now extracts `null: false`, `default:`, `array: true` from schema.rb columns, and parses `add_foreign_key` declarations.
|
|
16
|
+
- **Array column display** — schema tool shows PostgreSQL array types as `string[]`, `integer[]`, etc.
|
|
17
|
+
- **Concern test lookup** — `rails_get_test_info(model:"PlanLimitable")` searches concern test paths.
|
|
18
|
+
- **Controller flexible matching** — underscore-based normalization handles CamelCase, snake_case, and slash notation consistently.
|
|
19
|
+
|
|
8
20
|
## [0.15.6] - 2026-03-22
|
|
9
21
|
|
|
10
22
|
### Added
|
data/docs/GUIDE.md
CHANGED
|
@@ -404,7 +404,7 @@ Returns test infrastructure details. Optionally filter by model or controller to
|
|
|
404
404
|
|
|
405
405
|
| Param | Type | Description |
|
|
406
406
|
|-------|------|-------------|
|
|
407
|
-
| `model` | string | Show tests for a specific model (e.g. `User`). |
|
|
407
|
+
| `model` | string | Show tests for a specific model (e.g. `User`). Also searches concern test paths (`spec/models/concerns/`, `test/models/concerns/`). |
|
|
408
408
|
| `controller` | string | Show tests for a specific controller (e.g. `Cooks`). |
|
|
409
409
|
| `detail` | string | `summary` / `standard` (default) / `full`. |
|
|
410
410
|
|
|
@@ -782,6 +782,12 @@ RailsAiContext.configure do |config|
|
|
|
782
782
|
# schema.rb / structure.sql parse limit (default: 10MB)
|
|
783
783
|
# config.max_schema_file_size = 10_000_000
|
|
784
784
|
|
|
785
|
+
# Total aggregated view content for UI patterns (default: 5MB)
|
|
786
|
+
# config.max_view_total_size = 5_000_000
|
|
787
|
+
|
|
788
|
+
# Per-view file during aggregation (default: 500KB)
|
|
789
|
+
# config.max_view_file_size = 500_000
|
|
790
|
+
|
|
785
791
|
# Max search results per call (default: 100)
|
|
786
792
|
# config.max_search_results = 100
|
|
787
793
|
|
|
@@ -888,7 +894,7 @@ These run by default. Fast and cover core Rails structure.
|
|
|
888
894
|
| `controllers` | Actions, filters (before/after/around with only/except), strong params methods, parent class, API controller detection, concerns. |
|
|
889
895
|
| `tests` | Test framework (rspec/minitest), factories/fixtures with locations and counts, system tests, CI config files, coverage tool, test helpers, VCR cassettes. |
|
|
890
896
|
| `migrations` | Total count, schema version, pending migrations, recent migration history with detected actions (create_table, add_column, etc.), migration statistics. |
|
|
891
|
-
| `config` | Cache store, session store, timezone, middleware stack, initializers, credentials
|
|
897
|
+
| `config` | Cache store, session store, timezone, queue adapter, mailer settings, middleware stack, initializers, credentials status, CurrentAttributes classes. |
|
|
892
898
|
| `stimulus` | Stimulus controllers with targets, values (with types), actions, outlets, classes. Extracted from JS/TS files. |
|
|
893
899
|
| `view_templates` | View file contents, partial references, Stimulus data attributes, UI pattern extraction, model field usage in partials. |
|
|
894
900
|
| `design_tokens` | Auto-detects CSS framework (Tailwind v3/v4, Bootstrap, Sass, plain CSS) and extracts design tokens from config files and built CSS. |
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module RailsAiContext
|
|
4
4
|
module Introspectors
|
|
5
5
|
# Extracts application configuration: cache store, session store,
|
|
6
|
-
# timezone, middleware stack, initializers, credentials
|
|
6
|
+
# timezone, middleware stack, initializers, credentials status.
|
|
7
7
|
class ConfigIntrospector
|
|
8
8
|
attr_reader :app
|
|
9
9
|
|
|
@@ -150,30 +150,63 @@ module RailsAiContext
|
|
|
150
150
|
actions.sort
|
|
151
151
|
end
|
|
152
152
|
|
|
153
|
-
#
|
|
154
|
-
#
|
|
153
|
+
# Hybrid approach: reflection for complete filter names (handles inheritance + skips),
|
|
154
|
+
# source parsing from inheritance chain for only/except constraints.
|
|
155
155
|
def extract_filters(ctrl, source = nil)
|
|
156
|
+
if ctrl.respond_to?(:_process_action_callbacks)
|
|
157
|
+
reflection_filters = ctrl._process_action_callbacks.filter_map do |cb|
|
|
158
|
+
next if cb.filter.is_a?(Proc) || cb.filter.to_s.start_with?("_")
|
|
159
|
+
next if excluded_filters.include?(cb.filter.to_s)
|
|
160
|
+
{ name: cb.filter.to_s, kind: cb.kind.to_s }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
if reflection_filters.any?
|
|
164
|
+
# Collect only/except constraints from source files in the inheritance chain
|
|
165
|
+
source_constraints = collect_source_constraints(ctrl, source)
|
|
166
|
+
reflection_filters.each do |f|
|
|
167
|
+
if (sc = source_constraints[f[:name]])
|
|
168
|
+
f[:only] = sc[:only] if sc[:only]&.any?
|
|
169
|
+
f[:except] = sc[:except] if sc[:except]&.any?
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
return reflection_filters
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Fallback to source parsing when reflection is unavailable
|
|
156
177
|
if source
|
|
157
178
|
filters = extract_filters_from_source(source)
|
|
158
179
|
return filters if filters.any?
|
|
159
180
|
end
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
ctrl._process_action_callbacks.filter_map do |cb|
|
|
163
|
-
next if cb.filter.is_a?(Proc) || cb.filter.to_s.start_with?("_")
|
|
164
|
-
next if excluded_filters.include?(cb.filter.to_s)
|
|
165
|
-
|
|
166
|
-
filter = { name: cb.filter.to_s, kind: cb.kind.to_s }
|
|
167
|
-
filter[:only] = cb.instance_variable_get(:@if)&.filter_map { |c| extract_action_condition(c) }&.flatten
|
|
168
|
-
filter[:except] = cb.instance_variable_get(:@unless)&.filter_map { |c| extract_action_condition(c) }&.flatten
|
|
169
|
-
filter.delete(:only) if filter[:only]&.empty?
|
|
170
|
-
filter.delete(:except) if filter[:except]&.empty?
|
|
171
|
-
filter
|
|
172
|
-
end
|
|
181
|
+
|
|
182
|
+
[]
|
|
173
183
|
rescue
|
|
174
184
|
[]
|
|
175
185
|
end
|
|
176
186
|
|
|
187
|
+
# Walk up the controller inheritance chain and collect filter constraints from source files
|
|
188
|
+
def collect_source_constraints(ctrl, current_source = nil)
|
|
189
|
+
constraints = {}
|
|
190
|
+
klass = ctrl
|
|
191
|
+
while klass && klass.name
|
|
192
|
+
break if klass.name.start_with?("ActionController::", "AbstractController::")
|
|
193
|
+
break if klass == ActionController::Base
|
|
194
|
+
break if defined?(ActionController::API) && klass == ActionController::API
|
|
195
|
+
|
|
196
|
+
src = (klass == ctrl) ? (current_source || read_source(klass)) : read_source(klass)
|
|
197
|
+
if src
|
|
198
|
+
extract_filters_from_source(src).each do |sf|
|
|
199
|
+
# First definition wins (most specific controller in chain)
|
|
200
|
+
constraints[sf[:name]] ||= sf
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
klass = klass.superclass
|
|
204
|
+
end
|
|
205
|
+
constraints
|
|
206
|
+
rescue
|
|
207
|
+
{}
|
|
208
|
+
end
|
|
209
|
+
|
|
177
210
|
def extract_filters_from_source(source)
|
|
178
211
|
filters = []
|
|
179
212
|
source.each_line do |line|
|
|
@@ -145,7 +145,7 @@ module RailsAiContext
|
|
|
145
145
|
after_commit after_rollback
|
|
146
146
|
]
|
|
147
147
|
|
|
148
|
-
callback_types.each_with_object({}) do |type, hash|
|
|
148
|
+
result = callback_types.each_with_object({}) do |type, hash|
|
|
149
149
|
callbacks = model.send(:"_#{type}_callbacks").reject do |cb|
|
|
150
150
|
cb.filter.to_s.start_with?(*EXCLUDED_CALLBACKS) || cb.filter.is_a?(Proc)
|
|
151
151
|
end
|
|
@@ -154,6 +154,29 @@ module RailsAiContext
|
|
|
154
154
|
|
|
155
155
|
hash[type.to_s] = callbacks.map { |cb| cb.filter.to_s }
|
|
156
156
|
end
|
|
157
|
+
|
|
158
|
+
# If reflection returned nothing, fall back to source parsing
|
|
159
|
+
return result if result.any?
|
|
160
|
+
extract_callbacks_from_source(model)
|
|
161
|
+
rescue
|
|
162
|
+
extract_callbacks_from_source(model)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Parse callback declarations from model source file
|
|
166
|
+
def extract_callbacks_from_source(model)
|
|
167
|
+
source_path = model_source_path(model)
|
|
168
|
+
return {} unless source_path && File.exist?(source_path)
|
|
169
|
+
|
|
170
|
+
source = File.read(source_path)
|
|
171
|
+
callbacks = {}
|
|
172
|
+
source.each_line do |line|
|
|
173
|
+
if (match = line.match(/\A\s*(before_validation|after_validation|before_save|after_save|before_create|after_create|before_update|after_update|before_destroy|after_destroy|after_commit|after_rollback)\s+:(\w+)/))
|
|
174
|
+
type = match[1]
|
|
175
|
+
method_name = match[2]
|
|
176
|
+
(callbacks[type] ||= []) << method_name
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
callbacks
|
|
157
180
|
rescue
|
|
158
181
|
{}
|
|
159
182
|
end
|
|
@@ -168,13 +191,18 @@ module RailsAiContext
|
|
|
168
191
|
|
|
169
192
|
def framework_concern?(name)
|
|
170
193
|
return true if name.nil?
|
|
171
|
-
return true if %w[Kernel JSON PP Marshal MessagePack].
|
|
194
|
+
return true if %w[Kernel JSON PP Marshal MessagePack].any? { |prefix| name == prefix || name.start_with?("#{prefix}::") }
|
|
195
|
+
return true if name.start_with?("ActiveModel::", "ActiveRecord::", "ActiveSupport::")
|
|
172
196
|
RailsAiContext.configuration.excluded_concerns.any? { |pattern| name.match?(pattern) }
|
|
173
197
|
end
|
|
174
198
|
|
|
175
199
|
def extract_public_class_methods(model)
|
|
200
|
+
scope_names = extract_scopes(model).map(&:to_s)
|
|
176
201
|
(model.methods - ActiveRecord::Base.methods - Object.methods)
|
|
177
|
-
.reject { |m|
|
|
202
|
+
.reject { |m|
|
|
203
|
+
ms = m.to_s
|
|
204
|
+
ms.start_with?("_", "autosave") || scope_names.include?(ms)
|
|
205
|
+
}
|
|
178
206
|
.sort
|
|
179
207
|
.first(30) # Cap to avoid noise
|
|
180
208
|
.map(&:to_s)
|
|
@@ -179,7 +179,14 @@ module RailsAiContext
|
|
|
179
179
|
next if current_table.start_with?("ar_internal_metadata", "schema_migrations")
|
|
180
180
|
tables[current_table] = { columns: [], indexes: [], foreign_keys: [] }
|
|
181
181
|
elsif current_table && (match = line.match(/t\.(\w+)\s+"(\w+)"/))
|
|
182
|
-
|
|
182
|
+
col = { name: match[2], type: match[1] }
|
|
183
|
+
col[:null] = false if line.include?("null: false")
|
|
184
|
+
if (default_match = line.match(/default:\s*("[^"]*"|\{[^}]*\}|\[[^\]]*\]|-?\d+(?:\.\d+)?|true|false)/))
|
|
185
|
+
raw = default_match[1]
|
|
186
|
+
col[:default] = raw.start_with?('"') ? raw[1..-2] : raw
|
|
187
|
+
end
|
|
188
|
+
col[:array] = true if line.include?("array: true")
|
|
189
|
+
tables[current_table][:columns] << col
|
|
183
190
|
elsif current_table && (match = line.match(/t\.index\s+\[([^\]]*)\]/))
|
|
184
191
|
cols = match[1].scan(/["'](\w+)["']/).flatten
|
|
185
192
|
unique = line.include?("unique: true")
|
|
@@ -199,6 +206,17 @@ module RailsAiContext
|
|
|
199
206
|
unique = rest.include?("unique: true")
|
|
200
207
|
idx_name = rest.match(/name:\s*"(\w+)"/)&.send(:[], 1)
|
|
201
208
|
tables[table_name]&.dig(:indexes)&.push({ name: idx_name, columns: cols, unique: unique }.compact) if cols.any?
|
|
209
|
+
elsif (match = line.match(/add_foreign_key\s+"(\w+)",\s+"(\w+)"/))
|
|
210
|
+
from_table = match[1]
|
|
211
|
+
to_table = match[2]
|
|
212
|
+
column_match = line.match(/column:\s*"(\w+)"/)
|
|
213
|
+
column = column_match ? column_match[1] : "#{to_table.singularize}_id"
|
|
214
|
+
pk_match = line.match(/primary_key:\s*"(\w+)"/)
|
|
215
|
+
primary_key = pk_match ? pk_match[1] : "id"
|
|
216
|
+
tables[from_table]&.dig(:foreign_keys)&.push({
|
|
217
|
+
from_table: from_table, to_table: to_table,
|
|
218
|
+
column: column, primary_key: primary_key
|
|
219
|
+
})
|
|
202
220
|
end
|
|
203
221
|
end
|
|
204
222
|
|
|
@@ -155,7 +155,7 @@ module RailsAiContext
|
|
|
155
155
|
|
|
156
156
|
def detect_test_files
|
|
157
157
|
categories = {}
|
|
158
|
-
%w[models controllers requests system services integration features].each do |cat|
|
|
158
|
+
%w[models models/concerns controllers requests system services integration features].each do |cat|
|
|
159
159
|
%w[spec test].each do |base|
|
|
160
160
|
dir = File.join(root, base, cat)
|
|
161
161
|
next unless Dir.exist?(dir)
|
|
@@ -49,12 +49,14 @@ module RailsAiContext
|
|
|
49
49
|
# Flexible matching: "cooks", "CooksController", "cookscontroller" all work
|
|
50
50
|
if controller
|
|
51
51
|
# Accept multiple formats: "CooksController", "cooks", "bonus/crises", "Bonus::CrisesController"
|
|
52
|
-
|
|
52
|
+
# Use underscore for CamelCase→snake_case: "OmniauthCallbacks" → "omniauth_callbacks"
|
|
53
|
+
# Also match on plain downcase to handle "userscontroller" → "users"
|
|
54
|
+
input_snake = controller.gsub("/", "::").underscore.delete_suffix("_controller")
|
|
55
|
+
input_down = controller.downcase.delete_suffix("controller").tr("/", "::")
|
|
53
56
|
key = controllers.keys.find { |k|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
kd.delete_suffix("controller").tr("::", "/") == controller.downcase.delete_suffix("controller")
|
|
57
|
+
key_snake = k.underscore.delete_suffix("_controller")
|
|
58
|
+
key_down = k.downcase.delete_suffix("controller")
|
|
59
|
+
key_snake == input_snake || key_down == input_down
|
|
58
60
|
} || controller
|
|
59
61
|
info = controllers[key]
|
|
60
62
|
unless info
|
|
@@ -129,12 +129,17 @@ module RailsAiContext
|
|
|
129
129
|
end
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
-
# Validations — compress repeated inclusion lists
|
|
132
|
+
# Validations — compress repeated inclusion lists, deduplicate same kind+attribute
|
|
133
133
|
if data[:validations]&.any?
|
|
134
134
|
lines << "" << "## Validations"
|
|
135
|
+
# Deduplicate validations with same kind and attributes (e.g. implicit belongs_to + explicit validates :user, presence)
|
|
136
|
+
seen_validations = Set.new
|
|
135
137
|
# Track seen inclusion arrays to avoid repeating long lists
|
|
136
138
|
seen_inclusions = {}
|
|
137
139
|
data[:validations].each do |v|
|
|
140
|
+
dedup_key = "#{v[:kind]}:#{v[:attributes].sort.join(',')}"
|
|
141
|
+
next if seen_validations.include?(dedup_key)
|
|
142
|
+
seen_validations << dedup_key
|
|
138
143
|
attrs = v[:attributes].join(", ")
|
|
139
144
|
if v[:options]&.any?
|
|
140
145
|
compressed_opts = v[:options].map do |k, val|
|
|
@@ -136,7 +136,8 @@ module RailsAiContext
|
|
|
136
136
|
|
|
137
137
|
columns.each do |col|
|
|
138
138
|
nullable = col.key?(:null) ? (col[:null] ? "yes" : "**NO**") : "yes"
|
|
139
|
-
|
|
139
|
+
col_type = col[:array] ? "#{col[:type]}[]" : col[:type].to_s
|
|
140
|
+
line = "| #{col[:name]} | #{col_type} | #{nullable}"
|
|
140
141
|
line += " | #{col[:default]}" if has_defaults
|
|
141
142
|
lines << "#{line} |"
|
|
142
143
|
end
|
|
@@ -130,7 +130,9 @@ module RailsAiContext
|
|
|
130
130
|
when :model
|
|
131
131
|
[
|
|
132
132
|
"spec/models/#{snake}_spec.rb",
|
|
133
|
-
"test/models/#{snake}_test.rb"
|
|
133
|
+
"test/models/#{snake}_test.rb",
|
|
134
|
+
"spec/models/concerns/#{snake}_spec.rb",
|
|
135
|
+
"test/models/concerns/#{snake}_test.rb"
|
|
134
136
|
]
|
|
135
137
|
when :controller
|
|
136
138
|
[
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "open3"
|
|
4
|
+
require "erb"
|
|
4
5
|
|
|
5
6
|
module RailsAiContext
|
|
6
7
|
module Tools
|
|
@@ -106,28 +107,44 @@ module RailsAiContext
|
|
|
106
107
|
end
|
|
107
108
|
|
|
108
109
|
# Validate ERB by compiling to Ruby source then syntax-checking the result.
|
|
109
|
-
#
|
|
110
|
-
#
|
|
110
|
+
# Catches missing <% end %>, unclosed blocks, and mismatched do/end.
|
|
111
|
+
#
|
|
112
|
+
# Two key pre-processing steps avoid false positives:
|
|
113
|
+
# 1. Convert `<%= ... %>` to `<% ... %>` — prevents the `_buf << ( helper do ).to_s`
|
|
114
|
+
# ambiguity that standard ERB compilation creates for block-form helpers
|
|
115
|
+
# like `<%= link_to ... do %>`, `<%= form_with ... do |f| %>`, etc.
|
|
116
|
+
# 2. Wrap compiled source in a method def — makes `yield` syntactically valid.
|
|
111
117
|
private_class_method def self.validate_erb(full_path)
|
|
112
|
-
|
|
113
|
-
compile_script = "require 'erb'; print ERB.new(File.read(ARGV[0])).src"
|
|
114
|
-
compiled, compile_status = Open3.capture2e("ruby", "-e", compile_script, full_path.to_s)
|
|
118
|
+
return [ false, "file too large" ] if File.size(full_path) > RailsAiContext.configuration.max_file_size
|
|
115
119
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
end
|
|
120
|
+
content = File.binread(full_path).force_encoding("UTF-8")
|
|
121
|
+
|
|
122
|
+
# Pre-process: convert output tags to non-output for syntax-only checking.
|
|
123
|
+
# This is safe because we only check structure (do/end, if/end matching),
|
|
124
|
+
# not whether output is correct.
|
|
125
|
+
processed = content.gsub("<%=", "<%")
|
|
126
|
+
|
|
127
|
+
# Compile ERB to Ruby, wrapped in a method so `yield` is valid syntax.
|
|
128
|
+
# Force UTF-8 on .src output — ERB may return ASCII-8BIT which breaks
|
|
129
|
+
# concatenation with UTF-8 strings when non-ASCII bytes (emoji, etc.) are present.
|
|
130
|
+
erb_src = +ERB.new(processed).src
|
|
131
|
+
erb_src.force_encoding("UTF-8")
|
|
132
|
+
compiled = "# encoding: utf-8\ndef __erb_syntax_check\n#{erb_src}\nend"
|
|
120
133
|
|
|
121
|
-
# Step 2: Syntax-check the compiled Ruby to catch missing end, unclosed blocks, etc.
|
|
122
134
|
check_result, check_status = Open3.capture2e("ruby", "-c", "-", stdin_data: compiled)
|
|
123
135
|
if check_status.success?
|
|
124
136
|
[ true, nil ]
|
|
125
137
|
else
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
138
|
+
# Adjust line numbers: subtract 1 for the wrapper def line
|
|
139
|
+
error = check_result.lines
|
|
140
|
+
.reject { |l| l.strip.empty? || l.include?("Syntax OK") }
|
|
141
|
+
.first(5)
|
|
142
|
+
.map { |l| l.strip.sub(/-:(\d+):/) { "ruby: -:#{$1.to_i - 1}:" } }
|
|
143
|
+
msg = error.any? ? error.join("\n") : "ERB syntax error"
|
|
144
|
+
[ false, msg ]
|
|
130
145
|
end
|
|
146
|
+
rescue => e
|
|
147
|
+
[ false, "ERB check error: #{e.message}" ]
|
|
131
148
|
end
|
|
132
149
|
|
|
133
150
|
# Validate JavaScript syntax via `node -c` (no shell — uses Open3 array form)
|
data/server.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"url": "https://github.com/crisnahine/rails-ai-context",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.15.
|
|
10
|
+
"version": "0.15.6",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "mcpb",
|
|
14
|
-
"identifier": "https://github.com/crisnahine/rails-ai-context/releases/download/v0.15.
|
|
14
|
+
"identifier": "https://github.com/crisnahine/rails-ai-context/releases/download/v0.15.6/rails-ai-context-mcp.mcpb",
|
|
15
15
|
"fileSha256": "dd711a0ad6c4de943ae4da94eaf59a6dc9494b9d57f726e24649ed4e2f156990",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|