rails-ai-context 0.15.5 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9e71509ab3fcf2437fdd2616f8cb88140b478677abdd4ebd7cf6818eb8cead3
4
- data.tar.gz: c6847c4a500d762665ea2493c0ee56943bfe2e601b645bf5e9ec97df81f7e495
3
+ metadata.gz: 39df765c4841adb33e68870093b8f58d02ff28dfe5e245ee81655902a9b0fb78
4
+ data.tar.gz: 58385dad4ad55a627d854a2611ed16f951ac0409d54d80e96966efa8b0e1613b
5
5
  SHA512:
6
- metadata.gz: fb13a050a3e8200d1bac5f8d0d59bc1a67469a3fbe904682824541430f9f10755197fa3bd17858c08a2eedcc89f71c9da2dfffea8a8bcd0c177784f85ef511c6
7
- data.tar.gz: 4e05a8e22c395b4091d336268aa3e3994f416b11d174e63d922b5979b46695a222176a0871253ddd940638ead977e2917d14998618a91c25ad4a6f891eca9694
6
+ metadata.gz: 49755cc6de2afc6a536b470abea60b5a873a9f73419754f4ad304dd8988b34ebfa65b7d69e9878e2c7bdd3418183e9a71da16007f374161e87f44259c209f5f7
7
+ data.tar.gz: 83441b234d6f429d4a066ec79dece90fd5685d5ee4a3e5df882241b21b2b79cddde40d9c0c4fa3ac79322bf26764cf694a4beaf8dc9c419c82d9bc96e37009e3
data/CHANGELOG.md CHANGED
@@ -5,6 +5,34 @@ 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
+
20
+ ## [0.15.6] - 2026-03-22
21
+
22
+ ### Added
23
+
24
+ - **7 new configurable options** — `excluded_controllers`, `excluded_route_prefixes`, `excluded_concerns`, `excluded_filters`, `excluded_middleware`, `search_extensions`, `concern_paths` for stack-specific customization.
25
+ - **Configurable file size limits** — `max_file_size`, `max_test_file_size`, `max_schema_file_size`, `max_view_total_size`, `max_view_file_size`, `max_search_results`, `max_validate_files` all exposed via `Configuration`.
26
+ - **Class methods in model detail** — `rails_get_model_details` now shows class methods section.
27
+ - **Custom validate methods** — `validate :method_name` calls extracted from source and shown in model detail.
28
+
29
+ ### Fixed
30
+
31
+ - **Schema defaults always visible** — Null and Default columns always shown (NOT NULL marked bold). Previous token-saving logic accidentally hid critical migration data.
32
+ - **Optional associations** — `belongs_to` with `optional: true` now shows `[optional]` flag.
33
+ - **Concern methods inline** — shows public methods from concern source files (e.g. `PlanLimitable — can_cook?, increment_cook_count!`).
34
+ - **MCP tool error messages** — all tools now show available values on error/not-found for AI self-correction.
35
+
8
36
  ## [0.15.5] - 2026-03-22
9
37
 
10
38
  ### Fixed
data/README.md CHANGED
@@ -295,22 +295,45 @@ end
295
295
 
296
296
  | Option | Default | Description |
297
297
  |--------|---------|-------------|
298
+ | **Presets & Introspectors** | | |
298
299
  | `preset` | `:standard` | Introspector preset (`:standard` or `:full`) |
299
300
  | `introspectors` | 13 core | Array of introspector symbols |
301
+ | **Context Generation** | | |
300
302
  | `context_mode` | `:compact` | `:compact` (≤150 lines) or `:full` (dump everything) |
301
303
  | `claude_max_lines` | `150` | Max lines for CLAUDE.md in compact mode |
302
- | `max_tool_response_chars` | `120_000` | Safety cap for MCP tool responses |
303
- | `excluded_models` | internal Rails models | Models to skip during introspection |
304
- | `excluded_paths` | `node_modules tmp log vendor .git` | Paths excluded from code search |
305
- | `sensitive_patterns` | `.env .env.* config/master.key config/credentials.yml.enc config/credentials/*.yml.enc *.pem *.key` | File patterns blocked from search and read tools |
304
+ | `generate_root_files` | `true` | Generate root files (CLAUDE.md, etc.) — set `false` for split rules only |
305
+ | `output_dir` | `Rails.root` | Output directory for generated context files |
306
+ | **MCP Server** | | |
307
+ | `server_name` | `"rails-ai-context"` | MCP server name |
308
+ | `server_version` | gem version | MCP server version |
306
309
  | `auto_mount` | `false` | Auto-mount HTTP MCP endpoint |
307
310
  | `http_path` | `"/mcp"` | HTTP endpoint path |
308
311
  | `http_port` | `6029` | HTTP server port |
309
312
  | `http_bind` | `"127.0.0.1"` | HTTP server bind address |
310
313
  | `cache_ttl` | `30` | Cache TTL in seconds |
314
+ | `max_tool_response_chars` | `120_000` | Safety cap for MCP tool responses |
311
315
  | `live_reload` | `:auto` | `:auto`, `true`, or `false` — MCP live reload |
312
316
  | `live_reload_debounce` | `1.5` | Debounce interval in seconds |
313
- | `generate_root_files` | `true` | Generate root files (CLAUDE.md, etc.) — set `false` for split rules only |
317
+ | **Filtering & Exclusions** | | |
318
+ | `excluded_models` | internal Rails models | Models to skip during introspection |
319
+ | `excluded_paths` | `node_modules tmp log vendor .git` | Paths excluded from code search |
320
+ | `sensitive_patterns` | `.env .env.* config/master.key config/credentials.yml.enc config/credentials/*.yml.enc *.pem *.key` | File patterns blocked from search and read tools |
321
+ | `excluded_controllers` | `DeviseController` etc. | Controller classes hidden from listings |
322
+ | `excluded_route_prefixes` | `action_mailbox/ active_storage/ rails/` etc. | Route controller prefixes hidden with app_only |
323
+ | `excluded_concerns` | Rails/Devise/framework patterns | Regex patterns for concerns to hide |
324
+ | `excluded_filters` | `verify_authenticity_token` etc. | Framework filter names hidden from controller output |
325
+ | `excluded_middleware` | standard Rack/Rails middleware | Default middleware hidden from config output |
326
+ | **File Size Limits** | | |
327
+ | `max_file_size` | `2_000_000` | Per-file read limit for tools (bytes) |
328
+ | `max_test_file_size` | `500_000` | Test file read limit (bytes) |
329
+ | `max_schema_file_size` | `10_000_000` | schema.rb / structure.sql parse limit (bytes) |
330
+ | `max_view_total_size` | `5_000_000` | Total aggregated view content for UI patterns (bytes) |
331
+ | `max_view_file_size` | `500_000` | Per-view file during aggregation (bytes) |
332
+ | `max_search_results` | `100` | Max search results per call |
333
+ | `max_validate_files` | `20` | Max files per validate call |
334
+ | **Search & Discovery** | | |
335
+ | `search_extensions` | `rb js erb yml yaml json ts tsx vue svelte haml slim` | File extensions for Ruby fallback search |
336
+ | `concern_paths` | `app/models/concerns app/controllers/concerns` | Where to look for concern source files |
314
337
  </details>
315
338
 
316
339
  ---
data/docs/GUIDE.md CHANGED
@@ -297,6 +297,8 @@ Returns model details: associations, validations, scopes, enums, callbacks, conc
297
297
  |-------|------|-------------|
298
298
  | `model` | string | Model class name (e.g. `User`). Case-insensitive. Omit for listing. |
299
299
  | `detail` | string | `summary` / `standard` (default) / `full`. Ignored when model is specified. |
300
+ | `limit` | integer | Max models to return when listing. Default: 50. |
301
+ | `offset` | integer | Skip models for pagination. Default: 0. |
300
302
 
301
303
  **Examples:**
302
304
 
@@ -329,6 +331,7 @@ Returns all routes: HTTP verbs, paths, controller actions, route names.
329
331
  | `detail` | string | `summary` / `standard` (default) / `full` |
330
332
  | `limit` | integer | Max routes to return. Default: 100 (standard), 200 (full). |
331
333
  | `offset` | integer | Skip routes for pagination. Default: 0. |
334
+ | `app_only` | boolean | Filter out internal Rails routes (Active Storage, Action Mailbox, Conductor, etc.). Default: true. |
332
335
 
333
336
  **Examples:**
334
337
 
@@ -386,7 +389,7 @@ rails_get_controllers(detail: "full")
386
389
 
387
390
  Returns application configuration. No parameters.
388
391
 
389
- **Returns:** cache store, session store, timezone, queue adapter, mailer settings, custom middleware, initializers, current attributes.
392
+ **Returns:** cache store, session store, timezone, queue adapter, mailer settings, custom middleware (framework defaults are filtered out), notable initializers, CurrentAttributes classes.
390
393
 
391
394
  ```
392
395
  rails_get_config()
@@ -401,7 +404,7 @@ Returns test infrastructure details. Optionally filter by model or controller to
401
404
 
402
405
  | Param | Type | Description |
403
406
  |-------|------|-------------|
404
- | `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/`). |
405
408
  | `controller` | string | Show tests for a specific controller (e.g. `Cooks`). |
406
409
  | `detail` | string | `summary` / `standard` (default) / `full`. |
407
410
 
@@ -441,6 +444,108 @@ rails_get_conventions()
441
444
  → Architecture: [MVC, Service objects, Concerns], Patterns: [STI, Polymorphism], ...
442
445
  ```
443
446
 
447
+ ### rails_get_stimulus
448
+
449
+ Returns Stimulus controller details: targets, values, actions, outlets, classes.
450
+
451
+ **Parameters:**
452
+
453
+ | Param | Type | Description |
454
+ |-------|------|-------------|
455
+ | `controller` | string | Specific Stimulus controller name (e.g. `hello`, `filter-form`). Case-insensitive. |
456
+ | `detail` | string | `summary` / `standard` (default) / `full` |
457
+ | `limit` | integer | Max controllers to return when listing. Default: 50. |
458
+ | `offset` | integer | Skip controllers for pagination. Default: 0. |
459
+
460
+ **Examples:**
461
+
462
+ ```
463
+ rails_get_stimulus()
464
+ → Standard: controller names with targets and actions
465
+
466
+ rails_get_stimulus(detail: "summary")
467
+ → Names with target/action counts
468
+
469
+ rails_get_stimulus(controller: "filter-form")
470
+ → Full detail: targets, actions, values, outlets, classes, file path
471
+
472
+ rails_get_stimulus(detail: "full")
473
+ → All controllers with all details
474
+ ```
475
+
476
+ ### rails_get_view
477
+
478
+ Returns view template contents, partials, and Stimulus controller references.
479
+
480
+ **Parameters:**
481
+
482
+ | Param | Type | Description |
483
+ |-------|------|-------------|
484
+ | `controller` | string | Filter views by controller name (e.g. `cooks`, `brand_profiles`). Use `layouts` for layout files. |
485
+ | `path` | string | Specific view path relative to `app/views` (e.g. `cooks/index.html.erb`). Returns full content. |
486
+ | `detail` | string | `summary` / `standard` (default) / `full` |
487
+
488
+ **Examples:**
489
+
490
+ ```
491
+ rails_get_view()
492
+ → Standard: all view files with partial/stimulus refs
493
+
494
+ rails_get_view(controller: "cooks")
495
+ → All templates and partials for CooksController
496
+
497
+ rails_get_view(path: "cooks/index.html.erb")
498
+ → Full template content
499
+
500
+ rails_get_view(controller: "layouts")
501
+ → Layout files
502
+
503
+ rails_get_view(controller: "cooks", detail: "full")
504
+ → Full template content for all cooks views
505
+ ```
506
+
507
+ ### rails_get_edit_context
508
+
509
+ Returns just enough context to make a surgical Edit to a file. Returns the target area with line numbers and surrounding code.
510
+
511
+ **Parameters:**
512
+
513
+ | Param | Type | Description |
514
+ |-------|------|-------------|
515
+ | `file` | string | **Required.** File path relative to Rails root (e.g. `app/models/cook.rb`). |
516
+ | `near` | string | **Required.** What to find — a method name, keyword, or string to locate (e.g. `scope`, `def index`). |
517
+ | `context_lines` | integer | Lines of context above and below the match. Default: 5. |
518
+
519
+ **Examples:**
520
+
521
+ ```
522
+ rails_get_edit_context(file: "app/models/cook.rb", near: "scope")
523
+ → Code around the first scope with line numbers, expanded to full method
524
+
525
+ rails_get_edit_context(file: "app/controllers/cooks_controller.rb", near: "def index")
526
+ → The index action source with surrounding context
527
+ ```
528
+
529
+ ### rails_validate
530
+
531
+ Validates syntax of multiple files at once (Ruby, ERB, JavaScript).
532
+
533
+ **Parameters:**
534
+
535
+ | Param | Type | Description |
536
+ |-------|------|-------------|
537
+ | `files` | array | **Required.** File paths relative to Rails root (e.g. `["app/models/cook.rb", "app/views/cooks/index.html.erb"]`). |
538
+
539
+ **Examples:**
540
+
541
+ ```
542
+ rails_validate(files: ["app/models/cook.rb"])
543
+ → ✓ app/models/cook.rb — syntax OK
544
+
545
+ rails_validate(files: ["app/models/cook.rb", "app/controllers/cooks_controller.rb", "app/views/cooks/index.html.erb"])
546
+ → Checks all three files, reports pass/fail for each
547
+ ```
548
+
444
549
  ### rails_search_code
445
550
 
446
551
  Ripgrep-powered regex search across the codebase.
@@ -450,8 +555,10 @@ Ripgrep-powered regex search across the codebase.
450
555
  | Param | Type | Description |
451
556
  |-------|------|-------------|
452
557
  | `pattern` | string | **Required.** Regex pattern to search for. |
558
+ | `path` | string | Subdirectory to search in (e.g. `app/models`, `config`). Default: entire app. |
453
559
  | `file_type` | string | Filter by file type (e.g. `rb`, `erb`, `js`). Alphanumeric only. |
454
- | `max_results` | integer | Max results to return. Default: 20, max: 100. |
560
+ | `max_results` | integer | Max results to return. Default: 30, max: 100. |
561
+ | `context_lines` | integer | Lines of context before and after each match (like grep -C). Default: 0, max: 5. |
455
562
 
456
563
  **Examples:**
457
564
 
@@ -464,18 +571,28 @@ rails_search_code(pattern: "class.*Controller", file_type: "rb")
464
571
 
465
572
  rails_search_code(pattern: "def create", file_type: "rb", max_results: 50)
466
573
  → First 50 create methods across the codebase
574
+
575
+ rails_search_code(pattern: "current_user", path: "app/controllers")
576
+ → Search only in app/controllers/
577
+
578
+ rails_search_code(pattern: "validates", context_lines: 2)
579
+ → Matches with 2 lines of context before and after
467
580
  ```
468
581
 
469
- **Security:** Uses `Open3.capture2` with array arguments (no shell injection). Validates file_type. Blocks path traversal. Respects `excluded_paths` config.
582
+ **Security:** Uses `Open3.capture2` with array arguments (no shell injection). Validates file_type. Blocks path traversal. Respects `excluded_paths` and `sensitive_patterns` config.
470
583
 
471
584
  ### Detail Level Summary
472
585
 
473
- | Level | What it returns | Default limit | Best for |
474
- |-------|----------------|---------------|----------|
586
+ All tools that support `detail` use these three levels. Default limits vary by tool — schema defaults shown below:
587
+
588
+ | Level | What it returns | Schema default limit | Best for |
589
+ |-------|----------------|---------------------|----------|
475
590
  | `summary` | Names + counts | 50 | Getting the landscape, understanding what exists |
476
591
  | `standard` | Names + key details | 15 | Working context, column types, action names |
477
592
  | `full` | Everything | 5 | Deep inspection, indexes, FKs, constraints |
478
593
 
594
+ Other tools default to higher limits (e.g. models/controllers/stimulus: 50 for all levels, routes: 100/200).
595
+
479
596
  ### Recommended Workflow
480
597
 
481
598
  1. **Start with `detail:"summary"`** to see what exists
@@ -636,6 +753,55 @@ RailsAiContext.configure do |config|
636
753
  # Paths to exclude from code search
637
754
  config.excluded_paths += %w[vendor/bundle]
638
755
 
756
+ # Sensitive file patterns blocked from search and read tools
757
+ # config.sensitive_patterns += %w[config/my_secret.yml]
758
+
759
+ # Controllers hidden from listings (e.g. Devise internals)
760
+ # config.excluded_controllers += %w[MyInternalController]
761
+
762
+ # Route prefixes hidden with app_only (e.g. admin frameworks)
763
+ # config.excluded_route_prefixes += %w[admin/]
764
+
765
+ # Regex patterns for concerns to hide from model output
766
+ # config.excluded_concerns += [/MyInternal::/]
767
+
768
+ # Framework filter names hidden from controller output
769
+ # config.excluded_filters += %w[my_internal_filter]
770
+
771
+ # Default middleware hidden from config output
772
+ # config.excluded_middleware += %w[MyMiddleware]
773
+
774
+ # --- File size limits ---
775
+
776
+ # Per-file read limit for tools (default: 2MB)
777
+ # config.max_file_size = 2_000_000
778
+
779
+ # Test file read limit (default: 500KB)
780
+ # config.max_test_file_size = 500_000
781
+
782
+ # schema.rb / structure.sql parse limit (default: 10MB)
783
+ # config.max_schema_file_size = 10_000_000
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
+
791
+ # Max search results per call (default: 100)
792
+ # config.max_search_results = 100
793
+
794
+ # Max files per validate call (default: 20)
795
+ # config.max_validate_files = 20
796
+
797
+ # --- Search and file discovery ---
798
+
799
+ # File extensions for Ruby fallback search
800
+ # config.search_extensions = %w[rb js erb yml yaml json ts tsx vue svelte haml slim]
801
+
802
+ # Where to look for concern source files
803
+ # config.concern_paths = %w[app/models/concerns app/controllers/concerns]
804
+
639
805
  # --- Live reload ---
640
806
 
641
807
  # Auto-invalidate MCP tool caches on file changes
@@ -667,6 +833,7 @@ end
667
833
  | `cache_ttl` | Integer | `30` | Cache TTL in seconds for introspection results |
668
834
  | `excluded_models` | Array | internal Rails models | Models to skip |
669
835
  | `excluded_paths` | Array | `node_modules tmp log vendor .git` | Paths excluded from code search |
836
+ | `sensitive_patterns` | Array | `.env`, `.key`, `.pem`, credentials | File patterns blocked from search and read tools |
670
837
  | `output_dir` | String | `nil` (Rails.root) | Where to write context files |
671
838
  | `auto_mount` | Boolean | `false` | Auto-mount HTTP MCP endpoint |
672
839
  | `http_path` | String | `"/mcp"` | HTTP endpoint path |
@@ -675,7 +842,22 @@ end
675
842
  | `live_reload` | Symbol/Boolean | `:auto` | `:auto`, `true`, or `false` — enable MCP live reload |
676
843
  | `live_reload_debounce` | Float | `1.5` | Debounce interval in seconds for live reload |
677
844
  | `server_name` | String | `"rails-ai-context"` | MCP server name |
845
+ | `server_version` | String | gem version | MCP server version |
678
846
  | `generate_root_files` | Boolean | `true` | Generate root files (CLAUDE.md, .windsurfrules, etc.) — set `false` for split rules only |
847
+ | `max_file_size` | Integer | `2_000_000` | Per-file read limit for tools (2MB) |
848
+ | `max_test_file_size` | Integer | `500_000` | Test file read limit (500KB) |
849
+ | `max_schema_file_size` | Integer | `10_000_000` | schema.rb / structure.sql parse limit (10MB) |
850
+ | `max_view_total_size` | Integer | `5_000_000` | Total aggregated view content for UI patterns (5MB) |
851
+ | `max_view_file_size` | Integer | `500_000` | Per-view file during aggregation (500KB) |
852
+ | `max_search_results` | Integer | `100` | Max search results per call |
853
+ | `max_validate_files` | Integer | `20` | Max files per validate call |
854
+ | `excluded_controllers` | Array | `DeviseController`, etc. | Controller classes hidden from listings |
855
+ | `excluded_route_prefixes` | Array | `action_mailbox/`, `active_storage/`, etc. | Route controller prefixes hidden with `app_only` |
856
+ | `excluded_concerns` | Array | framework regex patterns | Regex patterns for concerns to hide from model output |
857
+ | `excluded_filters` | Array | `verify_authenticity_token`, etc. | Framework filter names hidden from controller output |
858
+ | `excluded_middleware` | Array | standard Rails middleware | Default middleware hidden from config output |
859
+ | `search_extensions` | Array | `rb js erb yml yaml json ts tsx vue svelte haml slim` | File extensions for Ruby fallback search |
860
+ | `concern_paths` | Array | `app/models/concerns app/controllers/concerns` | Where to look for concern source files |
679
861
 
680
862
  ### Root file generation
681
863
 
@@ -712,7 +894,7 @@ These run by default. Fast and cover core Rails structure.
712
894
  | `controllers` | Actions, filters (before/after/around with only/except), strong params methods, parent class, API controller detection, concerns. |
713
895
  | `tests` | Test framework (rspec/minitest), factories/fixtures with locations and counts, system tests, CI config files, coverage tool, test helpers, VCR cassettes. |
714
896
  | `migrations` | Total count, schema version, pending migrations, recent migration history with detected actions (create_table, add_column, etc.), migration statistics. |
715
- | `config` | Cache store, session store, timezone, middleware stack, initializers, credentials keys, CurrentAttributes classes. |
897
+ | `config` | Cache store, session store, timezone, queue adapter, mailer settings, middleware stack, initializers, credentials status, CurrentAttributes classes. |
716
898
  | `stimulus` | Stimulus controllers with targets, values (with types), actions, outlets, classes. Extracted from JS/TS files. |
717
899
  | `view_templates` | View file contents, partial references, Stimulus data attributes, UI pattern extraction, model field usage in partials. |
718
900
  | `design_tokens` | Auto-detects CSS framework (Tailwind v3/v4, Bootstrap, Sass, plain CSS) and extracts design tokens from config files and built CSS. |
@@ -750,7 +932,7 @@ config.preset = :full
750
932
 
751
933
  ```ruby
752
934
  # Start with standard, add specific ones
753
- config.introspectors += %i[views turbo auth api stimulus]
935
+ config.introspectors += %i[views turbo auth api]
754
936
 
755
937
  # Or build from scratch
756
938
  config.introspectors = %i[schema models routes gems auth api]
@@ -60,6 +60,26 @@ module RailsAiContext
60
60
  # When false, only generates split rule files (.claude/rules/, .cursor/rules/, etc.)
61
61
  attr_accessor :generate_root_files
62
62
 
63
+ # File size limits (bytes) — increase for larger projects
64
+ attr_accessor :max_file_size # Per-file read limit for tools (default: 2MB)
65
+ attr_accessor :max_test_file_size # Test file read limit (default: 500KB)
66
+ attr_accessor :max_schema_file_size # schema.rb / structure.sql parse limit (default: 10MB)
67
+ attr_accessor :max_view_total_size # Total aggregated view content for UI patterns (default: 5MB)
68
+ attr_accessor :max_view_file_size # Per-view file during aggregation (default: 500KB)
69
+ attr_accessor :max_search_results # Max search results per call (default: 100)
70
+ attr_accessor :max_validate_files # Max files per validate call (default: 20)
71
+
72
+ # Filtering — customize what's hidden from AI output
73
+ attr_accessor :excluded_controllers # Controller classes hidden from listings (e.g. DeviseController)
74
+ attr_accessor :excluded_route_prefixes # Route controller prefixes hidden with app_only (e.g. action_mailbox/)
75
+ attr_accessor :excluded_concerns # Regex patterns for concerns to hide (e.g. /Devise::Models/)
76
+ attr_accessor :excluded_filters # Framework filter names hidden from controller output
77
+ attr_accessor :excluded_middleware # Default middleware hidden from config output
78
+
79
+ # Search and file discovery
80
+ attr_accessor :search_extensions # File extensions for Ruby fallback search (default: rb,js,erb,yml,yaml,json)
81
+ attr_accessor :concern_paths # Where to look for concern source files (default: app/models/concerns)
82
+
63
83
  def initialize
64
84
  @server_name = "rails-ai-context"
65
85
  @server_version = RailsAiContext::VERSION
@@ -87,6 +107,41 @@ module RailsAiContext
87
107
  @live_reload = :auto
88
108
  @live_reload_debounce = 1.5
89
109
  @generate_root_files = true
110
+ @max_file_size = 2_000_000
111
+ @max_test_file_size = 500_000
112
+ @max_schema_file_size = 10_000_000
113
+ @max_view_total_size = 5_000_000
114
+ @max_view_file_size = 500_000
115
+ @max_search_results = 100
116
+ @max_validate_files = 20
117
+ @excluded_controllers = %w[DeviseController Devise::OmniauthCallbacksController]
118
+ @excluded_route_prefixes = %w[action_mailbox/ active_storage/ rails/ conductor/ devise/ turbo/]
119
+ @excluded_concerns = [
120
+ /::Generated/,
121
+ /\A(ActiveRecord|ActiveModel|ActiveSupport|ActionText|ActionMailbox|ActiveStorage)/,
122
+ /\A(ActionDispatch|ActionController|ActionView|AbstractController)/,
123
+ /\A(Devise::Models|Devise::Orm|Bullet::|Turbo::|GlobalID::|Rolify::)/
124
+ ]
125
+ @excluded_filters = %w[
126
+ verify_authenticity_token verify_same_origin_request
127
+ turbo_tracking_request_id handle_unverified_request
128
+ mark_for_same_origin_verification
129
+ ]
130
+ @excluded_middleware = %w[
131
+ Rack::Sendfile ActionDispatch::Static ActionDispatch::Executor
132
+ ActionDispatch::ServerTiming Rack::Runtime
133
+ ActionDispatch::RequestId ActionDispatch::RemoteIp
134
+ Rails::Rack::Logger ActionDispatch::ShowExceptions
135
+ ActionDispatch::DebugExceptions ActionDispatch::Callbacks
136
+ ActionDispatch::Cookies ActionDispatch::Session::CookieStore
137
+ ActionDispatch::Flash ActionDispatch::ContentSecurityPolicy::Middleware
138
+ ActionDispatch::PermissionsPolicy::Middleware ActionDispatch::ActionableExceptions
139
+ Rack::Head Rack::ConditionalGet Rack::ETag Rack::TempfileReaper
140
+ ActiveRecord::Migration::CheckPending ActionDispatch::HostAuthorization
141
+ Rack::MethodOverride ActionDispatch::Session::AbstractSecureStore
142
+ ]
143
+ @search_extensions = %w[rb js erb yml yaml json ts tsx vue svelte haml slim]
144
+ @concern_paths = %w[app/models/concerns app/controllers/concerns]
90
145
  end
91
146
 
92
147
  def preset=(name)
@@ -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 keys.
6
+ # timezone, middleware stack, initializers, credentials status.
7
7
  class ConfigIntrospector
8
8
  attr_reader :app
9
9
 
@@ -9,12 +9,9 @@ module RailsAiContext
9
9
  class ControllerIntrospector
10
10
  attr_reader :app
11
11
 
12
- # Framework filters inherited from ActionController::Base — suppress to reduce noise
13
- FRAMEWORK_FILTERS = %w[
14
- verify_authenticity_token verify_same_origin_request
15
- turbo_tracking_request_id handle_unverified_request
16
- mark_for_same_origin_verification
17
- ].freeze
12
+ def excluded_filters
13
+ RailsAiContext.configuration.excluded_filters
14
+ end
18
15
 
19
16
  def initialize(app)
20
17
  @app = app
@@ -153,30 +150,63 @@ module RailsAiContext
153
150
  actions.sort
154
151
  end
155
152
 
156
- # Prefer source-based parsing for filters always reflects current file state.
157
- # Falls back to reflection for controllers without readable source files.
153
+ # Hybrid approach: reflection for complete filter names (handles inheritance + skips),
154
+ # source parsing from inheritance chain for only/except constraints.
158
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
159
177
  if source
160
178
  filters = extract_filters_from_source(source)
161
179
  return filters if filters.any?
162
180
  end
163
- return [] unless ctrl.respond_to?(:_process_action_callbacks)
164
-
165
- ctrl._process_action_callbacks.filter_map do |cb|
166
- next if cb.filter.is_a?(Proc) || cb.filter.to_s.start_with?("_")
167
- next if FRAMEWORK_FILTERS.include?(cb.filter.to_s)
168
-
169
- filter = { name: cb.filter.to_s, kind: cb.kind.to_s }
170
- filter[:only] = cb.instance_variable_get(:@if)&.filter_map { |c| extract_action_condition(c) }&.flatten
171
- filter[:except] = cb.instance_variable_get(:@unless)&.filter_map { |c| extract_action_condition(c) }&.flatten
172
- filter.delete(:only) if filter[:only]&.empty?
173
- filter.delete(:except) if filter[:except]&.empty?
174
- filter
175
- end
181
+
182
+ []
176
183
  rescue
177
184
  []
178
185
  end
179
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
+
180
210
  def extract_filters_from_source(source)
181
211
  filters = []
182
212
  source.each_line do |line|
@@ -59,6 +59,7 @@ module RailsAiContext
59
59
  table_name: model.table_name,
60
60
  associations: extract_associations(model),
61
61
  validations: extract_validations(model),
62
+ custom_validates: extract_custom_validates(model),
62
63
  scopes: extract_scopes(model),
63
64
  enums: extract_enums(model),
64
65
  callbacks: extract_callbacks(model),
@@ -109,6 +110,17 @@ module RailsAiContext
109
110
  []
110
111
  end
111
112
 
113
+ # Extract custom validate :method_name calls from source
114
+ # These are business-rule validators that model.validators doesn't include
115
+ def extract_custom_validates(model)
116
+ source_path = model_source_path(model)
117
+ return [] unless source_path && File.exist?(source_path)
118
+
119
+ File.read(source_path).scan(/^\s*validate\s+:(\w+)/).flatten
120
+ rescue
121
+ []
122
+ end
123
+
112
124
  def model_source_path(model)
113
125
  root = app.root.to_s
114
126
  underscored = model.name.underscore
@@ -133,7 +145,7 @@ module RailsAiContext
133
145
  after_commit after_rollback
134
146
  ]
135
147
 
136
- callback_types.each_with_object({}) do |type, hash|
148
+ result = callback_types.each_with_object({}) do |type, hash|
137
149
  callbacks = model.send(:"_#{type}_callbacks").reject do |cb|
138
150
  cb.filter.to_s.start_with?(*EXCLUDED_CALLBACKS) || cb.filter.is_a?(Proc)
139
151
  end
@@ -142,6 +154,29 @@ module RailsAiContext
142
154
 
143
155
  hash[type.to_s] = callbacks.map { |cb| cb.filter.to_s }
144
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
145
180
  rescue
146
181
  {}
147
182
  end
@@ -156,16 +191,18 @@ module RailsAiContext
156
191
 
157
192
  def framework_concern?(name)
158
193
  return true if name.nil?
159
- return true if name.include?("::Generated")
160
- return true if name.match?(/\A(ActiveRecord|ActiveModel|ActiveSupport|ActionText|ActionMailbox|ActiveStorage|ActionDispatch|ActionController|ActionView|AbstractController)/)
161
- return true if name.match?(/\A(Devise::Models|Devise::Orm|Bullet::|Turbo::|GlobalID::|Rolify::)/)
162
- return true if %w[Kernel JSON PP Marshal MessagePack].include?(name)
163
- false
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::")
196
+ RailsAiContext.configuration.excluded_concerns.any? { |pattern| name.match?(pattern) }
164
197
  end
165
198
 
166
199
  def extract_public_class_methods(model)
200
+ scope_names = extract_scopes(model).map(&:to_s)
167
201
  (model.methods - ActiveRecord::Base.methods - Object.methods)
168
- .reject { |m| m.to_s.start_with?("_", "autosave") }
202
+ .reject { |m|
203
+ ms = m.to_s
204
+ ms.start_with?("_", "autosave") || scope_names.include?(ms)
205
+ }
169
206
  .sort
170
207
  .first(30) # Cap to avoid noise
171
208
  .map(&:to_s)