rails-ai-context 5.0.0 → 5.2.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/CHANGELOG.md +65 -0
- data/CONTRIBUTING.md +13 -0
- data/README.md +13 -11
- data/SECURITY.md +3 -0
- data/docs/GUIDE.md +109 -97
- data/lib/generators/rails_ai_context/install/install_generator.rb +63 -6
- data/lib/rails_ai_context/ast_cache.rb +79 -0
- data/lib/rails_ai_context/confidence.rb +45 -0
- data/lib/rails_ai_context/introspector.rb +39 -35
- data/lib/rails_ai_context/introspectors/active_storage_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/api_introspector.rb +4 -4
- data/lib/rails_ai_context/introspectors/component_introspector.rb +5 -4
- data/lib/rails_ai_context/introspectors/convention_introspector.rb +8 -3
- data/lib/rails_ai_context/introspectors/devops_introspector.rb +4 -1
- data/lib/rails_ai_context/introspectors/gem_introspector.rb +6 -6
- data/lib/rails_ai_context/introspectors/i18n_introspector.rb +26 -6
- data/lib/rails_ai_context/introspectors/job_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/listeners/associations_listener.rb +27 -0
- data/lib/rails_ai_context/introspectors/listeners/base_listener.rb +106 -0
- data/lib/rails_ai_context/introspectors/listeners/callbacks_listener.rb +69 -0
- data/lib/rails_ai_context/introspectors/listeners/enums_listener.rb +100 -0
- data/lib/rails_ai_context/introspectors/listeners/macros_listener.rb +124 -0
- data/lib/rails_ai_context/introspectors/listeners/methods_listener.rb +134 -0
- data/lib/rails_ai_context/introspectors/listeners/scopes_listener.rb +40 -0
- data/lib/rails_ai_context/introspectors/listeners/validations_listener.rb +87 -0
- data/lib/rails_ai_context/introspectors/model_introspector.rb +246 -340
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +8 -8
- data/lib/rails_ai_context/introspectors/route_introspector.rb +2 -0
- data/lib/rails_ai_context/introspectors/seeds_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/source_introspector.rb +72 -0
- data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +1 -1
- data/lib/rails_ai_context/serializers/tool_guide_helper.rb +9 -4
- data/lib/rails_ai_context/tools/base_tool.rb +1 -0
- data/lib/rails_ai_context/tools/validate.rb +9 -37
- data/lib/rails_ai_context/version.rb +1 -1
- data/server.json +3 -3
- metadata +44 -7
- data/CLAUDE.md +0 -85
- data/docs/token-comparison.jpeg +0 -0
- /data/{demo-trace.gif → demo/demo-trace.gif} +0 -0
- /data/{demo-trace.tape → demo/demo-trace.tape} +0 -0
- /data/{demo.gif → demo/demo.gif} +0 -0
- /data/{demo.tape → demo/demo.tape} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a1dd01df89a380a86e14436ae9275c5db3f88b0e4fc56ea6df7d308f2c468472
|
|
4
|
+
data.tar.gz: 6f2efe8319fa533b8ea19cc0ace32460b24cb9b96873af0956c4b126041cd14b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1447735cb99e717f1fa43a1fa54cc89fe1d6e0a836fcf156a7a66cede53c321328fbe4088a0c72103d91fe9c49de88f537cb59cddcbe1782773416892f76c728
|
|
7
|
+
data.tar.gz: 57b9e93748454c414563d453661eb4b51f94a063f15ac12a7073d6695b3cbc1e7fb1db323be3972ac1f830c1b21d73fcc65dc8fc1d5f170c8787cd9c8031881d
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,71 @@ 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
|
+
## [5.2.0] — 2026-04-07
|
|
9
|
+
|
|
10
|
+
### Added — Phase 1: Prism AST Foundation (Ground Truth Engine Blueprint #36)
|
|
11
|
+
|
|
12
|
+
System-wide AST migration replacing all regex-based Ruby source parsing with Prism AST visitors. This is the foundation layer for the Ground Truth Engine transformation (#37).
|
|
13
|
+
|
|
14
|
+
- **AstCache** (`lib/rails_ai_context/ast_cache.rb`) — Thread-safe Prism parse cache backed by `Concurrent::Map`. Keyed by path + SHA256 content hash + mtime. Invalidates automatically on file change. Shared by all AST-based introspectors.
|
|
15
|
+
|
|
16
|
+
- **VERIFIED/INFERRED confidence contract** — `Confidence.for_node(node)` determines whether an AST node's arguments are all static literals (`[VERIFIED]`) or contain dynamic expressions (`[INFERRED]`). Called from listeners via `BaseListener#confidence_for(node)`. Every source-level introspection result now carries a confidence tag.
|
|
17
|
+
|
|
18
|
+
- **7 Prism Listener classes** (`lib/rails_ai_context/introspectors/listeners/`):
|
|
19
|
+
- `AssociationsListener` — `belongs_to`, `has_many`, `has_one`, `has_and_belongs_to_many`
|
|
20
|
+
- `ValidationsListener` — `validates`, `validates_*_of`, custom `validate :method`
|
|
21
|
+
- `ScopesListener` — `scope :name, -> { ... }`
|
|
22
|
+
- `EnumsListener` — Rails 7+ and legacy enum syntax with prefix/suffix options
|
|
23
|
+
- `CallbacksListener` — all AR callback types including `after_commit` with `on:` resolution
|
|
24
|
+
- `MacrosListener` — `encrypts`, `normalizes`, `delegate`, `has_secure_password`, `serialize`, `store`, `has_one_attached`, `has_many_attached`, `has_rich_text`, `generates_token_for`, `attribute` API
|
|
25
|
+
- `MethodsListener` — `def`/`def self.` with visibility tracking, parameter extraction, `class << self` support
|
|
26
|
+
|
|
27
|
+
- **SourceIntrospector** (`lib/rails_ai_context/introspectors/source_introspector.rb`) — Single-pass Prism Dispatcher that walks the AST once and feeds events to all 7 listeners simultaneously. Available as `SourceIntrospector.call(path)` for file-based introspection or `SourceIntrospector.from_source(string)` for in-memory parsing.
|
|
28
|
+
|
|
29
|
+
- **73 new specs** covering AstCache, SourceIntrospector integration, and all 7 listener classes with edge cases (multi-line associations, legacy enums, visibility tracking, parameter extraction).
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **ModelIntrospector** rewritten to use AST-based source parsing via `SourceIntrospector` instead of regex. Reflection-based extraction (associations via AR, validations via AR, enums via AR) preserved where it provides runtime accuracy. All `source.scan(...)`, `source.each_line`, and `line.match?(...)` patterns in model introspection eliminated.
|
|
34
|
+
|
|
35
|
+
- **Install generator** now wraps `config/initializers/rails_ai_context.rb` in `if defined?(RailsAiContext)` so apps with the gem in `group :development` only don't crash in test/production. Re-install upgrades existing unguarded initializers and preserves indentation. All README and GUIDE initializer examples updated to the guarded form (#35).
|
|
36
|
+
|
|
37
|
+
### Dependencies
|
|
38
|
+
|
|
39
|
+
- Added `prism >= 0.28` (stdlib in Ruby 3.3+, gem for 3.2)
|
|
40
|
+
- Added `concurrent-ruby >= 1.2` (thread-safe AST cache; already transitive via Rails)
|
|
41
|
+
|
|
42
|
+
### Why
|
|
43
|
+
|
|
44
|
+
Regex-based Ruby source parsing was the #3 critical finding in the architecture audit: it breaks on heredocs, multi-line DSL calls, `class << self` blocks, and metaprogrammed constructs. Prism AST provides 100% syntax-level accuracy. The single-pass Dispatcher pattern means parsing a 500-line model file runs all 7 listeners in one tree walk — no repeated I/O or re-parsing. The confidence tagging gives AI agents explicit signal about what data is ground truth vs. what requires runtime verification.
|
|
45
|
+
|
|
46
|
+
## [5.1.0] — 2026-04-06
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
Accuracy fixes across 8 introspectors, eliminating false positives and capturing previously-missed signals. No public API changes; all 38 MCP tools retain their contracts.
|
|
51
|
+
|
|
52
|
+
- **ApiIntrospector** — pagination detection (`detect_pagination`) was substring-matching Gemfile.lock content, producing false positives on gems that merely contain the strategy name: `happypagy`, `kaminari-i18n`, transitive `pagy` dependencies. Now uses anchored lockfile regex (`^ pagy \(`) that only matches direct top-level dependencies. Same fix applied to `kaminari`, `will_paginate`, and `graphql-pro` detection.
|
|
53
|
+
- **DevOpsIntrospector** — health-check detection (`detect_health_check`) used an unanchored word regex (`\b(?:health|up|ping|status)\b`) that matched comments, controller names, and any line containing those words. Tightened to match only quoted route strings (`"/up"`, `"/healthz"`, `"/liveness"`, etc.) or the `rails_health_check` symbol. Also newly detects `/readiness`, `/alive`, and `/healthz` routes.
|
|
54
|
+
- **PerformanceIntrospector** — schema parsing (`parse_indexed_columns`) tracked table context with a boolean-ish `current_table` variable but never cleared it on `end` lines, so `add_index` statements after a `create_table` block matched both the inner block branch AND the outer branch, producing duplicate index entries. This polluted `missing_fk_indexes` analysis. Fixed via explicit `inside_create_table` state flag with block boundary detection. Also added `m` (multiline) flag to specific-association preload regex so `.includes(...)` calls spanning multiple lines are matched.
|
|
55
|
+
- **I18nIntrospector** — `count_keys_for_locale` only read `config/locales/{locale}.yml`, missing nested locale files that are the Rails convention for gem-added translations: `config/locales/devise.en.yml`, `config/locales/en/users.yml`, `config/locales/admin/en.yml`. New `find_locale_paths` method globs all YAML under `config/locales/**/*` and selects files whose basename equals the locale, ends with `.{locale}`, or lives under a `{locale}/` subfolder. In typical Rails apps this captures 2-10x more translation keys than the previous single-file read, making `translation_coverage` percentages meaningful.
|
|
56
|
+
- **JobIntrospector** — when a job class declared `queue_as ->(job) { ... }`, `job.queue_name` returned a Proc that was then called with no arguments, crashing or returning stale values. Now returns `"dynamic"` when queue is a Proc, matching the job's actual runtime behavior (queue is resolved per-invocation).
|
|
57
|
+
- **ModelIntrospector** — source-parsed class methods in `extract_source_class_methods` emitted a spurious `"self"` entry because `def self.foo` matched both the `def self.(\w+)` branch AND the generic `def (\w+)` branch inside `class << self` tracking. Restructured as `if/elsif` so each `def` line matches exactly one pattern. Also anchored `class << self` detection with `\b` to avoid partial-word matches.
|
|
58
|
+
- **RouteIntrospector** — `call` method could raise if `Rails.application.routes` was not yet loaded or a sub-method failed mid-extraction. Added a top-level rescue that returns `{ error: msg }`, matching the error contract used by every other introspector.
|
|
59
|
+
- **SeedsIntrospector** — `has_ordering` regex (`load.*order|require.*order|seeds.*\d+`) matched unrelated code like `require 'order'` or `seeds 001` in comments. Tightened to match actual ordering patterns: `Dir[...*.rb].sort`, `load "seeds/NN_foo.rb"`, `require_relative "seeds/NN_foo"`.
|
|
60
|
+
|
|
61
|
+
### Performance
|
|
62
|
+
|
|
63
|
+
- **ConventionIntrospector** — `gem_present?` was reading `Gemfile.lock` from disk 15 times per introspection pass (once per notable gem check). Memoized into a single read: **-93% I/O** (15 reads → 1 read). ~60% faster on typical apps.
|
|
64
|
+
- **ComponentIntrospector** — `build_summary` called `extract_components` again after `call` already computed it, doubling the filesystem walk and component parsing work. Now passes the result through: **-50% work**. ~50% faster.
|
|
65
|
+
- **GemIntrospector** — `categorize_gems(specs)` internally called `detect_notable_gems(specs)` after `call` had already called it, duplicating gem-list iteration and category lookup. Now accepts the notable-gem result directly: **-50% work**.
|
|
66
|
+
- **ActiveStorageIntrospector** — `uses_direct_uploads?` globbed `**/*` across `app/views` + `app/javascript`, reading every binary, image, font, and asset in those trees. Scoped to 9 relevant extensions (`erb,haml,slim,js,ts,jsx,tsx,mjs,rb`), avoiding wasteful I/O on irrelevant files.
|
|
67
|
+
- **Total**: ~14% cumulative speedup across all 12 modified introspectors on a medium-sized Rails app (23.66ms → 20.33ms).
|
|
68
|
+
|
|
69
|
+
### Why
|
|
70
|
+
|
|
71
|
+
Introspector output feeds every MCP tool response, every context file, and every rule file this gem generates. Silent inaccuracies (false-positive pagination detection, missed locale files, phantom duplicate indexes) compound: AI assistants make decisions based on this data, and incorrect data produces incorrect code suggestions. These fixes tighten the accuracy floor without changing any public interface.
|
|
72
|
+
|
|
8
73
|
## [5.0.0] — 2026-04-05
|
|
9
74
|
|
|
10
75
|
### Removed (BREAKING)
|
data/CONTRIBUTING.md
CHANGED
|
@@ -44,6 +44,19 @@ lib/rails_ai_context/
|
|
|
44
44
|
4. Register in `Server::TOOLS`
|
|
45
45
|
5. Write specs in `spec/lib/rails_ai_context/tools/your_tool_spec.rb`
|
|
46
46
|
|
|
47
|
+
## Adding a Prism Listener
|
|
48
|
+
|
|
49
|
+
Listeners extract specific concerns (associations, validations, etc.) from the AST via `Prism::Dispatcher` events.
|
|
50
|
+
|
|
51
|
+
1. Create `lib/rails_ai_context/introspectors/listeners/your_listener.rb` inheriting from `BaseListener`
|
|
52
|
+
2. Implement `on_call_node_enter(node)` and/or `on_def_node_enter(node)` — only the events your concern needs
|
|
53
|
+
3. Use `confidence_for(node)` from `BaseListener` to tag results `[VERIFIED]` or `[INFERRED]`
|
|
54
|
+
4. Store results in `@results` (accessed via `#results`)
|
|
55
|
+
5. Register the key/class pair in `SourceIntrospector::LISTENER_MAP`
|
|
56
|
+
6. Write specs in `spec/lib/rails_ai_context/introspectors/listeners/your_listener_spec.rb`
|
|
57
|
+
|
|
58
|
+
See existing listeners in `lib/rails_ai_context/introspectors/listeners/` for reference patterns.
|
|
59
|
+
|
|
47
60
|
## Adding a CLI Tool Interface
|
|
48
61
|
|
|
49
62
|
The `ToolRunner` (`lib/rails_ai_context/cli/tool_runner.rb`) handles CLI execution of all MCP tools. It is tested in `spec/lib/rails_ai_context/cli/tool_runner_spec.rb`. If you add a new MCP tool, it is automatically available via CLI — no extra registration needed. Tool name resolution (`schema` → `get_schema` → `rails_get_schema`) works for all tools.
|
data/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
[](https://registry.modelcontextprotocol.io)
|
|
20
20
|
[](https://github.com/crisnahine/rails-ai-context)
|
|
21
21
|
[](https://github.com/crisnahine/rails-ai-context)
|
|
22
|
-
[](https://github.com/crisnahine/rails-ai-context/actions)
|
|
23
23
|
[](LICENSE)
|
|
24
24
|
|
|
25
25
|
</div>
|
|
@@ -62,11 +62,11 @@ rails-ai-context serve # start MCP server
|
|
|
62
62
|
|
|
63
63
|
<div align="center">
|
|
64
64
|
|
|
65
|
-

|
|
65
|
+

|
|
66
66
|
|
|
67
67
|
</div>
|
|
68
68
|
|
|
69
|
-
Now your AI doesn't guess — it **asks your app directly.** 38 tools that query your schema, models, routes, controllers, views, and conventions on demand.
|
|
69
|
+
Now your AI doesn't guess — it **asks your app directly.** 38 tools that query your schema, models, routes, controllers, views, and conventions on demand. Model introspection uses Prism AST parsing — every result carries a `[VERIFIED]` or `[INFERRED]` confidence tag so AI knows what's ground truth and what needs runtime checking.
|
|
70
70
|
|
|
71
71
|
<br>
|
|
72
72
|
|
|
@@ -74,7 +74,7 @@ Now your AI doesn't guess — it **asks your app directly.** 38 tools that query
|
|
|
74
74
|
|
|
75
75
|
<div align="center">
|
|
76
76
|
|
|
77
|
-

|
|
77
|
+

|
|
78
78
|
|
|
79
79
|
</div>
|
|
80
80
|
|
|
@@ -263,7 +263,7 @@ Every tool is **read-only** and returns data verified against your actual app
|
|
|
263
263
|
| Tool | What it does |
|
|
264
264
|
|:-----|:------------|
|
|
265
265
|
| `get_schema` | Columns with indexed/unique/encrypted/default hints |
|
|
266
|
-
| `get_model_details` |
|
|
266
|
+
| `get_model_details` | AST-parsed associations, validations, scopes, enums, macros — each result tagged `[VERIFIED]` or `[INFERRED]` |
|
|
267
267
|
| `get_callbacks` | Callbacks in Rails execution order with source |
|
|
268
268
|
| `get_concern` | Concern methods + source + which models include it |
|
|
269
269
|
|
|
@@ -375,7 +375,7 @@ Enabled by default. Disable with `config.anti_hallucination_rules = false` if yo
|
|
|
375
375
|
▼
|
|
376
376
|
┌─────────────────────────────────────────────────────────┐
|
|
377
377
|
│ rails-ai-context │
|
|
378
|
-
│
|
|
378
|
+
│ Prism AST parsing. Cached. Confidence-tagged results. │
|
|
379
379
|
└────────┬──────────────────┬──────────────┬──────────────┘
|
|
380
380
|
│ │ │
|
|
381
381
|
▼ ▼ ▼
|
|
@@ -427,10 +427,12 @@ Both paths ask which AI tools you use and whether you want MCP or CLI mode. `.mc
|
|
|
427
427
|
|
|
428
428
|
```ruby
|
|
429
429
|
# config/initializers/rails_ai_context.rb
|
|
430
|
-
RailsAiContext
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
430
|
+
if defined?(RailsAiContext)
|
|
431
|
+
RailsAiContext.configure do |config|
|
|
432
|
+
config.ai_tools = %i[claude cursor] # Which AI tools to generate for
|
|
433
|
+
config.tool_mode = :mcp # :mcp (default) or :cli
|
|
434
|
+
config.preset = :full # :full (31 introspectors) or :standard (17)
|
|
435
|
+
end
|
|
434
436
|
end
|
|
435
437
|
```
|
|
436
438
|
|
|
@@ -470,7 +472,7 @@ end
|
|
|
470
472
|
## About
|
|
471
473
|
|
|
472
474
|
Built by a Rails developer with 10+ years of production experience.<br>
|
|
473
|
-
|
|
475
|
+
1668 tests. 38 tools. 31 introspectors. Standalone or in-Gemfile.<br>
|
|
474
476
|
MIT licensed. [Contributions welcome.](CONTRIBUTING.md)
|
|
475
477
|
|
|
476
478
|
<br>
|
data/SECURITY.md
CHANGED
data/docs/GUIDE.md
CHANGED
|
@@ -121,8 +121,10 @@ CONTEXT_MODE=full rails ai:context:copilot
|
|
|
121
121
|
|
|
122
122
|
```ruby
|
|
123
123
|
# config/initializers/rails_ai_context.rb
|
|
124
|
-
RailsAiContext
|
|
125
|
-
|
|
124
|
+
if defined?(RailsAiContext)
|
|
125
|
+
RailsAiContext.configure do |config|
|
|
126
|
+
config.context_mode = :full # or :compact (default)
|
|
127
|
+
end
|
|
126
128
|
end
|
|
127
129
|
```
|
|
128
130
|
|
|
@@ -306,10 +308,12 @@ Short names are resolved automatically:
|
|
|
306
308
|
The `tool_mode` config controls how tool references appear in generated context files:
|
|
307
309
|
|
|
308
310
|
```ruby
|
|
309
|
-
RailsAiContext
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
if defined?(RailsAiContext)
|
|
312
|
+
RailsAiContext.configure do |config|
|
|
313
|
+
# :mcp (default) — MCP primary, CLI as fallback
|
|
314
|
+
# :cli — CLI only, no MCP server needed
|
|
315
|
+
config.tool_mode = :mcp
|
|
316
|
+
end
|
|
313
317
|
end
|
|
314
318
|
```
|
|
315
319
|
|
|
@@ -359,7 +363,7 @@ rails_get_schema(detail: "full", format: "json")
|
|
|
359
363
|
|
|
360
364
|
### rails_get_model_details
|
|
361
365
|
|
|
362
|
-
Returns model details: associations, validations, scopes, enums, callbacks, concerns.
|
|
366
|
+
Returns model details: associations, validations, scopes, enums, callbacks, concerns. Source parsing uses Prism AST — every result carries a `[VERIFIED]` (static literal arguments) or `[INFERRED]` (dynamic expressions) confidence tag.
|
|
363
367
|
|
|
364
368
|
**Parameters:**
|
|
365
369
|
|
|
@@ -1110,11 +1114,13 @@ rails ai:serve_http
|
|
|
1110
1114
|
Or auto-mount inside your Rails app (no separate process):
|
|
1111
1115
|
|
|
1112
1116
|
```ruby
|
|
1113
|
-
RailsAiContext
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1117
|
+
if defined?(RailsAiContext)
|
|
1118
|
+
RailsAiContext.configure do |config|
|
|
1119
|
+
config.auto_mount = true
|
|
1120
|
+
config.http_path = "/mcp" # default
|
|
1121
|
+
config.http_port = 6029 # default
|
|
1122
|
+
config.http_bind = "127.0.0.1" # default (localhost only)
|
|
1123
|
+
end
|
|
1118
1124
|
end
|
|
1119
1125
|
```
|
|
1120
1126
|
|
|
@@ -1126,116 +1132,118 @@ Both transports are **read-only** — they expose the same 38 tools and never mo
|
|
|
1126
1132
|
|
|
1127
1133
|
```ruby
|
|
1128
1134
|
# config/initializers/rails_ai_context.rb
|
|
1129
|
-
RailsAiContext
|
|
1130
|
-
|
|
1135
|
+
if defined?(RailsAiContext)
|
|
1136
|
+
RailsAiContext.configure do |config|
|
|
1137
|
+
# --- Introspectors ---
|
|
1131
1138
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1139
|
+
# Presets: :full (31 introspectors, default) or :standard (17)
|
|
1140
|
+
config.preset = :full
|
|
1134
1141
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1142
|
+
# Cherry-pick on top of a preset
|
|
1143
|
+
config.introspectors += %i[views turbo auth api]
|
|
1137
1144
|
|
|
1138
|
-
|
|
1145
|
+
# --- Context files ---
|
|
1139
1146
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1147
|
+
# Context mode: :compact (default) or :full
|
|
1148
|
+
config.context_mode = :compact
|
|
1142
1149
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1150
|
+
# Max lines for CLAUDE.md in compact mode
|
|
1151
|
+
config.claude_max_lines = 150
|
|
1145
1152
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1153
|
+
# Output directory for context files (default: Rails.root)
|
|
1154
|
+
# config.output_dir = "/custom/path"
|
|
1148
1155
|
|
|
1149
|
-
|
|
1156
|
+
# --- MCP tools ---
|
|
1150
1157
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1158
|
+
# Tool mode: :mcp (MCP primary + CLI fallback) or :cli (CLI only)
|
|
1159
|
+
config.tool_mode = :mcp
|
|
1153
1160
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1161
|
+
# Max response size for tool results (safety net)
|
|
1162
|
+
config.max_tool_response_chars = 200_000
|
|
1156
1163
|
|
|
1157
|
-
|
|
1158
|
-
|
|
1164
|
+
# Cache TTL for introspection results (seconds)
|
|
1165
|
+
config.cache_ttl = 60
|
|
1159
1166
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1167
|
+
# Additional MCP tool classes to register alongside built-in tools
|
|
1168
|
+
# config.custom_tools = [MyApp::Tools::CustomTool]
|
|
1162
1169
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1170
|
+
# Exclude specific built-in tools (e.g. if you don't use Brakeman)
|
|
1171
|
+
# config.skip_tools = %w[rails_security_scan]
|
|
1165
1172
|
|
|
1166
|
-
|
|
1173
|
+
# --- Exclusions ---
|
|
1167
1174
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1175
|
+
# Models to skip during introspection
|
|
1176
|
+
config.excluded_models += %w[AdminUser InternalAuditLog]
|
|
1170
1177
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1178
|
+
# Paths to exclude from code search
|
|
1179
|
+
config.excluded_paths += %w[vendor/bundle]
|
|
1173
1180
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1181
|
+
# Sensitive file patterns blocked from search and read tools
|
|
1182
|
+
# config.sensitive_patterns += %w[config/my_secret.yml]
|
|
1176
1183
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1184
|
+
# Controllers hidden from listings (e.g. Devise internals)
|
|
1185
|
+
# config.excluded_controllers += %w[MyInternalController]
|
|
1179
1186
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1187
|
+
# Route prefixes hidden with app_only (e.g. admin frameworks)
|
|
1188
|
+
# config.excluded_route_prefixes += %w[admin/]
|
|
1182
1189
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1190
|
+
# Regex patterns for concerns to hide from model output
|
|
1191
|
+
# config.excluded_concerns += [/MyInternal::/]
|
|
1185
1192
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1193
|
+
# Framework filter names hidden from controller output
|
|
1194
|
+
# config.excluded_filters += %w[my_internal_filter]
|
|
1188
1195
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1196
|
+
# Default middleware hidden from config output
|
|
1197
|
+
# config.excluded_middleware += %w[MyMiddleware]
|
|
1191
1198
|
|
|
1192
|
-
|
|
1199
|
+
# --- File size limits ---
|
|
1193
1200
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1201
|
+
# Per-file read limit for tools (default: 5MB)
|
|
1202
|
+
# config.max_file_size = 5_000_000
|
|
1196
1203
|
|
|
1197
|
-
|
|
1198
|
-
|
|
1204
|
+
# Test file read limit (default: 1MB)
|
|
1205
|
+
# config.max_test_file_size = 1_000_000
|
|
1199
1206
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1207
|
+
# schema.rb / structure.sql parse limit (default: 10MB)
|
|
1208
|
+
# config.max_schema_file_size = 10_000_000
|
|
1202
1209
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1210
|
+
# Total aggregated view content for UI patterns (default: 10MB)
|
|
1211
|
+
# config.max_view_total_size = 10_000_000
|
|
1205
1212
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1213
|
+
# Per-view file during aggregation (default: 1MB)
|
|
1214
|
+
# config.max_view_file_size = 1_000_000
|
|
1208
1215
|
|
|
1209
|
-
|
|
1210
|
-
|
|
1216
|
+
# Max search results per call (default: 200)
|
|
1217
|
+
# config.max_search_results = 200
|
|
1211
1218
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1219
|
+
# Max files per validate call (default: 50)
|
|
1220
|
+
# config.max_validate_files = 50
|
|
1214
1221
|
|
|
1215
|
-
|
|
1222
|
+
# --- Search and file discovery ---
|
|
1216
1223
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1224
|
+
# File extensions for Ruby fallback search
|
|
1225
|
+
# config.search_extensions = %w[rb js erb yml yaml json ts tsx vue svelte haml slim]
|
|
1219
1226
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1227
|
+
# Where to look for concern source files
|
|
1228
|
+
# config.concern_paths = %w[app/models/concerns app/controllers/concerns]
|
|
1222
1229
|
|
|
1223
|
-
|
|
1230
|
+
# --- Live reload ---
|
|
1224
1231
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1232
|
+
# Auto-invalidate MCP tool caches on file changes
|
|
1233
|
+
# :auto — enable if `listen` gem is available (default)
|
|
1234
|
+
# true — enable, raise if `listen` is missing
|
|
1235
|
+
# false — disable entirely
|
|
1236
|
+
config.live_reload = :auto
|
|
1237
|
+
config.live_reload_debounce = 1.5 # seconds
|
|
1231
1238
|
|
|
1232
|
-
|
|
1239
|
+
# --- HTTP MCP endpoint ---
|
|
1233
1240
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1241
|
+
# Auto-mount Rack middleware for HTTP MCP
|
|
1242
|
+
config.auto_mount = false
|
|
1243
|
+
config.http_path = "/mcp"
|
|
1244
|
+
config.http_bind = "127.0.0.1"
|
|
1245
|
+
config.http_port = 6029
|
|
1246
|
+
end
|
|
1239
1247
|
end
|
|
1240
1248
|
```
|
|
1241
1249
|
|
|
@@ -1291,8 +1299,10 @@ By default, `rails ai:context` generates root files (CLAUDE.md, AGENTS.md, etc.)
|
|
|
1291
1299
|
**Skip root files:** If you prefer to maintain root files yourself and only want split rules (`.claude/rules/`, `.cursor/rules/`, `.github/instructions/`):
|
|
1292
1300
|
|
|
1293
1301
|
```ruby
|
|
1294
|
-
RailsAiContext
|
|
1295
|
-
|
|
1302
|
+
if defined?(RailsAiContext)
|
|
1303
|
+
RailsAiContext.configure do |config|
|
|
1304
|
+
config.generate_root_files = false
|
|
1305
|
+
end
|
|
1296
1306
|
end
|
|
1297
1307
|
```
|
|
1298
1308
|
|
|
@@ -1309,7 +1319,7 @@ Core Rails structure only. Use `config.preset = :standard` for a lighter footpri
|
|
|
1309
1319
|
| Introspector | What it discovers |
|
|
1310
1320
|
|-------------|-------------------|
|
|
1311
1321
|
| `schema` | Tables, columns, types, indexes, foreign keys, primary keys. Falls back to `db/schema.rb` parsing when no DB connected. |
|
|
1312
|
-
| `models` | Associations, validations, scopes, enums, callbacks, concerns, instance methods, class methods. Source-level macros: `has_secure_password`, `encrypts`, `normalizes`, `delegate`, `serialize`, `store`, `generates_token_for`, `has_one_attached`, `has_many_attached`, `has_rich_text`, `broadcasts_to`. |
|
|
1322
|
+
| `models` | Associations, validations, scopes, enums, callbacks, concerns, instance methods, class methods. Source-level macros via Prism AST (single-pass, 7 listeners): `has_secure_password`, `encrypts`, `normalizes`, `delegate`, `serialize`, `store`, `generates_token_for`, `has_one_attached`, `has_many_attached`, `has_rich_text`, `broadcasts_to`. Every result tagged `[VERIFIED]` or `[INFERRED]`. |
|
|
1313
1323
|
| `routes` | All routes with HTTP verbs, paths, controller actions, route names, API namespaces, mounted engines. |
|
|
1314
1324
|
| `jobs` | ActiveJob classes with queue names. Mailers with action methods. Action Cable channels. |
|
|
1315
1325
|
| `gems` | 70+ notable gems categorized: auth, background_jobs, admin, monitoring, search, pagination, forms, file_upload, testing, linting, security, api, frontend, utilities. |
|
|
@@ -1543,14 +1553,16 @@ Live reload is **enabled by default** when the `listen` gem is available. No con
|
|
|
1543
1553
|
### Configuration
|
|
1544
1554
|
|
|
1545
1555
|
```ruby
|
|
1546
|
-
RailsAiContext
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1556
|
+
if defined?(RailsAiContext)
|
|
1557
|
+
RailsAiContext.configure do |config|
|
|
1558
|
+
# :auto (default) — enable if `listen` gem is available, skip silently otherwise
|
|
1559
|
+
# true — enable, raise if `listen` gem is missing
|
|
1560
|
+
# false — disable entirely
|
|
1561
|
+
config.live_reload = :auto
|
|
1562
|
+
|
|
1563
|
+
# Debounce interval in seconds (default: 1.5)
|
|
1564
|
+
config.live_reload_debounce = 1.5
|
|
1565
|
+
end
|
|
1554
1566
|
end
|
|
1555
1567
|
```
|
|
1556
1568
|
|
|
@@ -310,6 +310,7 @@ module RailsAiContext
|
|
|
310
310
|
end
|
|
311
311
|
|
|
312
312
|
content += "end\n"
|
|
313
|
+
content, = ensure_initializer_guard(content)
|
|
313
314
|
|
|
314
315
|
create_file path, content
|
|
315
316
|
say "Created #{path} with all #{CONFIG_SECTIONS.size} config sections", :green
|
|
@@ -332,13 +333,16 @@ module RailsAiContext
|
|
|
332
333
|
marker = "── #{name}"
|
|
333
334
|
next if existing.include?(marker)
|
|
334
335
|
|
|
335
|
-
insert_point = existing
|
|
336
|
+
insert_point = configure_block_end_index(existing)
|
|
336
337
|
if insert_point
|
|
337
|
-
existing = existing.insert(insert_point, "\n#{section_content}\n")
|
|
338
|
+
existing = existing.insert(insert_point, "\n#{reindent_section_content(section_content, existing)}\n")
|
|
338
339
|
changes << "section: #{name}"
|
|
339
340
|
end
|
|
340
341
|
end
|
|
341
342
|
|
|
343
|
+
existing, changed = ensure_initializer_guard(existing)
|
|
344
|
+
changes << "guard" if changed
|
|
345
|
+
|
|
342
346
|
if changes.any?
|
|
343
347
|
File.write(full_path, existing)
|
|
344
348
|
say "Updated #{full_path.relative_path_from(Rails.root)}: #{changes.join(', ')}", :green
|
|
@@ -350,9 +354,11 @@ module RailsAiContext
|
|
|
350
354
|
# Replace or uncomment a config line. Returns [new_content, changed?]
|
|
351
355
|
def update_config_line(content, key, new_line)
|
|
352
356
|
# Match both commented and uncommented versions of this config key
|
|
353
|
-
pattern = /^[ \t]
|
|
357
|
+
pattern = /^([ \t]*)#?\s*#{Regexp.escape(key)}\s*=.*$/
|
|
354
358
|
if content.match?(pattern)
|
|
355
|
-
updated = content.sub(pattern
|
|
359
|
+
updated = content.sub(pattern) do
|
|
360
|
+
"#{Regexp.last_match(1)}#{new_line.lstrip}"
|
|
361
|
+
end
|
|
356
362
|
[ updated, updated != content ]
|
|
357
363
|
else
|
|
358
364
|
# Key not found at all — don't add (it's in a section that will be added)
|
|
@@ -360,6 +366,56 @@ module RailsAiContext
|
|
|
360
366
|
end
|
|
361
367
|
end
|
|
362
368
|
|
|
369
|
+
def configure_block_end_index(content)
|
|
370
|
+
end_positions = []
|
|
371
|
+
content.to_enum(:scan, /^[ \t]*end\b/).each do
|
|
372
|
+
end_positions << Regexp.last_match.begin(0)
|
|
373
|
+
end
|
|
374
|
+
return nil if end_positions.empty?
|
|
375
|
+
|
|
376
|
+
if guarded_initializer?(content) && end_positions.size >= 2
|
|
377
|
+
end_positions[-2]
|
|
378
|
+
else
|
|
379
|
+
end_positions[-1]
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def ensure_initializer_guard(content)
|
|
384
|
+
return [ content, false ] if guarded_initializer?(content)
|
|
385
|
+
|
|
386
|
+
header_match = content.match(/\A# frozen_string_literal: true\n(?:\n)?/)
|
|
387
|
+
header = header_match ? "# frozen_string_literal: true\n\n" : ""
|
|
388
|
+
body = header_match ? content.delete_prefix(header_match[0]) : content
|
|
389
|
+
body = "#{body}\n" unless body.end_with?("\n")
|
|
390
|
+
|
|
391
|
+
wrapped = "#{header}if defined?(RailsAiContext)\n#{indent_content(body)}end\n"
|
|
392
|
+
[ wrapped, wrapped != content ]
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def reindent_section_content(section_content, content)
|
|
396
|
+
indent = configure_body_indent(content)
|
|
397
|
+
section_content.lines.map do |line|
|
|
398
|
+
next line if line == "\n"
|
|
399
|
+
|
|
400
|
+
"#{indent}#{line.sub(/\A[ \t]{0,2}/, "")}"
|
|
401
|
+
end.join
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def configure_body_indent(content)
|
|
405
|
+
match = content.match(/^([ \t]*)RailsAiContext\.configure do \|config\|$/)
|
|
406
|
+
return " " unless match
|
|
407
|
+
|
|
408
|
+
"#{match[1]} "
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def guarded_initializer?(content)
|
|
412
|
+
content.match?(/^[ \t]*if defined\?\(RailsAiContext\)$/)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def indent_content(content)
|
|
416
|
+
content.lines.map { |line| line == "\n" ? line : " #{line}" }.join
|
|
417
|
+
end
|
|
418
|
+
|
|
363
419
|
def build_ai_tools_line
|
|
364
420
|
# Always write uncommented so re-install can detect previous selection
|
|
365
421
|
" config.ai_tools = %i[#{@selected_formats.join(' ')}]"
|
|
@@ -459,9 +515,10 @@ module RailsAiContext
|
|
|
459
515
|
say ""
|
|
460
516
|
say "Commands:", :yellow
|
|
461
517
|
say " rails ai:context # Regenerate context files"
|
|
462
|
-
|
|
518
|
+
tool_count = RailsAiContext::Server::TOOLS.size
|
|
519
|
+
say " rails 'ai:tool[schema]' # Run any of the #{tool_count} tools from CLI"
|
|
463
520
|
if @tool_mode == :mcp
|
|
464
|
-
say " rails ai:serve # Start MCP server (
|
|
521
|
+
say " rails ai:serve # Start MCP server (#{tool_count} live tools)"
|
|
465
522
|
end
|
|
466
523
|
say " rails ai:doctor # Check AI readiness"
|
|
467
524
|
say " rails ai:inspect # Print introspection summary"
|