rails-ai-context 2.0.3 → 2.0.4
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 +11 -0
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +8 -4
- data/lib/rails_ai_context/serializers/claude_serializer.rb +5 -4
- data/lib/rails_ai_context/serializers/copilot_instructions_serializer.rb +7 -1
- data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +7 -1
- data/lib/rails_ai_context/serializers/opencode_serializer.rb +5 -4
- data/lib/rails_ai_context/serializers/windsurf_rules_serializer.rb +7 -1
- data/lib/rails_ai_context/tools/analyze_feature.rb +49 -3
- data/lib/rails_ai_context/tools/get_concern.rb +62 -5
- data/lib/rails_ai_context/tools/get_schema.rb +8 -0
- 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: 70a0eb825c34329a1f0bc9d387a4a2bfd2a42b56717305dcaa5df4c0712264f7
|
|
4
|
+
data.tar.gz: 5a2e229f4bbcaf39d9eebe3e3851444ceed0860b79ad4bcccf11fdba9487ac0a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eca131443eb443b24a7681233f5e5adcef9a2c2fb3f4ee6d4e665c795470bed1e0bfdfa92f4f4861136f3e7c2b34210972273b2d0b976f3ffb7fa88834ed31b7
|
|
7
|
+
data.tar.gz: b2e96f95e25b8e9cf9fb140446969fc7a027ff22e210321c3ade42da59aa7c6ed702ceabc0c8be62e8a56c2fa25230dcb460bfd3182716c3a1ef1cc300b836bb
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ 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
|
+
## [2.0.4] - 2026-03-25
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Orphaned table detection** — `get_schema` standard mode flags tables with no ActiveRecord model: "⚠ Orphaned tables: content_calendars, cook_comments"
|
|
13
|
+
- **Concern method source code** — `get_concern(name:"X", detail:"full")` shows method bodies inline, same pattern as callbacks tool.
|
|
14
|
+
- **analyze_feature: inherited filters** — shows `authenticate_user! (from ApplicationController)` in controller section.
|
|
15
|
+
- **analyze_feature: code-ready route helpers** — `cook_path(@record)`, `cooks_path` inline with routes.
|
|
16
|
+
- **analyze_feature: service test gaps** — checks services for missing test files, not just models/controllers/jobs.
|
|
17
|
+
- **All 6 serializers updated** — Claude, Cursor, Copilot, Windsurf, OpenCode all document trace mode, concern source, orphaned tables, inherited filters.
|
|
18
|
+
|
|
8
19
|
## [2.0.3] - 2026-03-25
|
|
9
20
|
|
|
10
21
|
### Added
|
|
@@ -344,8 +344,8 @@ module RailsAiContext
|
|
|
344
344
|
"",
|
|
345
345
|
"## Tools (25)",
|
|
346
346
|
"",
|
|
347
|
-
"**rails_get_schema** — database tables, columns, indexes, foreign keys",
|
|
348
|
-
"- `rails_get_schema(detail:\"summary\")` — all tables with column counts",
|
|
347
|
+
"**rails_get_schema** — database tables, columns, indexes, foreign keys, orphaned table detection",
|
|
348
|
+
"- `rails_get_schema(detail:\"summary\")` — all tables with column counts + orphaned tables (tables with no ActiveRecord model)",
|
|
349
349
|
"- `rails_get_schema(table:\"users\")` — full detail for one table",
|
|
350
350
|
"",
|
|
351
351
|
"**rails_get_model_details** — associations, validations, scopes, enums, callbacks",
|
|
@@ -381,7 +381,7 @@ module RailsAiContext
|
|
|
381
381
|
"- `rails_validate(files:[\"app/models/cook.rb\"])` — checks Ruby, ERB, JS syntax in one call",
|
|
382
382
|
"",
|
|
383
383
|
"**rails_analyze_feature** — combined schema + models + controllers + routes for a feature area",
|
|
384
|
-
"- `rails_analyze_feature(feature:\"authentication\")` — one call gets everything related to a feature",
|
|
384
|
+
"- `rails_analyze_feature(feature:\"authentication\")` — one call gets everything related to a feature, including inherited filters from parent controllers and code-ready route helpers",
|
|
385
385
|
"",
|
|
386
386
|
"**rails_get_design_system** — color palette, component patterns, canonical page examples",
|
|
387
387
|
"- `rails_get_design_system(detail:\"standard\")` — colors + components + real HTML examples + design rules",
|
|
@@ -390,13 +390,17 @@ module RailsAiContext
|
|
|
390
390
|
"**rails_get_config** — cache store, session, timezone, middleware, initializers",
|
|
391
391
|
"**rails_get_gems** — notable gems categorized by function",
|
|
392
392
|
"**rails_get_conventions** — architecture patterns, directory structure",
|
|
393
|
-
"**rails_search_code** — regex search
|
|
393
|
+
"**rails_search_code** — regex search with trace mode for deep call-chain analysis",
|
|
394
|
+
"- `rails_search_code(pattern:\"regex\", file_type:\"rb\")` — basic regex search across files",
|
|
395
|
+
"- `rails_search_code(pattern:\"can_cook?\", match_type:\"trace\")` — trace mode: definition + class context + source + siblings + callers with route chain + test coverage",
|
|
396
|
+
"- `rails_search_code(pattern:\"cook\", exclude_tests: true)` — search app code only, skip test files",
|
|
394
397
|
"",
|
|
395
398
|
"**rails_security_scan** — Brakeman security analysis",
|
|
396
399
|
"- `rails_security_scan` — run security scan, returns warnings by confidence",
|
|
397
400
|
"",
|
|
398
401
|
"**rails_get_concern** — concern methods and includers",
|
|
399
402
|
"- `rails_get_concern(name:\"Searchable\")` — methods, included modules, includers",
|
|
403
|
+
"- `rails_get_concern(name:\"Searchable\", detail:\"full\")` — + method source code",
|
|
400
404
|
"",
|
|
401
405
|
"**rails_get_callbacks** — model callbacks in execution order",
|
|
402
406
|
"- `rails_get_callbacks(model:\"User\")` — before/after/around callbacks by type",
|
|
@@ -214,11 +214,11 @@ module RailsAiContext
|
|
|
214
214
|
"### Quick Reference",
|
|
215
215
|
"| Need | Use this MCP tool | Do NOT use |",
|
|
216
216
|
"|------|-------------------|------------|",
|
|
217
|
-
"| Column types | `rails_get_schema(table:\"x\")` | Read db/schema.rb |",
|
|
217
|
+
"| Column types | `rails_get_schema(table:\"x\")` — includes orphaned table warnings | Read db/schema.rb |",
|
|
218
218
|
"| Model associations | `rails_get_model_details(model:\"X\")` | Read app/models/x.rb |",
|
|
219
219
|
"| Route paths | `rails_get_routes(controller:\"x\")` | Read config/routes.rb |",
|
|
220
|
-
"| Feature overview | `rails_analyze_feature(feature:\"x\")` | Explore agent / Grep |",
|
|
221
|
-
"| Find code | `rails_search_code(pattern:\"x\")` | Grep tool |",
|
|
220
|
+
"| Feature overview | `rails_analyze_feature(feature:\"x\")` — models + controllers (inherited filters) + routes (code-ready helpers) + services + jobs + views + tests + gaps | Explore agent / Grep |",
|
|
221
|
+
"| Find code | `rails_search_code(pattern:\"x\", match_type:\"trace\")` | Grep tool |",
|
|
222
222
|
"| Validate edits | `rails_validate(files:[...])` | ruby -c / erb / node |",
|
|
223
223
|
"| Controller logic | `rails_get_controllers(controller:\"X\", action:\"y\")` | Read controller file |",
|
|
224
224
|
"| UI patterns | `rails_get_design_system` | Read view files |",
|
|
@@ -227,10 +227,11 @@ module RailsAiContext
|
|
|
227
227
|
"| Full context | `rails_get_context(model:\"X\")` | Multiple Read calls |",
|
|
228
228
|
"",
|
|
229
229
|
"### More Tools",
|
|
230
|
-
"- `rails_get_view(controller:\"x\")` | `rails_get_concern(name:\"X\")` | `rails_get_callbacks(model:\"X\")`",
|
|
230
|
+
"- `rails_get_view(controller:\"x\")` | `rails_get_concern(name:\"X\", detail:\"full\")` — concern methods with source code | `rails_get_callbacks(model:\"X\")`",
|
|
231
231
|
"- `rails_get_helper_methods` | `rails_get_service_pattern` | `rails_get_job_pattern`",
|
|
232
232
|
"- `rails_get_env` | `rails_get_partial_interface(path:\"shared/_form\")` | `rails_get_turbo_map`",
|
|
233
233
|
"- `rails_get_config` | `rails_get_gems` | `rails_get_conventions` | `rails_security_scan`",
|
|
234
|
+
"- `rails_search_code(pattern:\"method\", match_type:\"trace\")` — **trace mode**: definition + class context + source + siblings + callers + test coverage in one call",
|
|
234
235
|
"- `rails_get_edit_context(file:\"path\", near:\"keyword\")` — surgical edit helper with line numbers",
|
|
235
236
|
""
|
|
236
237
|
]
|
|
@@ -250,7 +250,13 @@ module RailsAiContext
|
|
|
250
250
|
"- `rails_get_config` | `rails_get_gems` | `rails_get_conventions` | `rails_security_scan`",
|
|
251
251
|
"- `rails_get_concern` | `rails_get_callbacks` | `rails_get_helper_methods` | `rails_get_service_pattern`",
|
|
252
252
|
"- `rails_get_job_pattern` | `rails_get_env` | `rails_get_partial_interface` | `rails_get_turbo_map`",
|
|
253
|
-
"- `rails_get_context(model:\"X\")` — composite cross-layer context in one call"
|
|
253
|
+
"- `rails_get_context(model:\"X\")` — composite cross-layer context in one call",
|
|
254
|
+
"",
|
|
255
|
+
"## Power Features",
|
|
256
|
+
"- `rails_search_code(pattern:\"method\", match_type:\"trace\")` — trace: definition + source + siblings + callers + tests",
|
|
257
|
+
"- `rails_get_concern(name:\"X\", detail:\"full\")` — concern methods with source code",
|
|
258
|
+
"- `rails_analyze_feature` — full-stack with inherited filters, route helpers, test gaps",
|
|
259
|
+
"- `rails_get_schema` — columns, indexes, defaults, encrypted hints, orphaned table warnings"
|
|
254
260
|
]
|
|
255
261
|
|
|
256
262
|
lines.join("\n")
|
|
@@ -290,7 +290,13 @@ module RailsAiContext
|
|
|
290
290
|
"- `rails_get_config` | `rails_get_gems` | `rails_get_conventions` | `rails_security_scan`",
|
|
291
291
|
"- `rails_get_concern` | `rails_get_callbacks` | `rails_get_helper_methods` | `rails_get_service_pattern`",
|
|
292
292
|
"- `rails_get_job_pattern` | `rails_get_env` | `rails_get_partial_interface` | `rails_get_turbo_map`",
|
|
293
|
-
"- `rails_get_context(model:\"X\")` — composite cross-layer context in one call"
|
|
293
|
+
"- `rails_get_context(model:\"X\")` — composite cross-layer context in one call",
|
|
294
|
+
"",
|
|
295
|
+
"## Power Features",
|
|
296
|
+
"- `rails_search_code(pattern:\"method\", match_type:\"trace\")` — trace: definition + source + siblings + callers + tests",
|
|
297
|
+
"- `rails_get_concern(name:\"X\", detail:\"full\")` — concern methods with source code",
|
|
298
|
+
"- `rails_analyze_feature` — full-stack with inherited filters, route helpers, test gaps",
|
|
299
|
+
"- `rails_get_schema` — columns, indexes, defaults, encrypted hints, orphaned table warnings"
|
|
294
300
|
]
|
|
295
301
|
|
|
296
302
|
lines.join("\n")
|
|
@@ -172,7 +172,7 @@ module RailsAiContext
|
|
|
172
172
|
"Start with `detail:\"summary\"`, then drill into specifics.",
|
|
173
173
|
"",
|
|
174
174
|
"### Mandatory Workflow",
|
|
175
|
-
"1. **Before exploring a feature**: `rails_analyze_feature(feature:\"...\")` —
|
|
175
|
+
"1. **Before exploring a feature**: `rails_analyze_feature(feature:\"...\")` — models + controllers (inherited filters) + routes (code-ready helpers) + services + jobs + views + tests + gaps",
|
|
176
176
|
"2. **Before writing migrations**: `rails_get_schema(table:\"...\")` — NOT reading db/schema.rb",
|
|
177
177
|
"3. **Before modifying a model**: `rails_get_model_details(model:\"...\")` — NOT reading the model file",
|
|
178
178
|
"4. **Before adding routes**: `rails_get_routes(controller:\"...\")` — Read only when you will edit",
|
|
@@ -182,10 +182,10 @@ module RailsAiContext
|
|
|
182
182
|
"### Do NOT Bypass",
|
|
183
183
|
"| Instead of... | Use this MCP tool |",
|
|
184
184
|
"|---------------|-------------------|",
|
|
185
|
-
"| Reading db/schema.rb | `rails_get_schema(table:\"x\")` |",
|
|
185
|
+
"| Reading db/schema.rb | `rails_get_schema(table:\"x\")` — includes orphaned table warnings |",
|
|
186
186
|
"| Reading model files | `rails_get_model_details(model:\"X\")` |",
|
|
187
187
|
"| Reading routes.rb | `rails_get_routes(controller:\"x\")` |",
|
|
188
|
-
"| Grep for code | `rails_search_code(pattern:\"x\")` |",
|
|
188
|
+
"| Grep for code | `rails_search_code(pattern:\"x\", match_type:\"trace\")` |",
|
|
189
189
|
"| Reading test files | `rails_get_test_info(model:\"X\")` |",
|
|
190
190
|
"| Reading controller | `rails_get_controllers(controller:\"X\", action:\"y\")` |",
|
|
191
191
|
"| ruby -c / erb / node | `rails_validate(files:[...])` |",
|
|
@@ -195,8 +195,9 @@ module RailsAiContext
|
|
|
195
195
|
"- `rails_get_view` | `rails_get_stimulus` | `rails_get_test_info` | `rails_analyze_feature`",
|
|
196
196
|
"- `rails_get_design_system` | `rails_get_edit_context` | `rails_validate` | `rails_search_code`",
|
|
197
197
|
"- `rails_get_config` | `rails_get_gems` | `rails_get_conventions` | `rails_security_scan`",
|
|
198
|
-
"- `rails_get_concern` | `rails_get_callbacks` | `rails_get_helper_methods` | `rails_get_service_pattern`",
|
|
198
|
+
"- `rails_get_concern(name:\"X\", detail:\"full\")` — concern methods with source code | `rails_get_callbacks` | `rails_get_helper_methods` | `rails_get_service_pattern`",
|
|
199
199
|
"- `rails_get_job_pattern` | `rails_get_env` | `rails_get_partial_interface` | `rails_get_turbo_map`",
|
|
200
|
+
"- `rails_search_code(pattern:\"method\", match_type:\"trace\")` — **trace mode**: definition + class context + source + siblings + callers + test coverage in one call",
|
|
200
201
|
"- `rails_get_context(model:\"X\")` — composite cross-layer context in one call",
|
|
201
202
|
""
|
|
202
203
|
]
|
|
@@ -95,7 +95,13 @@ module RailsAiContext
|
|
|
95
95
|
"- rails_get_config | rails_get_gems | rails_get_conventions | rails_security_scan",
|
|
96
96
|
"- rails_get_concern | rails_get_callbacks | rails_get_helper_methods | rails_get_service_pattern",
|
|
97
97
|
"- rails_get_job_pattern | rails_get_env | rails_get_partial_interface | rails_get_turbo_map",
|
|
98
|
-
"- rails_get_context(model:\"X\") — composite cross-layer context in one call"
|
|
98
|
+
"- rails_get_context(model:\"X\") — composite cross-layer context in one call",
|
|
99
|
+
"",
|
|
100
|
+
"Power Features:",
|
|
101
|
+
"- rails_search_code(pattern:\"method\", match_type:\"trace\") — trace: definition + source + siblings + callers + tests",
|
|
102
|
+
"- rails_get_concern(name:\"X\", detail:\"full\") — concern methods with source code",
|
|
103
|
+
"- rails_analyze_feature — full-stack with inherited filters, route helpers, test gaps",
|
|
104
|
+
"- rails_get_schema — columns, indexes, defaults, encrypted hints, orphaned table warnings"
|
|
99
105
|
]
|
|
100
106
|
|
|
101
107
|
lines.join("\n")
|
|
@@ -114,11 +114,17 @@ module RailsAiContext
|
|
|
114
114
|
actions = info[:actions]&.join(", ") || "none"
|
|
115
115
|
lines << "" << "### #{name}"
|
|
116
116
|
lines << "- **Actions:** #{actions}"
|
|
117
|
+
|
|
118
|
+
# Inherited filters from parent controller
|
|
119
|
+
parent_filters = detect_parent_filters_for_analyze(info[:parent_class], controllers)
|
|
120
|
+
if parent_filters.any?
|
|
121
|
+
lines << "- **Inherited filters:** #{parent_filters.map { |f| "#{f[:name]} _(from #{info[:parent_class]})_" }.join(', ')}"
|
|
122
|
+
end
|
|
123
|
+
|
|
117
124
|
filters = (info[:filters] || []).select { |f| f.is_a?(Hash) }.map do |f|
|
|
118
125
|
label = "#{f[:kind]} #{f[:name]}"
|
|
119
126
|
label += " only: #{Array(f[:only]).join(', ')}" if f[:only]&.any?
|
|
120
127
|
label += " except: #{Array(f[:except]).join(', ')}" if f[:except]&.any?
|
|
121
|
-
label += " unless: #{f[:unless]}" if f[:unless]
|
|
122
128
|
label
|
|
123
129
|
end
|
|
124
130
|
lines << "- **Filters:** #{filters.join('; ')}" if filters.any?
|
|
@@ -139,8 +145,14 @@ module RailsAiContext
|
|
|
139
145
|
lines << "## Routes (#{route_count})"
|
|
140
146
|
matched.sort.each do |ctrl, actions|
|
|
141
147
|
actions.each do |r|
|
|
142
|
-
|
|
143
|
-
|
|
148
|
+
params = r[:path].scan(/:(\w+)/).flatten
|
|
149
|
+
helper = if r[:name]
|
|
150
|
+
args = params.any? ? "(#{params.map { |p| p == "id" ? "@record" : ":#{p}" }.join(', ')})" : ""
|
|
151
|
+
" `#{r[:name]}_path#{args}`"
|
|
152
|
+
else
|
|
153
|
+
""
|
|
154
|
+
end
|
|
155
|
+
lines << "- `#{r[:verb]}` `#{r[:path]}` → #{ctrl}##{r[:action]}#{helper}"
|
|
144
156
|
end
|
|
145
157
|
end
|
|
146
158
|
else
|
|
@@ -321,6 +333,18 @@ module RailsAiContext
|
|
|
321
333
|
end
|
|
322
334
|
end
|
|
323
335
|
|
|
336
|
+
# Check services
|
|
337
|
+
service_dir = File.join(root, "app", "services")
|
|
338
|
+
if Dir.exist?(service_dir)
|
|
339
|
+
Dir.glob(File.join(service_dir, "**", "*.rb")).each do |path|
|
|
340
|
+
next unless File.basename(path, ".rb").include?(pattern)
|
|
341
|
+
snake = File.basename(path, ".rb")
|
|
342
|
+
unless test_basenames.any? { |t| t.include?(snake) }
|
|
343
|
+
gaps << "Service `#{snake}` — no test file found"
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
324
348
|
return if gaps.empty?
|
|
325
349
|
|
|
326
350
|
lines << "## Test Coverage Gaps"
|
|
@@ -330,6 +354,28 @@ module RailsAiContext
|
|
|
330
354
|
nil
|
|
331
355
|
end
|
|
332
356
|
|
|
357
|
+
# Detect inherited filters from parent controller
|
|
358
|
+
def detect_parent_filters_for_analyze(parent_class, all_controllers)
|
|
359
|
+
return [] unless parent_class
|
|
360
|
+
parent_data = all_controllers[parent_class]
|
|
361
|
+
if parent_data
|
|
362
|
+
return (parent_data[:filters] || []).select { |f| f.is_a?(Hash) && f[:kind] == "before" && !f[:only]&.any? }
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Fallback: read source file
|
|
366
|
+
path = Rails.root.join("app", "controllers", "#{parent_class.underscore}.rb")
|
|
367
|
+
return [] unless File.exist?(path)
|
|
368
|
+
source = File.read(path, encoding: "UTF-8") rescue nil
|
|
369
|
+
return [] unless source
|
|
370
|
+
|
|
371
|
+
source.each_line.filter_map do |line|
|
|
372
|
+
next if line.include?("only:") || line.include?("except:")
|
|
373
|
+
{ name: $1 } if line.match(/\A\s*before_action\s+:(\w+)/)
|
|
374
|
+
end
|
|
375
|
+
rescue
|
|
376
|
+
[]
|
|
377
|
+
end
|
|
378
|
+
|
|
333
379
|
# --- AF6: Related Models via Associations ---
|
|
334
380
|
def discover_related_models(ctx, matched_models, lines)
|
|
335
381
|
return if matched_models.empty?
|
|
@@ -18,13 +18,18 @@ module RailsAiContext
|
|
|
18
18
|
type: "string",
|
|
19
19
|
enum: %w[model controller all],
|
|
20
20
|
description: "Filter by concern type. model: app/models/concerns/. controller: app/controllers/concerns/. all: both (default)."
|
|
21
|
+
},
|
|
22
|
+
detail: {
|
|
23
|
+
type: "string",
|
|
24
|
+
enum: %w[summary standard full],
|
|
25
|
+
description: "Detail level. summary: concern names only. standard: names + method signatures (default). full: method signatures with source code."
|
|
21
26
|
}
|
|
22
27
|
}
|
|
23
28
|
)
|
|
24
29
|
|
|
25
30
|
annotations(read_only_hint: true, destructive_hint: false, idempotent_hint: true, open_world_hint: false)
|
|
26
31
|
|
|
27
|
-
def self.call(name: nil, type: "all", server_context: nil)
|
|
32
|
+
def self.call(name: nil, type: "all", detail: "standard", server_context: nil)
|
|
28
33
|
root = rails_app.root.to_s
|
|
29
34
|
max_size = RailsAiContext.configuration.max_file_size
|
|
30
35
|
|
|
@@ -36,7 +41,7 @@ module RailsAiContext
|
|
|
36
41
|
|
|
37
42
|
# Specific concern — full detail
|
|
38
43
|
if name
|
|
39
|
-
return show_concern(name, concern_dirs, root, max_size)
|
|
44
|
+
return show_concern(name, concern_dirs, root, max_size, detail)
|
|
40
45
|
end
|
|
41
46
|
|
|
42
47
|
# List all concerns
|
|
@@ -67,7 +72,7 @@ module RailsAiContext
|
|
|
67
72
|
end
|
|
68
73
|
end
|
|
69
74
|
|
|
70
|
-
private_class_method def self.show_concern(name, concern_dirs, root, max_size)
|
|
75
|
+
private_class_method def self.show_concern(name, concern_dirs, root, max_size, detail = "standard")
|
|
71
76
|
# Find the concern file — try underscore variants and nested paths
|
|
72
77
|
underscore = name.underscore
|
|
73
78
|
file_path = nil
|
|
@@ -121,14 +126,47 @@ module RailsAiContext
|
|
|
121
126
|
public_methods = parse_public_methods(source)
|
|
122
127
|
if public_methods.any?
|
|
123
128
|
lines << "" << "## Public Methods"
|
|
124
|
-
|
|
129
|
+
if detail == "full"
|
|
130
|
+
public_methods.each do |m|
|
|
131
|
+
method_name = m.to_s.split("(").first
|
|
132
|
+
method_source = extract_method_source(source, method_name)
|
|
133
|
+
if method_source
|
|
134
|
+
lines << "### #{m}"
|
|
135
|
+
lines << "```ruby"
|
|
136
|
+
lines << method_source
|
|
137
|
+
lines << "```"
|
|
138
|
+
lines << ""
|
|
139
|
+
else
|
|
140
|
+
lines << "- `#{m}`"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
else
|
|
144
|
+
public_methods.each { |m| lines << "- `#{m}`" }
|
|
145
|
+
end
|
|
125
146
|
end
|
|
126
147
|
|
|
127
148
|
# Parse class methods (inside class_methods block or def self.)
|
|
128
149
|
class_methods = parse_class_methods(source)
|
|
129
150
|
if class_methods.any?
|
|
130
151
|
lines << "" << "## Class Methods"
|
|
131
|
-
|
|
152
|
+
if detail == "full"
|
|
153
|
+
class_methods.each do |m|
|
|
154
|
+
method_name = m.to_s.split("(").first
|
|
155
|
+
# Try both `def method_name` and `def self.method_name`
|
|
156
|
+
method_source = extract_method_source(source, method_name) || extract_method_source(source, "self.#{method_name}")
|
|
157
|
+
if method_source
|
|
158
|
+
lines << "### #{m}"
|
|
159
|
+
lines << "```ruby"
|
|
160
|
+
lines << method_source
|
|
161
|
+
lines << "```"
|
|
162
|
+
lines << ""
|
|
163
|
+
else
|
|
164
|
+
lines << "- `#{m}`"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
else
|
|
168
|
+
class_methods.each { |m| lines << "- `#{m}`" }
|
|
169
|
+
end
|
|
132
170
|
end
|
|
133
171
|
|
|
134
172
|
# Parse callbacks defined in the concern
|
|
@@ -345,6 +383,25 @@ module RailsAiContext
|
|
|
345
383
|
[]
|
|
346
384
|
end
|
|
347
385
|
|
|
386
|
+
# Extract method source from raw source string using indentation-based matching
|
|
387
|
+
private_class_method def self.extract_method_source(source, method_name)
|
|
388
|
+
source_lines = source.lines
|
|
389
|
+
start_idx = source_lines.index { |l| l.match?(/\A\s*def\s+#{Regexp.escape(method_name.to_s)}\b/) }
|
|
390
|
+
return nil unless start_idx
|
|
391
|
+
|
|
392
|
+
def_indent = source_lines[start_idx][/\A\s*/].length
|
|
393
|
+
result = []
|
|
394
|
+
|
|
395
|
+
source_lines[start_idx..].each_with_index do |line, i|
|
|
396
|
+
result << line.rstrip
|
|
397
|
+
break if i > 0 && line.match?(/\A\s{#{def_indent}}end\b/)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
result.join("\n")
|
|
401
|
+
rescue
|
|
402
|
+
nil
|
|
403
|
+
end
|
|
404
|
+
|
|
348
405
|
private_class_method def self.find_includers(concern_name, root, concern_type)
|
|
349
406
|
includers = []
|
|
350
407
|
search_dirs = []
|
|
@@ -160,6 +160,14 @@ module RailsAiContext
|
|
|
160
160
|
lines << cols
|
|
161
161
|
lines << ""
|
|
162
162
|
end
|
|
163
|
+
|
|
164
|
+
# Detect orphaned tables (no ActiveRecord model maps to them)
|
|
165
|
+
orphaned = paginated.select { |name| models_for_table(name).empty? }
|
|
166
|
+
if orphaned.any?
|
|
167
|
+
lines << "\u26A0 **Orphaned tables** (no ActiveRecord model): #{orphaned.join(', ')}"
|
|
168
|
+
lines << ""
|
|
169
|
+
end
|
|
170
|
+
|
|
163
171
|
lines << "_Use `detail:\"summary\"` for all #{total} tables, `detail:\"full\"` for indexes/FKs, or `table:\"name\"` for one table._" if total > limit
|
|
164
172
|
text_response(lines.join("\n"))
|
|
165
173
|
|
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": "2.0.
|
|
10
|
+
"version": "2.0.4",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "mcpb",
|
|
14
|
-
"identifier": "https://github.com/crisnahine/rails-ai-context/releases/download/v2.0.
|
|
14
|
+
"identifier": "https://github.com/crisnahine/rails-ai-context/releases/download/v2.0.4/rails-ai-context-mcp.mcpb",
|
|
15
15
|
"fileSha256": "dd711a0ad6c4de943ae4da94eaf59a6dc9494b9d57f726e24649ed4e2f156990",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|