rails-ai-bridge 3.3.0 → 3.4.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.reek.yml +53 -34
  3. data/.rubocop.yml +1 -5
  4. data/.rubocop_todo.yml +7 -0
  5. data/AGENTS.md +7 -0
  6. data/CHANGELOG.md +67 -0
  7. data/README.md +21 -13
  8. data/docs/GUIDE.md +75 -13
  9. data/lib/rails_ai_bridge/cache_warmer.rb +42 -0
  10. data/lib/rails_ai_bridge/config/introspection.rb +46 -13
  11. data/lib/rails_ai_bridge/config/rubydex.rb +9 -1
  12. data/lib/rails_ai_bridge/configuration.rb +8 -1
  13. data/lib/rails_ai_bridge/context_provider.rb +7 -5
  14. data/lib/rails_ai_bridge/doctor/checkers/bridge_freshness_checker.rb +137 -0
  15. data/lib/rails_ai_bridge/doctor.rb +1 -0
  16. data/lib/rails_ai_bridge/engine.rb +24 -1
  17. data/lib/rails_ai_bridge/fingerprinter/cached_snapshot.rb +78 -0
  18. data/lib/rails_ai_bridge/fingerprinter.rb +49 -0
  19. data/lib/rails_ai_bridge/freshness_header.rb +132 -0
  20. data/lib/rails_ai_bridge/introspector/parallel_runner.rb +136 -0
  21. data/lib/rails_ai_bridge/introspector/timed_runner.rb +47 -0
  22. data/lib/rails_ai_bridge/introspector.rb +139 -22
  23. data/lib/rails_ai_bridge/introspectors/controller_introspector/filter_extractor.rb +62 -0
  24. data/lib/rails_ai_bridge/introspectors/controller_introspector.rb +2 -25
  25. data/lib/rails_ai_bridge/introspectors/convention_detector.rb +8 -3
  26. data/lib/rails_ai_bridge/introspectors/model_introspector/association_extractor.rb +70 -0
  27. data/lib/rails_ai_bridge/introspectors/model_introspector/callback_extractor.rb +54 -0
  28. data/lib/rails_ai_bridge/introspectors/model_introspector/method_extractor.rb +44 -0
  29. data/lib/rails_ai_bridge/introspectors/model_introspector/source_macro_extractor.rb +223 -0
  30. data/lib/rails_ai_bridge/introspectors/model_introspector.rb +8 -110
  31. data/lib/rails_ai_bridge/introspectors/model_semantic_enrichment.rb +9 -9
  32. data/lib/rails_ai_bridge/introspectors/non_ar_models_introspector.rb +3 -2
  33. data/lib/rails_ai_bridge/introspectors/route_introspector.rb +84 -40
  34. data/lib/rails_ai_bridge/introspectors/semantic_introspector.rb +2 -2
  35. data/lib/rails_ai_bridge/rubydex_adapter/incremental_indexer.rb +316 -0
  36. data/lib/rails_ai_bridge/rubydex_adapter/indexer.rb +9 -3
  37. data/lib/rails_ai_bridge/rubydex_adapter/method_counter.rb +7 -8
  38. data/lib/rails_ai_bridge/rubydex_adapter/serializer.rb +79 -34
  39. data/lib/rails_ai_bridge/rubydex_adapter.rb +110 -14
  40. data/lib/rails_ai_bridge/serializers/context_file_serializer.rb +99 -21
  41. data/lib/rails_ai_bridge/serializers/formatters/sections/semantic_formatter.rb +28 -17
  42. data/lib/rails_ai_bridge/serializers/providers/rules_serializer.rb +2 -83
  43. data/lib/rails_ai_bridge/tasks/rails_ai_bridge.rake +133 -36
  44. data/lib/rails_ai_bridge/tools/search_code/ripgrep_search.rb +116 -0
  45. data/lib/rails_ai_bridge/tools/search_code/ruby_search.rb +100 -0
  46. data/lib/rails_ai_bridge/tools/search_code/validator.rb +152 -0
  47. data/lib/rails_ai_bridge/tools/search_code.rb +30 -249
  48. data/lib/rails_ai_bridge/tools/search_semantic.rb +2 -2
  49. data/lib/rails_ai_bridge/version.rb +1 -1
  50. data/rails-ai-bridge.gemspec +3 -4
  51. metadata +39 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 70c4f4fa10f3ab710b0fc739366e9b68f30c309ad18759c8f7d0e25f5c5ba9c1
4
- data.tar.gz: 6efeb017419fd80d4c4ea28631640067f46823b29f9e42a7a92fddb95dfcb2f9
3
+ metadata.gz: 40f60ba64b6bd873a3cd1ebd9935e0c1b8250b9a8311135201bac1b66611f533
4
+ data.tar.gz: '08046f7af401786ad68cb95daaad9647eb2fec306b2dfb0a4b89987ccf8d081f'
5
5
  SHA512:
6
- metadata.gz: 30f13fd84f004af9056801f31444b657f57278628b49ce2fb9ee4e77a1ba2845dbc91f3e1f2092c42205f321d328f4b07a92a645d22e3095098a3a02be40975a
7
- data.tar.gz: b8ca2fde28e9c545c8cd38359e56382d17dd0d942164d3ce3c26bb52cef3a575e34abc32e7f04321888898b40c44b21dc6cb2f962a62f54d51f8255928ff808c
6
+ metadata.gz: 4ecb06105c4587df9114b465c8db3c88b462182b6462e1f07f841e92093693edeb52720b1fce378191323e38054412fa7637cc6198b91b371911396ebbf2f967
7
+ data.tar.gz: 65ac9a5d50ccf9f89535eaab7e229c90b685f35ef35a85b5edc4afb23047ac22f6ce1bc90398a4c682f99d19611a6678ba24935fccdffdb27af0c49c63265b49
data/.reek.yml CHANGED
@@ -19,6 +19,12 @@ detectors:
19
19
  - RailsAiBridge::Config::Introspection#search_code_allowed_file_types
20
20
  - RailsAiBridge::Config::Introspection#search_code_pattern_max_bytes
21
21
  - RailsAiBridge::Config::Introspection#search_code_timeout_seconds
22
+ - RailsAiBridge::Config::Introspection#snapshot_ttl
23
+ - RailsAiBridge::Config::Introspection#cache_warm_on_boot
24
+ - RailsAiBridge::Config::Introspection#parallel_introspection
25
+ - RailsAiBridge::Config::Introspection#parallel_pool_size
26
+ - RailsAiBridge::Config::Introspection#parallel_timeout_seconds
27
+ - RailsAiBridge::Fingerprinter::CachedSnapshot#snapshot_ttl
22
28
  - RailsAiBridge::Config::Mcp#authorize
23
29
  - RailsAiBridge::Config::Mcp#http_log_json
24
30
  - RailsAiBridge::Config::Mcp#mode
@@ -47,6 +53,8 @@ detectors:
47
53
  - RailsAiBridge::Config::Rubydex#rubydex_index_path
48
54
  - RailsAiBridge::Config::Rubydex#semantic_context_depth
49
55
  - RailsAiBridge::Config::Rubydex#semantic_introspector_enabled
56
+ - RailsAiBridge::Config::Rubydex#rubydex_incremental_threshold
57
+ - RailsAiBridge::Config::Rubydex#rubydex_persist_index
50
58
  - RailsAiBridge#configuration
51
59
  BooleanParameter:
52
60
  exclude:
@@ -60,6 +68,7 @@ detectors:
60
68
  exclude:
61
69
  - RailsAiBridge::ContextProvider#rebuild
62
70
  - RailsAiBridge::Doctor::Checkers::BaseChecker#check
71
+ - RailsAiBridge::RubydexAdapter#apply_failure_state
63
72
  - RailsAiBridge::Doctor#initialize
64
73
  - RailsAiBridge::Introspectors::MiddlewareIntrospector#categorize_middleware
65
74
  - RailsAiBridge::Mcp::HttpRateLimiter#initialize
@@ -72,10 +81,12 @@ detectors:
72
81
  - RailsAiBridge::Watcher#initialize
73
82
  - RailsAiBridge::RubydexAdapter#declaration_to_hash
74
83
  - RailsAiBridge::Services::FileManagementService#validate_path!
84
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#build
75
85
  DataClump:
76
86
  exclude:
77
87
  - RailsAiBridge::Serializers::RegenerationFooter
78
88
  - RailsAiBridge::Introspectors::SemanticIntrospector
89
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer
79
90
  DuplicateMethodCall:
80
91
  exclude:
81
92
  - RailsAiBridge::Config::Introspection#excluded_table?
@@ -90,11 +101,13 @@ detectors:
90
101
  - RailsAiBridge::HttpTransportApp#build
91
102
  - RailsAiBridge::Introspector#app_name
92
103
  - RailsAiBridge::Introspector#call
104
+ - RailsAiBridge::Introspector::ParallelRunner#resolve_future
105
+ - RailsAiBridge::Introspector::TimedRunner#self.call
106
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#reindex
93
107
  - RailsAiBridge::Introspectors::ActiveStorageIntrospector#extract_attachments
94
108
  - RailsAiBridge::Introspectors::ControllerIntrospector#call
95
109
  - RailsAiBridge::Introspectors::ControllerIntrospector#discover_controllers
96
110
  - RailsAiBridge::Introspectors::ControllerIntrospector#eager_load_controllers!
97
- - RailsAiBridge::Introspectors::ControllerIntrospector#extract_filters
98
111
  - RailsAiBridge::Introspectors::ConventionDetector#detect_architecture
99
112
  - RailsAiBridge::Introspectors::DatabaseStatsIntrospector#call
100
113
  - RailsAiBridge::Introspectors::EngineIntrospector#discover_mounted_engines
@@ -110,11 +123,7 @@ detectors:
110
123
  - RailsAiBridge::Introspectors::ModelIntrospector#call
111
124
  - RailsAiBridge::Introspectors::ModelIntrospector#discover_models
112
125
  - RailsAiBridge::Introspectors::ModelIntrospector#eager_load_models!
113
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_associations
114
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_callbacks
115
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_source_macros
116
126
  - RailsAiBridge::Introspectors::ModelSemanticEnrichment#extract_semantic_summary
117
- - RailsAiBridge::Introspectors::ModelSemanticEnrichment#find_similar_models
118
127
  - RailsAiBridge::Introspectors::MultiDatabaseIntrospector#detect_model_connections
119
128
  - RailsAiBridge::Introspectors::MultiDatabaseIntrospector#discover_databases
120
129
  - RailsAiBridge::Introspectors::NonArModelsIntrospector#collect_safe_object_space_entries
@@ -122,7 +131,6 @@ detectors:
122
131
  - RailsAiBridge::Introspectors::NonArModelsIntrospector#eager_load_app_models_dir
123
132
  - RailsAiBridge::Introspectors::NonArModelsIntrospector#safe_to_process?
124
133
  - RailsAiBridge::Introspectors::RakeTaskIntrospector#parse_rake_file
125
- - RailsAiBridge::Introspectors::RouteIntrospector#detect_api_namespaces
126
134
  - RailsAiBridge::Introspectors::RouteIntrospector#detect_mounted_engines
127
135
  - RailsAiBridge::Introspectors::RouteIntrospector#extract_routes
128
136
  - RailsAiBridge::Introspectors::SemanticIntrospector#detect_code_patterns
@@ -239,7 +247,6 @@ detectors:
239
247
  - RailsAiBridge::Introspectors::ConfigIntrospector#detect_cache_store
240
248
  - RailsAiBridge::Introspectors::ConfigIntrospector#extract_credentials_keys
241
249
  - RailsAiBridge::Introspectors::ConfigIntrospector#extract_middleware
242
- - RailsAiBridge::Introspectors::ControllerIntrospector#extract_filters
243
250
  - RailsAiBridge::Introspectors::ConventionDetector#detect_patterns
244
251
  - RailsAiBridge::Introspectors::ConventionDetector#scan_directory_structure
245
252
  - RailsAiBridge::Introspectors::DevOpsIntrospector#extract_docker_info
@@ -251,7 +258,6 @@ detectors:
251
258
  - RailsAiBridge::Introspectors::MiddlewareIntrospector#discover_custom_middleware
252
259
  - RailsAiBridge::Introspectors::MigrationIntrospector#pending_migrations
253
260
  - RailsAiBridge::Introspectors::ModelIntrospector#discover_models
254
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_source_macros
255
261
  - RailsAiBridge::Introspectors::ModelIntrospector#extract_validations
256
262
  - RailsAiBridge::Introspectors::ModelIntrospector#model_table_excluded?
257
263
  - RailsAiBridge::Introspectors::ModelSemanticEnrichment#find_similar_models
@@ -272,6 +278,8 @@ detectors:
272
278
  - RailsAiBridge::Introspectors::ViewIntrospector#detect_template_engines
273
279
  - RailsAiBridge::RubydexAdapter#constant_references
274
280
  - RailsAiBridge::RubydexAdapter#count_methods
281
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#find_modified_files
282
+ - RailsAiBridge::RubydexAdapter#indexer_options
275
283
  - RailsAiBridge::RubydexAdapter#declaration_to_hash
276
284
  - RailsAiBridge::RubydexAdapter#definition_to_hash
277
285
  - RailsAiBridge::RubydexAdapter#format_location
@@ -334,6 +342,7 @@ detectors:
334
342
  - RailsAiBridge::Tools::GetView::FullFormatter
335
343
  - RailsAiBridge::Tools::GetView::StandardFormatter
336
344
  - RailsAiBridge::Tools::GetView::SummaryFormatter
345
+ - RailsAiBridge::Fingerprinter::CachedSnapshot
337
346
  IrresponsibleModule:
338
347
  exclude:
339
348
  - RailsAiBridge::Generators::InstallGenerator
@@ -349,6 +358,7 @@ detectors:
349
358
  - RailsAiBridge::Doctor::Checkers::BaseChecker#check
350
359
  - RailsAiBridge::Doctor::Checkers::BaseChecker#new_check
351
360
  - RailsAiBridge::Mcp::HttpStructuredLog#emit
361
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#initialize
352
362
  - RailsAiBridge::Serializers::ContextFileSerializer#generate_split_rules
353
363
  - RailsAiBridge::Serializers::ProviderDocumentHeader#call
354
364
  - RailsAiBridge::Tools::GetRoutes#self.call
@@ -363,12 +373,13 @@ detectors:
363
373
  - RailsAiBridge::Tools::SearchCode#self.process_file_for_search
364
374
  - RailsAiBridge::Tools::SearchCode#self.search_with_ripgrep
365
375
  - RailsAiBridge::Tools::SearchCode#self.search_with_ruby
376
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#call
377
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#build
378
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#reindex
366
379
  ManualDispatch:
367
380
  exclude:
368
381
  - RailsAiBridge::Introspector#app_name
369
382
  - RailsAiBridge::Introspectors::ConfigIntrospector#extract_credentials_keys
370
- - RailsAiBridge::Introspectors::ControllerIntrospector#extract_action_condition
371
- - RailsAiBridge::Introspectors::ControllerIntrospector#extract_filters
372
383
  - RailsAiBridge::Introspectors::MigrationIntrospector#pending_migrations
373
384
  - RailsAiBridge::Introspectors::ModelIntrospector#extract_enums
374
385
  - RailsAiBridge::Introspectors::MultiDatabaseIntrospector#discover_databases
@@ -385,7 +396,14 @@ detectors:
385
396
  - RailsAiBridge::RubydexAdapter#descendants
386
397
  - RailsAiBridge::RubydexAdapter#format_location
387
398
  - RailsAiBridge::RubydexAdapter#safe_count
399
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#patch_graph
400
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#re_index_file
401
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#remove_from_graph
388
402
  - RailsAiBridge::Services::FileManagementService#default_allowed_base_dir
403
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#apply_incremental_changes
404
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#reindex_file
405
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#remove_file
406
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#log_error
389
407
  MissingSafeMethod:
390
408
  exclude:
391
409
  - RailsAiBridge::AssistantFormatsPreference
@@ -397,6 +415,7 @@ detectors:
397
415
  - RailsAiBridge::Services::FileManagementService
398
416
  - RailsAiBridge::Tools::BaseTool
399
417
  - RailsAiBridge::Watcher::BridgeRegenerator
418
+ - RailsAiBridge::Fingerprinter::CachedSnapshot
400
419
  NestedIterators:
401
420
  exclude:
402
421
  - RailsAiBridge::Fingerprinter#snapshot
@@ -405,15 +424,11 @@ detectors:
405
424
  - RailsAiBridge::Introspectors::ActionTextIntrospector#extract_rich_text_fields
406
425
  - RailsAiBridge::Introspectors::ActiveStorageIntrospector#detect_direct_upload
407
426
  - RailsAiBridge::Introspectors::ActiveStorageIntrospector#extract_attachments
408
- - RailsAiBridge::Introspectors::ControllerIntrospector#extract_filters
409
427
  - RailsAiBridge::Introspectors::DevOpsIntrospector#extract_procfile
410
428
  - RailsAiBridge::Introspectors::EngineIntrospector#discover_mounted_engines
411
429
  - RailsAiBridge::Introspectors::GemIntrospector#categorize_gems
412
430
  - RailsAiBridge::Introspectors::JobIntrospector#extract_channels
413
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_callbacks
414
- - RailsAiBridge::Introspectors::ModelSemanticEnrichment#find_similar_models
415
431
  - RailsAiBridge::Introspectors::MultiDatabaseIntrospector#detect_model_connections
416
- - RailsAiBridge::Introspectors::RouteIntrospector#group_by_controller
417
432
  - RailsAiBridge::Introspectors::SeedsIntrospector#detect_seeded_models
418
433
  - RailsAiBridge::Introspectors::TestIntrospector#detect_test_helpers
419
434
  - RailsAiBridge::Introspectors::TurboIntrospector#extract_turbo_frames
@@ -446,6 +461,7 @@ detectors:
446
461
  exclude:
447
462
  - RailsAiBridge::AssistantFormatsPreference#formats_for_default_bridge_task
448
463
  - RailsAiBridge::Config::Introspection#excluded_table?
464
+ - RailsAiBridge::RubydexAdapter#sanitize_index_path
449
465
  - RailsAiBridge::Config::Mcp#effective_http_rate_limit_max_requests
450
466
  - RailsAiBridge::Generators::InstallGenerator#profile_from_option
451
467
  - RailsAiBridge::Introspectors::ControllerIntrospector#discover_controllers
@@ -485,10 +501,12 @@ detectors:
485
501
  - RailsAiBridge::Config::Introspection
486
502
  - RailsAiBridge::Config::Mcp
487
503
  - RailsAiBridge::Config::Output
504
+ - RailsAiBridge::Config::Rubydex
488
505
  - RailsAiBridge::Config::Server
489
506
  - RailsAiBridge::Configuration
490
507
  - RailsAiBridge::Mcp::HttpRateLimiter
491
508
  - RailsAiBridge::RubydexAdapter
509
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer
492
510
  - RailsAiBridge::Tools::GetModelDetails::ResponseFormatter
493
511
  - RailsAiBridge::Tools::GetRoutes::ResponseFormatter
494
512
  - RailsAiBridge::Tools::GetSchema::ResponseFormatter
@@ -497,6 +515,7 @@ detectors:
497
515
  - RailsAiBridge::Generators::InstallGenerator
498
516
  - RailsAiBridge::Introspectors::ModelIntrospector
499
517
  - RailsAiBridge::RubydexAdapter
518
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer
500
519
  - RailsAiBridge::Serializers::Providers::BaseProviderSerializer
501
520
  TooManyConstants:
502
521
  exclude:
@@ -507,6 +526,9 @@ detectors:
507
526
  - RailsAiBridge#validate_auto_mount_configuration!
508
527
  - RailsAiBridge::AssistantFormatsPreference#formats_for_default_bridge_task
509
528
  - RailsAiBridge::AssistantFormatsPreference#write!
529
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#call
530
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#patch_graph
531
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#run_reindex
510
532
  - RailsAiBridge::Config::Mcp#effective_http_rate_limit_max_requests
511
533
  - RailsAiBridge::ContextProvider#fetch
512
534
  - RailsAiBridge::ContextProvider#fetch_section
@@ -520,6 +542,10 @@ detectors:
520
542
  - RailsAiBridge::Generators::InstallGenerator#show_instructions
521
543
  - RailsAiBridge::HttpTransportApp#build
522
544
  - RailsAiBridge::Introspector#call
545
+ - RailsAiBridge::Introspector#run_single
546
+ - RailsAiBridge::Introspector::ParallelRunner#call
547
+ - RailsAiBridge::Introspector::ParallelRunner#resolve_future
548
+ - RailsAiBridge::Introspector::TimedRunner#self.call
523
549
  - RailsAiBridge::Introspectors::ActionMailboxIntrospector#extract_mailboxes
524
550
  - RailsAiBridge::Introspectors::ActionTextIntrospector#extract_rich_text_fields
525
551
  - RailsAiBridge::Introspectors::ActiveStorageIntrospector#detect_direct_upload
@@ -536,7 +562,6 @@ detectors:
536
562
  - RailsAiBridge::Introspectors::AuthIntrospector#scan_models_for
537
563
  - RailsAiBridge::Introspectors::ConfigIntrospector#detect_current_attributes
538
564
  - RailsAiBridge::Introspectors::ControllerIntrospector#call
539
- - RailsAiBridge::Introspectors::ControllerIntrospector#extract_filters
540
565
  - RailsAiBridge::Introspectors::ConventionDetector#detect_architecture
541
566
  - RailsAiBridge::Introspectors::ConventionDetector#detect_patterns
542
567
  - RailsAiBridge::Introspectors::ConventionDetector#scan_directory_structure
@@ -559,10 +584,7 @@ detectors:
559
584
  - RailsAiBridge::Introspectors::MigrationIntrospector#migration_stats
560
585
  - RailsAiBridge::Introspectors::MigrationIntrospector#pending_migrations
561
586
  - RailsAiBridge::Introspectors::ModelIntrospector#call
562
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_associations
563
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_callbacks
564
587
  - RailsAiBridge::Introspectors::ModelIntrospector#extract_model_details
565
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_source_macros
566
588
  - RailsAiBridge::Introspectors::ModelSemanticEnrichment#calculate_complexity_score
567
589
  - RailsAiBridge::Introspectors::ModelSemanticEnrichment#extract_semantic_data
568
590
  - RailsAiBridge::Introspectors::ModelSemanticEnrichment#extract_semantic_summary
@@ -620,6 +642,11 @@ detectors:
620
642
  - RailsAiBridge::RubydexAdapter#index!
621
643
  - RailsAiBridge::RubydexAdapter#instance
622
644
  - RailsAiBridge::RubydexAdapter#search
645
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#call
646
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#reindex
647
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#update_mtimes
648
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#load_mtimes
649
+ - RailsAiBridge::RubydexAdapter#handle_index_result
623
650
  - RailsAiBridge::Serializers::ContextFileSerializer#call
624
651
  - RailsAiBridge::Serializers::ContextFileSerializer#generate_split_rules
625
652
  - RailsAiBridge::Serializers::ContextFileSerializer#serialize
@@ -774,7 +801,6 @@ detectors:
774
801
  - RailsAiBridge::Introspectors::ConfigIntrospector#extract_initializers
775
802
  - RailsAiBridge::Introspectors::ConfigIntrospector#extract_middleware
776
803
  - RailsAiBridge::Introspectors::ControllerIntrospector#call
777
- - RailsAiBridge::Introspectors::ControllerIntrospector#extract_filters
778
804
  - RailsAiBridge::Introspectors::ConventionDetector#detect_config_files
779
805
  - RailsAiBridge::Introspectors::ConventionDetector#detect_patterns
780
806
  - RailsAiBridge::Introspectors::DatabaseStatsIntrospector#call
@@ -796,11 +822,6 @@ detectors:
796
822
  - RailsAiBridge::Introspectors::MigrationIntrospector#migration_stats
797
823
  - RailsAiBridge::Introspectors::MigrationIntrospector#pending_migrations
798
824
  - RailsAiBridge::Introspectors::ModelIntrospector#call
799
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_public_class_methods
800
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_public_instance_methods
801
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_source_macros
802
- - RailsAiBridge::Introspectors::ModelIntrospector#sanitize_options
803
- - RailsAiBridge::Introspectors::ModelSemanticEnrichment#find_similar_models
804
825
  - RailsAiBridge::Introspectors::MultiDatabaseIntrospector#call
805
826
  - RailsAiBridge::Introspectors::MultiDatabaseIntrospector#detect_model_connections
806
827
  - RailsAiBridge::Introspectors::MultiDatabaseIntrospector#discover_replicas
@@ -809,9 +830,7 @@ detectors:
809
830
  - RailsAiBridge::Introspectors::NonArModelsIntrospector#safe_to_process?
810
831
  - RailsAiBridge::Introspectors::RakeTaskIntrospector#call
811
832
  - RailsAiBridge::Introspectors::RakeTaskIntrospector#parse_rake_file
812
- - RailsAiBridge::Introspectors::RouteIntrospector#detect_api_namespaces
813
833
  - RailsAiBridge::Introspectors::RouteIntrospector#detect_mounted_engines
814
- - RailsAiBridge::Introspectors::RouteIntrospector#group_by_controller
815
834
  - RailsAiBridge::Introspectors::Schema::StaticSchemaParser#skip_table?
816
835
  - RailsAiBridge::Introspectors::SchemaIntrospector#table_names
817
836
  - RailsAiBridge::Introspectors::SeedsIntrospector#analyze_seeds_file
@@ -937,13 +956,20 @@ detectors:
937
956
  UtilityFunction:
938
957
  exclude:
939
958
  - RailsAiBridge::Doctor#compute_score
959
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#file_mtime
960
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#current_files
961
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#reindex_file
962
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#remove_file
963
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#threshold_exceeded?
964
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#mtimes_path
965
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#serialize_mtimes
966
+ - RailsAiBridge::RubydexAdapter::IncrementalIndexer#deserialize_mtimes
940
967
  - RailsAiBridge::Doctor::Checkers::BaseChecker#new_check
941
968
  - RailsAiBridge::Doctor::Checkers::StimulusMcpToolChecker#tool_registered?
942
969
  - RailsAiBridge::Doctor::Checkers::ViewMcpToolChecker#tool_registered?
943
970
  - RailsAiBridge::Introspectors::ControllerIntrospector#api_controller?
944
971
  - RailsAiBridge::Introspectors::ControllerIntrospector#discover_controllers
945
972
  - RailsAiBridge::Introspectors::ControllerIntrospector#eager_load_controllers!
946
- - RailsAiBridge::Introspectors::ControllerIntrospector#extract_action_condition
947
973
  - RailsAiBridge::Introspectors::ControllerIntrospector#extract_actions
948
974
  - RailsAiBridge::Introspectors::ControllerIntrospector#extract_concerns
949
975
  - RailsAiBridge::Introspectors::ControllerIntrospector#extract_respond_to
@@ -957,12 +983,8 @@ detectors:
957
983
  - RailsAiBridge::Introspectors::JobIntrospector#extract_mailers
958
984
  - RailsAiBridge::Introspectors::MigrationIntrospector#detect_migration_actions
959
985
  - RailsAiBridge::Introspectors::ModelIntrospector#eager_load_models!
960
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_associations
961
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_callbacks
962
986
  - RailsAiBridge::Introspectors::ModelIntrospector#extract_concerns
963
987
  - RailsAiBridge::Introspectors::ModelIntrospector#extract_enums
964
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_public_class_methods
965
- - RailsAiBridge::Introspectors::ModelIntrospector#extract_public_instance_methods
966
988
  - RailsAiBridge::Introspectors::ModelIntrospector#sanitize_options
967
989
  - RailsAiBridge::Introspectors::ModelSemanticEnrichment#calculate_complexity_score
968
990
  - RailsAiBridge::Introspectors::ModelSemanticEnrichment#extract_semantic_summary
@@ -974,9 +996,6 @@ detectors:
974
996
  - RailsAiBridge::Introspectors::NonArModelsIntrospector#safe_to_process?
975
997
  - RailsAiBridge::Introspectors::NonArModelsIntrospector#sanitize_error_message
976
998
  - RailsAiBridge::Introspectors::RakeTaskIntrospector#parse_rake_file
977
- - RailsAiBridge::Introspectors::RouteIntrospector#detect_api_namespaces
978
- - RailsAiBridge::Introspectors::RouteIntrospector#extract_constraints
979
- - RailsAiBridge::Introspectors::RouteIntrospector#group_by_controller
980
999
  - RailsAiBridge::Introspectors::SchemaIntrospector#active_record_connected?
981
1000
  - RailsAiBridge::Introspectors::SchemaIntrospector#adapter_name
982
1001
  - RailsAiBridge::Introspectors::SchemaIntrospector#connection
data/.rubocop.yml CHANGED
@@ -1,7 +1,6 @@
1
1
  plugins:
2
2
  - rubocop-rails
3
3
  - rubocop-rspec
4
-
5
4
  AllCops:
6
5
  TargetRubyVersion: 3.2
7
6
  NewCops: enable
@@ -10,19 +9,15 @@ AllCops:
10
9
  - "vendor/**/*"
11
10
  - "spec/internal/**/*"
12
11
  - "gemfiles/**/*"
13
-
14
12
  Naming/FileName:
15
13
  Exclude:
16
14
  - "lib/rails-ai-bridge.rb"
17
15
  Naming/RescuedExceptionsVariableName:
18
16
  PreferredName: error
19
-
20
17
  Style/Documentation:
21
18
  Enabled: false
22
-
23
19
  Metrics/ClassLength:
24
20
  Max: 240
25
-
26
21
  Metrics/BlockLength:
27
22
  Max: 120
28
23
  Exclude:
@@ -73,3 +68,4 @@ Metrics/CyclomaticComplexity:
73
68
  Max: 35
74
69
  Metrics/ParameterLists:
75
70
  Max: 6
71
+ inherit_from: .rubocop_todo.yml
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,7 @@
1
+ Rails/ApplicationJob:
2
+ Exclude:
3
+ - 'spec/lib/rails_ai_bridge/introspectors/job_introspector_spec.rb'
4
+
5
+ Rails/ApplicationMailer:
6
+ Exclude:
7
+ - 'spec/lib/rails_ai_bridge/introspectors/job_introspector_spec.rb'
data/AGENTS.md CHANGED
@@ -53,3 +53,10 @@ Uses combustion gem for testing Rails engine behavior in isolation.
53
53
  - All tools prefixed with `rails_` per MCP naming best practices
54
54
  - `generate_context` returns `{ written: [], skipped: [] }` hash
55
55
  - Zeitwerk autoloads all files — no `require_relative` needed for new classes
56
+
57
+ <!-- lean-ctx -->
58
+ ## lean-ctx
59
+
60
+ Prefer lean-ctx MCP tools over native equivalents for token savings.
61
+ Full rules: @LEAN-CTX.md
62
+ <!-- /lean-ctx -->
data/CHANGELOG.md CHANGED
@@ -5,6 +5,73 @@ 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
+ ## [3.4.0] - 2026-05-21
9
+
10
+ ### Added
11
+
12
+ - **`TimedRunner` — per-introspector wall-clock timing** (#36) — new `RailsAiBridge::Introspector::TimedRunner.call(klass, app)` value object wraps any introspector class and returns `{ result:, duration_ms: }`. Uses `Process.clock_gettime(CLOCK_MONOTONIC)` for accurate measurement regardless of system clock adjustments. Duration is recorded even when the introspector raises, so you can diagnose slow-then-failing classes. Sequential runs now log duration at `debug` level via `Rails.logger.debug`.
13
+ - **Config-driven `ParallelRunner` pool size** (#36) — `config.parallel_pool_size` (default `4`) sets the upper bound for the `Concurrent::FixedThreadPool`; the actual size is `min(introspector_count, pool_size)` so no idle threads are ever created.
14
+ - **Per-future timeout for parallel introspection** (#36) — `config.parallel_timeout_seconds` (default `10`) is enforced on each `Concurrent::Future` via `future.value(timeout)`. Introspectors that exceed their budget are cancelled and return `{ error: "timed out after Ns" }` without blocking the rest of the pool. The pool's `wait_for_termination` also uses this value.
15
+ - **Rubydex incremental indexing** (#38) — new `RailsAiBridge::RubydexAdapter::IncrementalIndexer` service skips unchanged files on re-index using mtime tracking (integer seconds, no IEEE 754 precision loss). A full rebuild is triggered when the ratio of changed files exceeds `config.rubydex_incremental_threshold` (default `0.3`). The mtime snapshot can optionally survive process restarts via `config.rubydex_persist_index` (default `false`).
16
+ - **`config.rubydex_incremental_threshold`** (#38) (default `0.3`) — ratio of changed-to-total files above which the incremental indexer falls back to a full rebuild.
17
+ - **`config.rubydex_persist_index`** (#38) (default `false`) — when `true`, the rubydex mtime snapshot is written to disk alongside the index so incremental re-indexing survives process restarts.
18
+ - **Path-traversal guard for rubydex index path** (#38) — `RubydexAdapter#indexer_options` now sanitises `config.rubydex_index_path` through a `Pathname#cleanpath` + root-prefix check, returning `nil` (and falling back to the default) for any path that escapes `Rails.root`.
19
+ - **Bridge file freshness stamps** (#37) — generated bridge files (CLAUDE.md, AGENTS.md, GEMINI.md, .cursorrules, etc.) now embed a freshness header containing the generation timestamp, a 12-character source fingerprint (SHA-256 of `db/schema.rb` + `config/routes.rb`), and the gem version. Files are skipped on re-generation when their fingerprint matches, eliminating unnecessary timestamps and noisy git diffs.
20
+ - **`Fingerprinter.source_fingerprint`** (#37) — new singleton method that hashes the app's schema and routes files into a compact 12-char hex fingerprint used by the freshness system.
21
+
22
+ ### Fixed (Security & Architecture Audit)
23
+
24
+ - **ReDoS Vulnerability in `RubySearch`** — Added a 2-second timeout to the `Regexp.new` engine to prevent catastrophic backtracking denial-of-service on malicious search patterns.
25
+ - **Path Traversal via Symlinks in `RubydexAdapter`** — `sanitize_index_path` now uses `Pathname#realpath` to strictly validate that the configured index path resolves safely inside the `Rails.root` boundary.
26
+ - **TOCTOU Race Condition in `IncrementalIndexer`** — Upgraded mtime tracking from integer seconds (`to_i`) to rational (`to_r`) for precise sub-second caching, preventing scenarios where high-frequency file modifications within the same second bypassed change detection.
27
+ - **Threshold Edge Case in `IncrementalIndexer`** — Changed the rebuild cutoff comparison from `>` to `>=` so that precise boundary thresholds (like 100% of files) trigger full rebuilds correctly.
28
+ - **Memory Leaks & Exhaustion in `ParallelRunner`** — Replaced deprecated `clear_active_connections!` with `connection_pool.release_connection`, and explicitly added `pool.kill` to forcefully shut down long-running threads on timeouts.
29
+ - **State Leakage in Extractors** — Refactored `FilterExtractor`, `AssociationExtractor`, and `SourceMacroExtractor` to eliminate shared mutable state, establishing purely functional object APIs and tightening private encapsulation.
30
+ - **`db/structure.sql` fallback** (#37) — `source_fingerprint` automatically falls back to `db/structure.sql` when `db/schema.rb` is absent (apps using SQL schema format are now supported).
31
+ - **`FreshnessHeader` module** (#37) — centralized utility for embedding and extracting freshness metadata from bridge files. Supports both Markdown (HTML comment header) and JSON (`_meta` object) formats, with backward-compatible parsing of older files that lack the gem-version field.
32
+ - **Bridge freshness Doctor check** (#37) — a new `BridgeFreshnessChecker` is registered with the `Doctor` service. It reports stale bridge files (fingerprint mismatch) or missing bridge files as `:warn`, and fresh files as `:pass`. The Doctor now runs 16 total checks.
33
+ - **`rails ai:check` rake task** (#37) — runs all diagnostic checks and exits with code `1` if any check fails, enabling straightforward CI/CD integration (e.g., `rails ai:check || exit 1`).
34
+ - **`CHECK=1` pre-generation guard** (#37) — pass `CHECK=1` to `rails ai:bridge` (or any bridge sub-task) to run Doctor diagnostics first; generation is aborted if any check fails.
35
+ - **`RailsAiBridge::RakeHelpers` module** (#37) — extracted top-level rake helper methods (`print_result`, `apply_context_mode_override`, `conflict_strategy`, `run_pre_generation_checks`) from global `Object` scope into a properly namespaced module.
36
+ - **`CacheWarmer` & `CachedSnapshot`** (#36) — implemented TTL-based thread-safe caching system with `config.cache_warm_on_boot` to preemptively load context into memory on application start.
37
+
38
+ ### Changed
39
+
40
+ - **`rubydex` enabled by default** — The `rubydex` gem (v0.2.3) is now a mandatory dependency and semantic analysis is enabled out of the box (`@rubydex_enabled = true`). This provides zero-config code graph and semantic context functionality to all users.
41
+ - **Improved IDE configurations in documentation** — Promoted HTTP/SSE as the primary and highly recommended connection method for `rbenv`/`rvm` users within IDEs (like Antigravity and Cursor) to bypass subprocess ruby environment pathing issues.
42
+ - **`Introspector#run_single`** (#36) — sequential execution is now routed through `TimedRunner` instead of a bare `rescue` block. Error handling behaviour is unchanged (`{ error: message }`), but every introspector call now produces a debug-level duration log entry.
43
+ - **`ParallelRunner#resolve_future`** (#36) — uses `future.value(timeout)` + `future.complete?` check instead of blocking `future.value!`. A `nil` return from a timed-out future is no longer misinterpreted as a successful result.
44
+ - **`ParallelRunner` pool shutdown** (#36) — `wait_for_termination` now uses `config.parallel_timeout_seconds` instead of a hardcoded `10`.
45
+ - **`RubydexAdapter#handle_index_result`** (#38) — on `:reindex!` failure, existing `@graph` and `@indexed` state is preserved rather than reset to `nil`/`false`, preventing a full context blackout on transient indexing errors.
46
+ - **Integer mtimes throughout `IncrementalIndexer`** (#38) — `serialize_mtimes`, `deserialize_mtimes`, and `file_mtime` now all operate in integer seconds (`Time#to_i`) to avoid IEEE 754 floating-point comparison drift.
47
+ - **`FreshnessHeader`** (#37) — expanded API with `embed_for(fmt, ...)`, `extract_metadata_for(fmt, content)`, and `extract_fingerprint_for(fmt, content)` dispatching methods. JSON and Markdown branching is now fully centralized here, removing format-aware `if fmt == :json` conditionals from callers.
48
+ - **`ContextFileSerializer`** (#37) — refactored to use a new `FreshnessWriter` inner class that encapsulates freshness metadata embedding and file write decisions. This eliminates `ControlParameter`, `UtilityFunction`, and `LongParameterList` Reek warnings.
49
+ - **`BridgeFreshnessChecker`** (#37) — refactored with a `ScanResult` struct to eliminate the 6-parameter `check_file` method; introduced `scan_files`, `accumulate_file_result`, `stale?`, and `freshness_check` helpers reducing `TooManyStatements` and `DuplicateMethodCall` Reek warnings.
50
+ - **`Fingerprinter.source_fingerprint`** (#37) — extracted `schema_path(root)` and `read_source_content(paths)` private helpers to reduce method statement count.
51
+ - **`RubySearch`** (#35) — wrapped the 5 search params into a `SearchParams` struct to resolve the `TooManyInstanceVariables` Reek warning; extracted `secret_file?(basename)` from `skip_file?` to fix `FeatureEnvy`; added `SECRET_EXTENSIONS` constant.
52
+ - **`RipgrepSearch::CommandBuilder`** (#35) — moved hardcoded secret file globs to a `SECRET_EXCLUDES` constant; renamed helpers to `excluded_path_flags` / `secret_exclude_flags`; added `# :reek:UtilityFunction` suppressions for intentional stateless helpers.
53
+ - **`Validator`** (#35) — extracted `effective_max_bytes`, `present?`, `normalize_extension`, `safe_extension?`, `build_search_path`, `within_root?`, `path_not_found`, and `pattern_too_long_error` helpers. Fixes `DuplicateMethodCall` on `BaseTool.text_response("Path not found: ...")` in `validate_path_security`.
54
+ - **`SourceMacroExtractor`** (#35) — split `add_attachment_macros` into three single-step helpers (`add_single_attached`, `add_many_attached`, `add_rich_text`) to reduce statement counts.
55
+ - **Rake namespace splitting** (#35) — `namespace :ai` reopened across multiple smaller blocks in `rails_ai_bridge.rake` to comply with `Metrics/BlockLength` RuboCop limit.
56
+
57
+ ### Fixed
58
+
59
+ - **`ASSISTANT_TABLE` constant redefinition warning** (#35) — wrapped constant definition in `unless defined?` to prevent warnings when Rake tasks are loaded multiple times in test environments.
60
+
61
+ ### Tests
62
+
63
+ - Added **68 new examples** covering:
64
+ - `TimedRunner` — result forwarding, error capture, monotonic duration, error-path duration
65
+ - `ParallelRunner` — config-driven pool size, per-future timeout, pool shutdown, mixed success/failure, `available?` with pool-size and missing-constant edge cases
66
+ - `Introspector` — sequential `TimedRunner` wiring (plain result, no `duration_ms` envelope), error capture in sequential mode, debug log assertion
67
+ - `AppOverviewFormatter` — nil/error guards, optional fields, field ordering
68
+ - `GemsFormatter` — nil/error guards, total count, Notable Gems section, category+name sort order
69
+ - `MigrationsFormatter` — nil/error guards, schema version, pending migrations count, recent migrations with and without actions
70
+ - `RubySearch` / `FileProcessor` — pattern matching, max_results cap, secret file skipping (`.env`, `.key`, `.pem`, `.p12`, `.pfx`, `.crt`), excluded paths, file_type filtering, case-insensitive search, relative paths, unreadable file recovery, `:full` return signal
71
+ - `Fingerprinter` — restored `.compute` and `.changed?` unit tests; added `db/structure.sql` fallback and schema.rb-wins-when-both-exist edge cases
72
+ - `FreshnessHeader` — backward-compatible parsing of headers without gem version
73
+ - **Total: 1,745 examples, 0 failures, 94.49% line coverage** (up from 94.04%)
74
+
8
75
  ## [3.2.0] - 2026-05-04
9
76
 
10
77
  ### Added
data/README.md CHANGED
@@ -261,7 +261,7 @@ The gem exposes **12 built-in tools** via MCP that AI clients call on-demand (ho
261
261
  | `rails_get_conventions` | Architecture patterns, directory structure |
262
262
  | `rails_search_code` | Ripgrep (or Ruby) search under `Rails.root` with allowlisted extensions, pattern size cap, and optional wall-clock timeout |
263
263
  | `rails_get_view` | View layouts, templates, partials; optional per-file detail under the configured `app/views` path |
264
- | `rails_search_semantic` | Semantic code search using rubydex — find declarations by name with types, locations, and relationships (requires rubydex) |
264
+ | `rails_search_semantic` | Semantic code search using rubydex — find declarations by name with types, locations, and relationships |
265
265
  | `rails_get_stimulus` | Stimulus controllers: targets, values, actions, outlets (requires `:stimulus` introspector) |
266
266
 
267
267
  All tools are **read-only** — they never modify your application or database.
@@ -485,6 +485,9 @@ end
485
485
  | `http_port` | `6029` | HTTP server port |
486
486
  | `cache_ttl` | `30` | Cache TTL in seconds |
487
487
  | `watcher_formats` | `:all` | Formats regenerated by `rails ai:watch` (e.g. `%i[claude cursor]` to limit churn) |
488
+ | `parallel_introspection` | `false` | Run introspectors concurrently (requires `concurrent-ruby`, which is already a Rails dependency) |
489
+ | `parallel_pool_size` | `4` | Max threads in the parallel pool; capped at the number of active introspectors so no idle threads are created |
490
+ | `parallel_timeout_seconds` | `10` | Per-introspector future timeout (seconds); timed-out introspectors return `{ error: "timed out after Ns" }` without blocking the others |
488
491
 
489
492
  Other HTTP MCP knobs live only on the nested object, for example `RailsAiBridge.configuration.mcp.authorize`, `mcp.mode`, `mcp.security_profile`, and `mcp.require_auth_in_production` — see [docs/GUIDE.md](docs/GUIDE.md) and [docs/mcp-security.md](docs/mcp-security.md).
490
493
  </details>
@@ -513,19 +516,17 @@ Built-in MCP tools and resources now read through a shared runtime context provi
513
516
 
514
517
  ### Rubydex Integration (Semantic Code Analysis)
515
518
 
516
- [Rubydex](https://github.com/Shopify/rubydex) is Shopify's high-performance Ruby static analysis toolkit. When installed, rails-ai-bridge can leverage rubydex for semantic code understanding:
519
+ [Rubydex](https://github.com/Shopify/rubydex) is Shopify's high-performance Ruby static analysis toolkit. rails-ai-bridge leverages rubydex out-of-the-box for semantic code understanding.
517
520
 
518
521
  **Setup:**
519
522
 
520
- 1. Add rubydex to your Gemfile:
521
- ```ruby
522
- gem 'rubydex', '>= 0.1.0.beta9'
523
- ```
523
+ Rubydex is installed automatically as a core dependency and is enabled by default.
524
+
525
+ To customize it in your initializer (`config/initializers/rails_ai_bridge.rb`):
524
526
 
525
- 2. Enable in your initializer:
526
527
  ```ruby
527
528
  RailsAiBridge.configure do |config|
528
- config.rubydex_enabled = true
529
+ # config.rubydex_enabled = false # Set to false to disable semantic analysis
529
530
 
530
531
  # Optional: enable the semantic introspector (adds :semantic to introspectors)
531
532
  config.semantic_introspector_enabled = true
@@ -533,6 +534,10 @@ Built-in MCP tools and resources now read through a shared runtime context provi
533
534
 
534
535
  # Optional: control semantic context depth in generated files
535
536
  # config.semantic_context_depth = :standard # :summary, :standard, or :full
537
+
538
+ # Optional: incremental re-indexing (skip unchanged files on subsequent runs)
539
+ # config.rubydex_incremental_threshold = 0.3 # full rebuild if >30% of files changed
540
+ # config.rubydex_persist_index = true # survive process restarts
536
541
  end
537
542
  ```
538
543
 
@@ -545,19 +550,22 @@ Built-in MCP tools and resources now read through a shared runtime context provi
545
550
  | Semantic introspector | Codebase-wide pattern detection, inheritance analysis, and complexity hotspots |
546
551
  | `rails://semantic/analysis` resource | Full semantic analysis accessible via MCP resource read |
547
552
  | Context file enrichment | Semantic insights automatically included in generated AI context files |
553
+ | Incremental re-indexing | Only changed files are re-parsed on subsequent calls; unchanged files are skipped |
548
554
 
549
- **Internal architecture:** The `RubydexAdapter` wraps the rubydex API as a thin coordinator delegating to three single-responsibility collaborators: `Serializer` (hash conversion), `Indexer` (graph construction + source file scanning), and `MethodCounter` (flat method-counting pipeline). See [CONTRIBUTING.md](CONTRIBUTING.md#project-structure) for the full project layout.
555
+ **Internal architecture:** The `RubydexAdapter` wraps the rubydex API as a thin coordinator delegating to three single-responsibility collaborators: `Serializer` (hash conversion), `Indexer` (graph construction + source file scanning), and `MethodCounter` (flat method-counting pipeline). Incremental re-indexing is handled by the `IncrementalIndexer` service, which tracks file mtimes and triggers a full rebuild when the changed-file ratio exceeds `rubydex_incremental_threshold`. See [CONTRIBUTING.md](CONTRIBUTING.md#project-structure) for the full project layout.
550
556
 
551
557
  **Configuration options:**
552
558
 
553
559
  | Option | Default | Description |
554
560
  |--------|---------|-------------|
555
- | `rubydex_enabled` | `false` | Enable rubydex integration |
556
- | `rubydex_index_path` | `"tmp/rubydex_index"` | Path for the rubydex index |
561
+ | `rubydex_enabled` | `true` | Enable rubydex integration |
562
+ | `rubydex_index_path` | `"tmp/rubydex_index"` | Path for the rubydex index (must stay within `Rails.root`) |
557
563
  | `semantic_introspector_enabled` | `false` | Enable the dedicated semantic introspector |
558
- | `semantic_context_depth` | `:standard` | Depth of semantic context in generated files |
564
+ | `semantic_context_depth` | `:standard` | Depth of semantic context in generated files (`:summary`, `:standard`, `:full`) |
565
+ | `rubydex_incremental_threshold` | `0.3` | Fraction of changed files (0.0–1.0) above which a full rebuild replaces incremental patching |
566
+ | `rubydex_persist_index` | `false` | Persist the mtime snapshot to disk so incremental re-indexing survives process restarts |
559
567
 
560
- **Performance considerations:** Rubydex indexes your Ruby source files at startup. For large codebases, the initial indexing may take a few seconds. The index is cached in memory and shared across MCP tool calls.
568
+ **Performance considerations:** Rubydex indexes your Ruby source files at startup. For large codebases, the initial indexing may take a few seconds. Subsequent calls re-use the in-memory index and only re-parse changed files, keeping re-indexing overhead proportional to churn rather than codebase size.
561
569
 
562
570
  ---
563
571