rails-ai-context 0.13.1 → 0.14.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 +4 -4
- data/CLAUDE.md +1 -1
- data/README.md +3 -3
- data/demo_script.sh +1 -1
- data/docs/GUIDE.md +3 -3
- data/lib/rails_ai_context/configuration.rb +1 -1
- data/lib/rails_ai_context/introspectors/route_introspector.rb +3 -0
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +10 -5
- data/lib/rails_ai_context/introspectors/stimulus_introspector.rb +17 -2
- data/lib/rails_ai_context/tools/get_model_details.rb +8 -3
- data/lib/rails_ai_context/tools/get_routes.rb +16 -2
- data/lib/rails_ai_context/tools/get_stimulus.rb +7 -3
- data/lib/rails_ai_context/tools/search_code.rb +10 -4
- 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: f5b745a3b7eec70c71340022f60a7fadacd80cae84f5d4586936b78633677f0e
|
|
4
|
+
data.tar.gz: 5619c0302827cb96ef33175264e83f36fa2f055925e6517316b379d24599fff7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 810c890604f5d4b78d56e4580254279b3b741d15fd0dbe2419fbf661d2ee47dd70b678cf962aa65244e025e70633dcc5cceee30319c27edf60c04d2a6127ce39
|
|
7
|
+
data.tar.gz: 4f6ed5c100bcf1907b5c38ac9043669ab4c4d833a30c65613d25f0a125de1909f054da48ce3b3fd87f427e1f5e7ff6f8fb422cd812016ed57b85218d592c5f52
|
data/CLAUDE.md
CHANGED
|
@@ -32,7 +32,7 @@ structure to AI assistants via the Model Context Protocol (MCP).
|
|
|
32
32
|
6. **Diff-aware** — context regeneration skips unchanged files
|
|
33
33
|
7. **Per-assistant serializers** — each AI tool gets tailored output format
|
|
34
34
|
8. **Zeitwerk autoloading** — files loaded on-demand, not all upfront
|
|
35
|
-
9. **Introspector presets** — `:standard` (
|
|
35
|
+
9. **Introspector presets** — `:standard` (13 core) default, `:full` (28) for power users
|
|
36
36
|
10. **MCP auto-discovery** — `.mcp.json` generated by install generator
|
|
37
37
|
11. **Compact by default** — context files ≤150 lines, MCP tools use `detail` parameter (summary/standard/full)
|
|
38
38
|
12. **Per-tool split rules** — `.claude/rules/`, `.cursor/rules/`, `.windsurf/rules/`, `.github/instructions/`
|
data/README.md
CHANGED
|
@@ -203,7 +203,7 @@ Root files (CLAUDE.md, AGENTS.md, etc.) use **section markers** — your custom
|
|
|
203
203
|
| **DevOps** | Puma, Procfile, Docker, deployment tools, asset pipeline |
|
|
204
204
|
| **Architecture** | Service objects, STI, polymorphism, state machines, multi-tenancy, engines |
|
|
205
205
|
|
|
206
|
-
29 introspectors total. The `:standard` preset runs
|
|
206
|
+
29 introspectors total. The `:standard` preset runs 13 core ones by default; use `:full` for 28 (`database_stats` is opt-in, PostgreSQL only).
|
|
207
207
|
|
|
208
208
|
---
|
|
209
209
|
|
|
@@ -257,7 +257,7 @@ end
|
|
|
257
257
|
```ruby
|
|
258
258
|
# config/initializers/rails_ai_context.rb
|
|
259
259
|
RailsAiContext.configure do |config|
|
|
260
|
-
# Presets: :standard (
|
|
260
|
+
# Presets: :standard (13 introspectors, default) or :full (all 28)
|
|
261
261
|
config.preset = :standard
|
|
262
262
|
|
|
263
263
|
# Cherry-pick on top of a preset
|
|
@@ -291,7 +291,7 @@ end
|
|
|
291
291
|
| Option | Default | Description |
|
|
292
292
|
|--------|---------|-------------|
|
|
293
293
|
| `preset` | `:standard` | Introspector preset (`:standard` or `:full`) |
|
|
294
|
-
| `introspectors` |
|
|
294
|
+
| `introspectors` | 13 core | Array of introspector symbols |
|
|
295
295
|
| `context_mode` | `:compact` | `:compact` (≤150 lines) or `:full` (dump everything) |
|
|
296
296
|
| `claude_max_lines` | `150` | Max lines for CLAUDE.md in compact mode |
|
|
297
297
|
| `max_tool_response_chars` | `120_000` | Safety cap for MCP tool responses |
|
data/demo_script.sh
CHANGED
data/docs/GUIDE.md
CHANGED
|
@@ -579,7 +579,7 @@ Both transports are **read-only** — they expose the same 13 tools and never mo
|
|
|
579
579
|
RailsAiContext.configure do |config|
|
|
580
580
|
# --- Introspectors ---
|
|
581
581
|
|
|
582
|
-
# Presets: :standard (
|
|
582
|
+
# Presets: :standard (13 core, default) or :full (all 28)
|
|
583
583
|
config.preset = :standard
|
|
584
584
|
|
|
585
585
|
# Cherry-pick on top of a preset
|
|
@@ -636,7 +636,7 @@ end
|
|
|
636
636
|
| Option | Type | Default | Description |
|
|
637
637
|
|--------|------|---------|-------------|
|
|
638
638
|
| `preset` | Symbol | `:standard` | Introspector preset (`:standard` or `:full`) |
|
|
639
|
-
| `introspectors` | Array |
|
|
639
|
+
| `introspectors` | Array | 13 core symbols | Which introspectors to run |
|
|
640
640
|
| `context_mode` | Symbol | `:compact` | `:compact` or `:full` |
|
|
641
641
|
| `claude_max_lines` | Integer | `150` | Max lines for CLAUDE.md in compact mode |
|
|
642
642
|
| `max_tool_response_chars` | Integer | `120_000` | Safety cap for MCP tool responses |
|
|
@@ -673,7 +673,7 @@ All split rules include an app overview file, so no context is lost when root fi
|
|
|
673
673
|
|
|
674
674
|
## Introspectors — Full List
|
|
675
675
|
|
|
676
|
-
### Standard preset (
|
|
676
|
+
### Standard preset (13 introspectors)
|
|
677
677
|
|
|
678
678
|
These run by default. Fast and cover core Rails structure.
|
|
679
679
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module RailsAiContext
|
|
4
4
|
class Configuration
|
|
5
5
|
PRESETS = {
|
|
6
|
-
standard: %i[schema models routes jobs gems conventions controllers tests migrations stimulus view_templates design_tokens],
|
|
6
|
+
standard: %i[schema models routes jobs gems conventions controllers tests migrations stimulus view_templates design_tokens config],
|
|
7
7
|
full: %i[schema models routes jobs gems conventions stimulus controllers views view_templates design_tokens turbo
|
|
8
8
|
i18n config active_storage action_text auth api tests rake_tasks assets
|
|
9
9
|
devops action_mailbox migrations seeds middleware engines multi_database]
|
|
@@ -26,6 +26,9 @@ module RailsAiContext
|
|
|
26
26
|
private
|
|
27
27
|
|
|
28
28
|
def extract_routes
|
|
29
|
+
# Force Rails to reload routes if routes.rb has changed
|
|
30
|
+
app.routes_reloader&.execute_if_updated rescue nil
|
|
31
|
+
|
|
29
32
|
app.routes.routes.filter_map do |route|
|
|
30
33
|
next if route.respond_to?(:internal?) && route.internal?
|
|
31
34
|
next if route.defaults[:controller].blank?
|
|
@@ -138,8 +138,13 @@ module RailsAiContext
|
|
|
138
138
|
tables[current_table] = { columns: [], indexes: [], foreign_keys: [] }
|
|
139
139
|
elsif current_table && (match = line.match(/t\.(\w+)\s+"(\w+)"/))
|
|
140
140
|
tables[current_table][:columns] << { name: match[2], type: match[1] }
|
|
141
|
-
elsif (match = line.match(/add_index\s+"(\w+)",\s
|
|
142
|
-
|
|
141
|
+
elsif (match = line.match(/add_index\s+"(\w+)",\s+(.+)/))
|
|
142
|
+
table_name = match[1]
|
|
143
|
+
rest = match[2]
|
|
144
|
+
cols = rest.scan(/(?::|\")(\w+)/).flatten
|
|
145
|
+
unique = rest.include?("unique: true")
|
|
146
|
+
idx_name = rest.match(/name:\s*"(\w+)"/)&.send(:[], 1)
|
|
147
|
+
tables[table_name]&.dig(:indexes)&.push({ name: idx_name, columns: cols, unique: unique }.compact) if cols.any?
|
|
143
148
|
end
|
|
144
149
|
end
|
|
145
150
|
|
|
@@ -164,9 +169,9 @@ module RailsAiContext
|
|
|
164
169
|
end
|
|
165
170
|
|
|
166
171
|
# Match CREATE INDEX / CREATE UNIQUE INDEX
|
|
167
|
-
content.scan(/CREATE (
|
|
168
|
-
col_list = cols.scan(/\w+/)
|
|
169
|
-
tables[table]&.dig(:indexes)&.push({ name: idx_name, columns: col_list })
|
|
172
|
+
content.scan(/CREATE (UNIQUE )?INDEX (\w+) ON (?:public\.)?(\w+).*?\((.+?)\)/m) do |unique, idx_name, table, cols|
|
|
173
|
+
col_list = cols.scan(/\w+/)
|
|
174
|
+
tables[table]&.dig(:indexes)&.push({ name: idx_name, columns: col_list, unique: !!unique })
|
|
170
175
|
end
|
|
171
176
|
|
|
172
177
|
# Match ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY (handles multi-line)
|
|
@@ -52,10 +52,25 @@ module RailsAiContext
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def extract_values(content)
|
|
55
|
-
match = content.match(/static\s+values\s*=\s*\{(
|
|
55
|
+
match = content.match(/static\s+values\s*=\s*\{(.*?)\}/m)
|
|
56
56
|
return {} unless match
|
|
57
57
|
|
|
58
|
-
match[1]
|
|
58
|
+
body = match[1]
|
|
59
|
+
values = {}
|
|
60
|
+
|
|
61
|
+
# Handle complex format: name: { type: Type, default: val }
|
|
62
|
+
body.scan(/(\w+)\s*:\s*\{([^}]+)\}/).each do |name, inner|
|
|
63
|
+
type = inner.match(/type:\s*(\w+)/)&.send(:[], 1) || "Object"
|
|
64
|
+
default = inner.match(/default:\s*(\S+)/)&.send(:[], 1)&.chomp(",")
|
|
65
|
+
values[name] = default ? "#{type} (default: #{default})" : type
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Handle simple format: name: Type (single line or multi-line)
|
|
69
|
+
body.scan(/(\w+)\s*:\s*([A-Z]\w+)/).each do |name, type|
|
|
70
|
+
values[name] ||= type
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
values
|
|
59
74
|
end
|
|
60
75
|
|
|
61
76
|
def extract_actions(content)
|
|
@@ -135,10 +135,15 @@ module RailsAiContext
|
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
137
|
|
|
138
|
-
# Concerns
|
|
138
|
+
# Concerns — filter out internal Rails modules
|
|
139
139
|
if data[:concerns]&.any?
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
app_concerns = data[:concerns].reject do |c|
|
|
141
|
+
c.match?(/\A(ActiveRecord|ActiveModel|ActiveSupport|ActionText|ActionMailbox|ActiveStorage|GeneratedAssociationMethods|Kernel|JSON|PP|Marshal|MessagePack|GeneratedRelationMethods)/)
|
|
142
|
+
end
|
|
143
|
+
if app_concerns.any?
|
|
144
|
+
lines << "" << "## Concerns"
|
|
145
|
+
lines << app_concerns.map { |c| "- #{c}" }.join("\n")
|
|
146
|
+
end
|
|
142
147
|
end
|
|
143
148
|
|
|
144
149
|
# Key instance methods — include signatures from source if available
|
|
@@ -24,13 +24,21 @@ module RailsAiContext
|
|
|
24
24
|
offset: {
|
|
25
25
|
type: "integer",
|
|
26
26
|
description: "Skip routes for pagination. Default: 0."
|
|
27
|
+
},
|
|
28
|
+
app_only: {
|
|
29
|
+
type: "boolean",
|
|
30
|
+
description: "Filter out internal Rails routes (Active Storage, Action Mailbox, Conductor, etc.). Default: true."
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
)
|
|
30
34
|
|
|
35
|
+
INTERNAL_PREFIXES = %w[
|
|
36
|
+
action_mailbox/ active_storage/ rails/ conductor/
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
31
39
|
annotations(read_only_hint: true, destructive_hint: false, idempotent_hint: true, open_world_hint: false)
|
|
32
40
|
|
|
33
|
-
def self.call(controller: nil, detail: "standard", limit: nil, offset: 0, server_context: nil)
|
|
41
|
+
def self.call(controller: nil, detail: "standard", limit: nil, offset: 0, app_only: true, server_context: nil)
|
|
34
42
|
routes = cached_context[:routes]
|
|
35
43
|
return text_response("Route introspection not available. Add :routes to introspectors.") unless routes
|
|
36
44
|
return text_response("Route introspection failed: #{routes[:error]}") if routes[:error]
|
|
@@ -38,6 +46,11 @@ module RailsAiContext
|
|
|
38
46
|
by_controller = routes[:by_controller] || {}
|
|
39
47
|
offset = [ offset.to_i, 0 ].max
|
|
40
48
|
|
|
49
|
+
# Filter out internal Rails routes by default
|
|
50
|
+
if app_only
|
|
51
|
+
by_controller = by_controller.reject { |k, _| INTERNAL_PREFIXES.any? { |p| k.downcase.start_with?(p) } }
|
|
52
|
+
end
|
|
53
|
+
|
|
41
54
|
# Filter by controller
|
|
42
55
|
if controller
|
|
43
56
|
filtered = by_controller.select { |k, _| k.downcase.include?(controller.downcase) }
|
|
@@ -70,7 +83,8 @@ module RailsAiContext
|
|
|
70
83
|
count += 1
|
|
71
84
|
next if count <= offset
|
|
72
85
|
break if count > offset + limit
|
|
73
|
-
|
|
86
|
+
name_part = r[:name] ? " `#{r[:name]}`" : ""
|
|
87
|
+
ctrl_lines << "- `#{r[:verb]}` `#{r[:path]}` → #{r[:action]}#{name_part}"
|
|
74
88
|
end
|
|
75
89
|
if ctrl_lines.any?
|
|
76
90
|
lines << "## #{ctrl}"
|
|
@@ -30,10 +30,12 @@ module RailsAiContext
|
|
|
30
30
|
controllers = data[:controllers] || []
|
|
31
31
|
return text_response("No Stimulus controllers found.") if controllers.empty?
|
|
32
32
|
|
|
33
|
-
# Specific controller
|
|
33
|
+
# Specific controller — accepts both dash and underscore naming
|
|
34
|
+
# (HTML uses data-controller="weekly-chart", file is weekly_chart_controller.js)
|
|
34
35
|
if controller
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
normalized = controller.downcase.tr("-", "_")
|
|
37
|
+
ctrl = controllers.find { |c| c[:name]&.downcase&.tr("-", "_") == normalized }
|
|
38
|
+
return text_response("Controller '#{controller}' not found. Available: #{controllers.map { |c| c[:name] }.sort.join(', ')}\n\n_Note: use dashes in HTML (`data-controller=\"my-name\"`) but underscores for lookup (`controller:\"my_name\"`)._") unless ctrl
|
|
37
39
|
return text_response(format_controller_full(ctrl))
|
|
38
40
|
end
|
|
39
41
|
|
|
@@ -78,6 +80,8 @@ module RailsAiContext
|
|
|
78
80
|
lines << "- **Outlets:** #{ctrl[:outlets].join(', ')}" if ctrl[:outlets]&.any?
|
|
79
81
|
lines << "- **Classes:** #{ctrl[:classes].join(', ')}" if ctrl[:classes]&.any?
|
|
80
82
|
lines << "- **File:** #{ctrl[:file]}" if ctrl[:file]
|
|
83
|
+
html_name = ctrl[:name]&.tr("_", "-")
|
|
84
|
+
lines << "" << "_HTML: `data-controller=\"#{html_name}\"`_" if html_name
|
|
81
85
|
lines.join("\n")
|
|
82
86
|
end
|
|
83
87
|
end
|
|
@@ -27,6 +27,10 @@ module RailsAiContext
|
|
|
27
27
|
max_results: {
|
|
28
28
|
type: "integer",
|
|
29
29
|
description: "Maximum number of results. Default: 30, max: 100."
|
|
30
|
+
},
|
|
31
|
+
context_lines: {
|
|
32
|
+
type: "integer",
|
|
33
|
+
description: "Lines of context before and after each match (like grep -C). Default: 0, max: 5."
|
|
30
34
|
}
|
|
31
35
|
},
|
|
32
36
|
required: [ "pattern" ]
|
|
@@ -34,7 +38,7 @@ module RailsAiContext
|
|
|
34
38
|
|
|
35
39
|
annotations(read_only_hint: true, destructive_hint: false, idempotent_hint: true, open_world_hint: false)
|
|
36
40
|
|
|
37
|
-
def self.call(pattern:, path: nil, file_type: nil, max_results: 30, server_context: nil)
|
|
41
|
+
def self.call(pattern:, path: nil, file_type: nil, max_results: 30, context_lines: 0, server_context: nil)
|
|
38
42
|
root = Rails.root.to_s
|
|
39
43
|
|
|
40
44
|
# Validate file_type to prevent injection
|
|
@@ -42,9 +46,10 @@ module RailsAiContext
|
|
|
42
46
|
return text_response("Invalid file_type: must contain only alphanumeric characters.")
|
|
43
47
|
end
|
|
44
48
|
|
|
45
|
-
# Cap max_results
|
|
49
|
+
# Cap max_results and context_lines
|
|
46
50
|
max_results = [ max_results.to_i, MAX_RESULTS_CAP ].min
|
|
47
51
|
max_results = 30 if max_results < 1
|
|
52
|
+
context_lines = [ [ context_lines.to_i, 0 ].max, 5 ].min
|
|
48
53
|
|
|
49
54
|
search_path = path ? File.join(root, path) : root
|
|
50
55
|
|
|
@@ -64,7 +69,7 @@ module RailsAiContext
|
|
|
64
69
|
end
|
|
65
70
|
|
|
66
71
|
results = if ripgrep_available?
|
|
67
|
-
search_with_ripgrep(pattern, search_path, file_type, max_results, root)
|
|
72
|
+
search_with_ripgrep(pattern, search_path, file_type, max_results, root, context_lines)
|
|
68
73
|
else
|
|
69
74
|
search_with_ruby(pattern, search_path, file_type, max_results, root)
|
|
70
75
|
end
|
|
@@ -84,8 +89,9 @@ module RailsAiContext
|
|
|
84
89
|
@rg_available ||= system("which rg > /dev/null 2>&1")
|
|
85
90
|
end
|
|
86
91
|
|
|
87
|
-
private_class_method def self.search_with_ripgrep(pattern, search_path, file_type, max_results, root)
|
|
92
|
+
private_class_method def self.search_with_ripgrep(pattern, search_path, file_type, max_results, root, ctx_lines = 0)
|
|
88
93
|
cmd = [ "rg", "--no-heading", "--line-number", "--max-count", max_results.to_s ]
|
|
94
|
+
cmd.push("-C", ctx_lines.to_s) if ctx_lines > 0
|
|
89
95
|
|
|
90
96
|
RailsAiContext.configuration.excluded_paths.each do |p|
|
|
91
97
|
cmd << "--glob=!#{p}"
|
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.
|
|
10
|
+
"version": "0.14.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "mcpb",
|
|
14
|
-
"identifier": "https://github.com/crisnahine/rails-ai-context/releases/download/v0.
|
|
14
|
+
"identifier": "https://github.com/crisnahine/rails-ai-context/releases/download/v0.14.0/rails-ai-context-mcp.mcpb",
|
|
15
15
|
"fileSha256": "dd711a0ad6c4de943ae4da94eaf59a6dc9494b9d57f726e24649ed4e2f156990",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|