rails-ai-context 4.2.2 → 4.2.3
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 +14 -0
- data/CLAUDE.md +1 -1
- data/README.md +2 -2
- data/lib/rails_ai_context/doctor.rb +8 -1
- data/lib/rails_ai_context/introspectors/model_introspector.rb +1 -1
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +2 -1
- data/lib/rails_ai_context/serializers/claude_serializer.rb +2 -1
- data/lib/rails_ai_context/serializers/copilot_instructions_serializer.rb +2 -1
- data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +2 -1
- data/lib/rails_ai_context/serializers/opencode_rules_serializer.rb +2 -1
- data/lib/rails_ai_context/tools/get_context.rb +3 -3
- data/lib/rails_ai_context/tools/query.rb +13 -1
- data/lib/rails_ai_context/tools/search_code.rb +6 -3
- data/lib/rails_ai_context/version.rb +1 -1
- 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: b5a6ff4a0b32e6be5d9109b7c603f3b7ae097cf97439f8b7a219ee374a62469d
|
|
4
|
+
data.tar.gz: f80ea797a13825703b45ced9c9f2b94544f1c00774f00cec683194a19af8d3b9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 370160ebbbcd9bca03eb55171474b017c23235a7211f46ba5df52e64a33fdf3fa470acd38952a422ec7ccca770377d8415e8f0011a101622a15b3db1e5a98ee4
|
|
7
|
+
data.tar.gz: 07df46d61e556d7290ad8768d3ecb6fc7ef3d9498c6fe646791d12c05794bfec258f15085571ae3c09aa8b10ff517b7aa5b372964f5708ad2190675c21bacfc4
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ 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
|
+
## [4.2.3] — 2026-04-01
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Unicode output** — `rails_get_context` ivar cross-check now renders actual Unicode symbols (✓✗⚠) instead of literal `\u2713` escape sequences
|
|
12
|
+
- **Scope name rendering** — all 6 serializers (claude, cursor, copilot, opencode, claude_rules, copilot_instructions) now extract scope names from hash-style scope data instead of dumping raw `{:name=>"active", :body=>"..."}` into output
|
|
13
|
+
- **Scope exclusion** — `ModelIntrospector#extract_public_class_methods` now correctly extracts scope names from hash-style scope data so scopes are properly excluded from the class methods listing
|
|
14
|
+
- **Pending migrations check** — `Doctor#check_pending_migrations` now uses `MigrationContext#pending_migrations` on Rails 7.1+ instead of the deprecated `ActiveRecord::Migrator.new` API (silently returned nil on modern Rails)
|
|
15
|
+
- **SQLite query timeout** — `rails_query` now uses `set_progress_handler` for real statement timeout enforcement on SQLite instead of `busy_timeout` (which only controls lock-wait, not query execution time)
|
|
16
|
+
- **ripgrep caching** — `SearchCode.ripgrep_available?` now caches `false` results, avoiding repeated `which rg` system calls on every search when ripgrep is not installed
|
|
17
|
+
- **Controller action extraction** — `SearchCode#extract_controller_actions_from_matches` now correctly captures RESTful action names instead of always appending `nil` (was using `match?` which doesn't set `$1`, plus overly broad `[a-z_]+` regex)
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Test count: 1003 → 1016
|
|
21
|
+
|
|
8
22
|
## [4.2.2] — 2026-04-01
|
|
9
23
|
|
|
10
24
|
### Fixed
|
data/CLAUDE.md
CHANGED
data/README.md
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
[](https://registry.modelcontextprotocol.io)
|
|
9
9
|
[](https://github.com/crisnahine/rails-ai-context)
|
|
10
10
|
[](https://github.com/crisnahine/rails-ai-context)
|
|
11
|
-
[](https://github.com/crisnahine/rails-ai-context/actions)
|
|
12
12
|
[](LICENSE)
|
|
13
13
|
|
|
14
14
|
**Works with:** Claude Code • Cursor • GitHub Copilot • OpenCode • Any terminal
|
|
15
15
|
|
|
16
|
-
> Built by a Rails developer with 10+ years of production experience. AI assisted — the same way it assists me shipping features at work. I designed the architecture, made every decision, reviewed every line, and wrote
|
|
16
|
+
> Built by a Rails developer with 10+ years of production experience. AI assisted — the same way it assists me shipping features at work. I designed the architecture, made every decision, reviewed every line, and wrote 1016 tests. This gem exists because I understand Rails deeply enough to know exactly what AI agents get wrong and what context they need to get it right.
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
gem "rails-ai-context", group: :development
|
|
@@ -64,7 +64,14 @@ module RailsAiContext
|
|
|
64
64
|
def check_pending_migrations
|
|
65
65
|
return nil unless defined?(ActiveRecord::Base) && ActiveRecord::Base.connected?
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
context = ActiveRecord::MigrationContext.new(File.join(app.root, "db/migrate"))
|
|
68
|
+
pending = if context.respond_to?(:pending_migrations)
|
|
69
|
+
# Rails 7.1+
|
|
70
|
+
context.pending_migrations
|
|
71
|
+
else
|
|
72
|
+
# Rails 7.0 and earlier
|
|
73
|
+
ActiveRecord::Migrator.new(:up, context.migrations).pending_migrations
|
|
74
|
+
end
|
|
68
75
|
if pending.empty?
|
|
69
76
|
Check.new(name: "Pending migrations", status: :pass, message: "No pending migrations", fix: nil)
|
|
70
77
|
else
|
|
@@ -236,7 +236,7 @@ module RailsAiContext
|
|
|
236
236
|
].to_set.freeze
|
|
237
237
|
|
|
238
238
|
def extract_public_class_methods(model)
|
|
239
|
-
scope_names = extract_scopes(model).map(
|
|
239
|
+
scope_names = extract_scopes(model).map { |s| s.is_a?(Hash) ? s[:name].to_s : s.to_s }
|
|
240
240
|
|
|
241
241
|
# Prioritize methods defined in the model's own source file
|
|
242
242
|
source_methods = extract_source_class_methods(model)
|
|
@@ -220,7 +220,8 @@ module RailsAiContext
|
|
|
220
220
|
|
|
221
221
|
# Include scopes so agents know available query methods
|
|
222
222
|
scopes = data[:scopes] || []
|
|
223
|
-
|
|
223
|
+
scope_names = scopes.map { |s| s.is_a?(Hash) ? s[:name] : s }
|
|
224
|
+
lines << " scopes: #{scope_names.join(', ')}" if scopes.any?
|
|
224
225
|
|
|
225
226
|
# Instance methods — introspector already prioritizes source-defined and filters Devise
|
|
226
227
|
methods = (data[:instance_methods] || []).reject { |m| m.end_with?("=") }.first(20)
|
|
@@ -125,7 +125,8 @@ module RailsAiContext
|
|
|
125
125
|
constants = (data[:constants] || [])
|
|
126
126
|
if scopes.any? || constants.any?
|
|
127
127
|
extras = []
|
|
128
|
-
|
|
128
|
+
scope_names = scopes.map { |s| s.is_a?(Hash) ? s[:name] : s }
|
|
129
|
+
extras << "scopes: #{scope_names.join(', ')}" if scopes.any?
|
|
129
130
|
constants.each { |c| extras << "#{c[:name]}: #{c[:values].join(', ')}" }
|
|
130
131
|
lines << " #{extras.join(' | ')}"
|
|
131
132
|
end
|
|
@@ -149,7 +149,8 @@ module RailsAiContext
|
|
|
149
149
|
constants = (data[:constants] || [])
|
|
150
150
|
if scopes.any? || constants.any?
|
|
151
151
|
extras = []
|
|
152
|
-
|
|
152
|
+
scope_names = scopes.map { |s| s.is_a?(Hash) ? s[:name] : s }
|
|
153
|
+
extras << "scopes: #{scope_names.join(', ')}" if scopes.any?
|
|
153
154
|
constants.each { |c| extras << "#{c[:name]}: #{c[:values].join(', ')}" }
|
|
154
155
|
lines << " #{extras.join(' | ')}"
|
|
155
156
|
end
|
|
@@ -162,7 +162,8 @@ module RailsAiContext
|
|
|
162
162
|
constants = (data[:constants] || [])
|
|
163
163
|
if scopes.any? || constants.any?
|
|
164
164
|
extras = []
|
|
165
|
-
|
|
165
|
+
scope_names = scopes.map { |s| s.is_a?(Hash) ? s[:name] : s }
|
|
166
|
+
extras << "scopes: #{scope_names.join(', ')}" if scopes.any?
|
|
166
167
|
constants.each { |c| extras << "#{c[:name]}: #{c[:values].join(', ')}" }
|
|
167
168
|
lines << " #{extras.join(' | ')}"
|
|
168
169
|
end
|
|
@@ -72,7 +72,8 @@ module RailsAiContext
|
|
|
72
72
|
constants = (data[:constants] || [])
|
|
73
73
|
if scopes.any? || constants.any?
|
|
74
74
|
extras = []
|
|
75
|
-
|
|
75
|
+
scope_names = scopes.map { |s| s.is_a?(Hash) ? s[:name] : s }
|
|
76
|
+
extras << "scopes: #{scope_names.join(', ')}" if scopes.any?
|
|
76
77
|
constants.each { |c| extras << "#{c[:name]}: #{c[:values].join(', ')}" }
|
|
77
78
|
lines << " #{extras.join(' | ')}"
|
|
78
79
|
end
|
|
@@ -151,12 +151,12 @@ module RailsAiContext
|
|
|
151
151
|
in_ctrl = ctrl_ivars.include?(ivar)
|
|
152
152
|
in_view = view_ivars.include?(ivar)
|
|
153
153
|
if in_ctrl && in_view
|
|
154
|
-
lines << "-
|
|
154
|
+
lines << "- \u2713 @#{ivar} — set in controller, used in view"
|
|
155
155
|
elsif in_view && !in_ctrl
|
|
156
|
-
lines << "-
|
|
156
|
+
lines << "- \u2717 @#{ivar} — used in view but NOT set in controller"
|
|
157
157
|
mismatches = true
|
|
158
158
|
elsif in_ctrl && !in_view
|
|
159
|
-
lines << "-
|
|
159
|
+
lines << "- \u26A0 @#{ivar} — set in controller but not used in view"
|
|
160
160
|
end
|
|
161
161
|
end
|
|
162
162
|
|
|
@@ -175,12 +175,24 @@ module RailsAiContext
|
|
|
175
175
|
|
|
176
176
|
private_class_method def self.execute_sqlite(conn, sql, timeout)
|
|
177
177
|
raw = conn.raw_connection
|
|
178
|
-
raw.busy_timeout = (timeout * 1000).to_i
|
|
179
178
|
result = nil
|
|
180
179
|
begin
|
|
181
180
|
conn.execute("PRAGMA query_only = ON")
|
|
181
|
+
# SQLite has no native statement timeout. Use a progress handler
|
|
182
|
+
# to abort queries that run too long (checked every 1000 VM steps).
|
|
183
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
184
|
+
if raw.respond_to?(:set_progress_handler)
|
|
185
|
+
raw.set_progress_handler(1000) do
|
|
186
|
+
if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
|
|
187
|
+
1 # non-zero = abort
|
|
188
|
+
else
|
|
189
|
+
0
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
182
193
|
result = conn.select_all(sql)
|
|
183
194
|
ensure
|
|
195
|
+
raw.set_progress_handler(0, nil) if raw.respond_to?(:set_progress_handler)
|
|
184
196
|
conn.execute("PRAGMA query_only = OFF")
|
|
185
197
|
end
|
|
186
198
|
result
|
|
@@ -180,7 +180,8 @@ module RailsAiContext
|
|
|
180
180
|
end
|
|
181
181
|
|
|
182
182
|
private_class_method def self.ripgrep_available?
|
|
183
|
-
@rg_available
|
|
183
|
+
return @rg_available unless @rg_available.nil?
|
|
184
|
+
@rg_available = system("which rg > /dev/null 2>&1")
|
|
184
185
|
end
|
|
185
186
|
|
|
186
187
|
private_class_method def self.search_with_ripgrep(pattern, search_path, file_type, max_results, root, ctx_lines = 0, exclude_tests: false)
|
|
@@ -462,8 +463,10 @@ module RailsAiContext
|
|
|
462
463
|
private_class_method def self.extract_controller_actions_from_matches(matches)
|
|
463
464
|
actions = []
|
|
464
465
|
matches.each do |m|
|
|
465
|
-
#
|
|
466
|
-
|
|
466
|
+
# Match standard RESTful action names from the content
|
|
467
|
+
if (match = m[:content].match(/\b(index|show|new|create|edit|update|destroy)\b/))
|
|
468
|
+
actions << match[1]
|
|
469
|
+
end
|
|
467
470
|
end
|
|
468
471
|
actions.uniq.first(3)
|
|
469
472
|
end
|