rails-ai-context 5.30.0 → 5.30.2
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 +159 -0
- data/docs/ARCHITECTURE.md +5 -1
- data/docs/CLI.md +1 -1
- data/docs/COMPATIBILITY.md +7 -1
- data/docs/CONFIGURATION.md +2 -2
- data/docs/FAQ.md +1 -1
- data/docs/GUIDE.md +1 -1
- data/exe/rails-ai-context +3 -4
- data/lib/rails_ai_context/action_filters.rb +69 -6
- data/lib/rails_ai_context/boot_manager.rb +21 -5
- data/lib/rails_ai_context/cli/entry_boot.rb +3 -2
- data/lib/rails_ai_context/cli/tool_runner.rb +22 -3
- data/lib/rails_ai_context/concern_paths.rb +2 -1
- data/lib/rails_ai_context/configuration.rb +6 -6
- data/lib/rails_ai_context/erb_source.rb +22 -3
- data/lib/rails_ai_context/introspector.rb +13 -2
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +43 -16
- data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +10 -15
- data/lib/rails_ai_context/introspectors/eager_load.rb +22 -10
- data/lib/rails_ai_context/introspectors/i18n_introspector.rb +4 -3
- data/lib/rails_ai_context/introspectors/job_introspector.rb +24 -10
- data/lib/rails_ai_context/introspectors/listeners/associations_listener.rb +12 -0
- data/lib/rails_ai_context/introspectors/listeners/migration_replay_listener.rb +9 -1
- data/lib/rails_ai_context/introspectors/listeners/mount_listener.rb +13 -6
- data/lib/rails_ai_context/introspectors/listeners/schema_dsl_listener.rb +83 -27
- data/lib/rails_ai_context/introspectors/listeners/with_options_scope.rb +49 -12
- data/lib/rails_ai_context/introspectors/migration_replay/statements.rb +15 -0
- data/lib/rails_ai_context/introspectors/migration_replay.rb +2 -0
- data/lib/rails_ai_context/introspectors/model_introspector.rb +14 -3
- data/lib/rails_ai_context/introspectors/pg_partitions.rb +73 -0
- data/lib/rails_ai_context/introspectors/route_introspector.rb +62 -4
- data/lib/rails_ai_context/introspectors/schema_conventions.rb +17 -2
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +13 -7
- data/lib/rails_ai_context/introspectors/schema_reader.rb +28 -2
- data/lib/rails_ai_context/introspectors/structure_sql_reader.rb +8 -4
- data/lib/rails_ai_context/introspectors/view_template_introspector.rb +18 -16
- data/lib/rails_ai_context/migration_status.rb +6 -1
- data/lib/rails_ai_context/pending_migrations.rb +3 -2
- data/lib/rails_ai_context/portable_path.rb +14 -0
- data/lib/rails_ai_context/schema_adapter.rb +6 -1
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +1 -1
- data/lib/rails_ai_context/serializers/context_file_serializer.rb +2 -2
- data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +5 -0
- data/lib/rails_ai_context/tasks/rails_ai_context.rake +1 -1
- data/lib/rails_ai_context/tools/base_tool.rb +9 -8
- data/lib/rails_ai_context/tools/diagnose.rb +4 -0
- data/lib/rails_ai_context/tools/generate_test.rb +19 -0
- data/lib/rails_ai_context/tools/get_model_details.rb +1 -1
- data/lib/rails_ai_context/tools/get_partial_interface.rb +18 -4
- data/lib/rails_ai_context/tools/get_routes.rb +19 -9
- data/lib/rails_ai_context/tools/get_schema.rb +24 -5
- data/lib/rails_ai_context/tools/get_service_pattern.rb +44 -25
- data/lib/rails_ai_context/tools/get_test_info.rb +8 -0
- data/lib/rails_ai_context/tools/migration_advisor.rb +1 -1
- data/lib/rails_ai_context/tools/query.rb +8 -7
- data/lib/rails_ai_context/tools/runtime_info.rb +13 -10
- data/lib/rails_ai_context/tools/validate_semantics.rb +56 -14
- data/lib/rails_ai_context/version.rb +1 -1
- data/server.json +1 -1
- metadata +2 -1
|
@@ -39,6 +39,8 @@ module RailsAiContext
|
|
|
39
39
|
@singleton_method_names_by_scope = Hash.new { |hash, key| hash[key] = Set.new }
|
|
40
40
|
@scope_stack = [ TOP_LEVEL_SCOPE ]
|
|
41
41
|
@method_context_stack = []
|
|
42
|
+
@uncertain_blocks = 0
|
|
43
|
+
@hook_params = []
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def local_route_method_defined?(helper, scope, method_kind)
|
|
@@ -64,18 +66,24 @@ module RailsAiContext
|
|
|
64
66
|
def visit_def_node(node)
|
|
65
67
|
method_kind = node.receiver ? :singleton : :instance
|
|
66
68
|
method_names_for(method_kind, current_scope) << node.name.to_s
|
|
69
|
+
hook = Introspectors::Listeners::WithOptionsScope.hook_param(node)
|
|
70
|
+
@hook_params << hook if hook
|
|
67
71
|
with_method_context(method_kind) { super }
|
|
72
|
+
ensure
|
|
73
|
+
@hook_params.pop if hook
|
|
68
74
|
end
|
|
69
75
|
|
|
70
76
|
def visit_call_node(node)
|
|
71
77
|
case node.name
|
|
72
78
|
when :render then extract_render(node)
|
|
73
|
-
when :validates then extract_validates(node)
|
|
79
|
+
when :validates then extract_validates(node) if @uncertain_blocks.zero?
|
|
74
80
|
when :permit then extract_permit(node)
|
|
75
81
|
# An encrypted attribute (Lockbox's has_encrypted keeps `<name>_ciphertext`)
|
|
76
82
|
# and a store key are attributes with no column of their own name.
|
|
77
|
-
when :attribute, :attr_accessor, :attr_writer, :has_encrypted, :encrypts, :attr_encrypted
|
|
83
|
+
when :attribute, :attr_accessor, :attr_reader, :attr_writer, :has_encrypted, :encrypts, :attr_encrypted
|
|
78
84
|
extract_virtual_attributes(node)
|
|
85
|
+
# `alias_attribute :name, :lastname` reads the second name's column.
|
|
86
|
+
when :alias_attribute then extract_virtual_attributes(node, only: 1)
|
|
79
87
|
when :store_accessor then extract_virtual_attributes(node, skip: 1)
|
|
80
88
|
when :store then extract_store_accessors(node)
|
|
81
89
|
else
|
|
@@ -86,11 +94,15 @@ module RailsAiContext
|
|
|
86
94
|
scope: current_scope,
|
|
87
95
|
method_kind: current_method_kind
|
|
88
96
|
}
|
|
89
|
-
elsif CALLBACK_NAMES.include?(node.name) && node.receiver.nil?
|
|
97
|
+
elsif CALLBACK_NAMES.include?(node.name) && node.receiver.nil? && @uncertain_blocks.zero?
|
|
90
98
|
extract_callback(node)
|
|
91
99
|
end
|
|
92
100
|
end
|
|
101
|
+
uncertain = Introspectors::Listeners::WithOptionsScope.uncertain_block?(node) { |name| @hook_params.include?(name) }
|
|
102
|
+
@uncertain_blocks += 1 if uncertain
|
|
93
103
|
super
|
|
104
|
+
ensure
|
|
105
|
+
@uncertain_blocks -= 1 if uncertain
|
|
94
106
|
end
|
|
95
107
|
|
|
96
108
|
private
|
|
@@ -167,10 +179,11 @@ module RailsAiContext
|
|
|
167
179
|
# `attribute :foo` and `attr_accessor :foo` are real readers with no
|
|
168
180
|
# column behind them, and a migration for one is a column nobody
|
|
169
181
|
# wants.
|
|
170
|
-
def extract_virtual_attributes(node, skip: 0)
|
|
182
|
+
def extract_virtual_attributes(node, skip: 0, only: nil)
|
|
171
183
|
return unless node.receiver.nil?
|
|
172
184
|
|
|
173
|
-
(node.arguments&.arguments || []).drop(skip)
|
|
185
|
+
args = (node.arguments&.arguments || []).drop(skip)
|
|
186
|
+
(only ? args.first(only) : args).each do |arg|
|
|
174
187
|
@virtual_attributes << arg.unescaped if arg.is_a?(Prism::SymbolNode)
|
|
175
188
|
end
|
|
176
189
|
end
|
|
@@ -488,7 +501,7 @@ module RailsAiContext
|
|
|
488
501
|
columns = table_columns.dup
|
|
489
502
|
model_data[:associations]&.each do |a|
|
|
490
503
|
columns << a[:name] if a[:name]
|
|
491
|
-
columns
|
|
504
|
+
columns.merge(Array(a[:foreign_key]))
|
|
492
505
|
end
|
|
493
506
|
|
|
494
507
|
{ columns: columns, table_columns: table_columns, table: table_name, model: model_name, model_data: model_data }
|
|
@@ -518,7 +531,7 @@ module RailsAiContext
|
|
|
518
531
|
|
|
519
532
|
valid = Set.new
|
|
520
533
|
table_data[:columns]&.each { |c| valid << c[:name] }
|
|
521
|
-
model_data[:associations]&.each { |a| valid << a[:name]; valid
|
|
534
|
+
model_data[:associations]&.each { |a| valid << a[:name]; valid.merge(Array(a[:foreign_key])) }
|
|
522
535
|
valid.merge(%w[id _destroy created_at updated_at])
|
|
523
536
|
|
|
524
537
|
# When JSONB columns exist, plain-word params may be keys inside JSONB columns.
|
|
@@ -644,6 +657,7 @@ module RailsAiContext
|
|
|
644
657
|
|
|
645
658
|
declared = Introspectors::SourceIntrospector
|
|
646
659
|
.walk_source(content.to_s, { associations: Introspectors::Listeners::AssociationsListener })[:associations]
|
|
660
|
+
.reject { |a| a[:scope_uncertain] }
|
|
647
661
|
.map { |a| a[:name].to_s }.to_set
|
|
648
662
|
|
|
649
663
|
(model_data[:associations] || []).each do |assoc|
|
|
@@ -671,26 +685,54 @@ module RailsAiContext
|
|
|
671
685
|
|
|
672
686
|
table_name = model_data[:table_name]
|
|
673
687
|
table_data = schema[:tables] && schema[:tables][table_name]
|
|
674
|
-
|
|
688
|
+
# A static table whose block called what no reader interprets has columns and indexes unknown.
|
|
689
|
+
return warnings if table_data.nil? || table_data[:unread_calls]
|
|
675
690
|
|
|
676
691
|
# Only flag columns that are ACTUAL foreign keys (declared via add_foreign_key or belongs_to)
|
|
677
692
|
declared_fk_columns = (table_data[:foreign_keys] || []).map { |fk| fk[:column] }
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
693
|
+
belongs_to = (model_data[:associations] || []).select { |a| a[:type] == "belongs_to" && column_key?(a) }
|
|
694
|
+
table_columns = Array(table_data[:columns]).map { |c| c[:name].to_s }
|
|
695
|
+
# A key the table lacks is a broken association, not an unindexed column.
|
|
696
|
+
absent, belongs_to = belongs_to.partition do |a|
|
|
697
|
+
table_columns.any? && !(Array(a[:foreign_key]).map(&:to_s) - table_columns).empty?
|
|
698
|
+
end
|
|
699
|
+
absent.each do |a|
|
|
700
|
+
warnings << "belongs_to :#{a[:name]} - column \"#{Introspectors::SchemaConventions.key_text(a[:foreign_key])}\" not found in #{table_name} table. " \
|
|
701
|
+
"Fix: pass `foreign_key:` naming the real column, or add the column"
|
|
702
|
+
end
|
|
703
|
+
fk_columns = (declared_fk_columns + belongs_to.map { |a| a[:foreign_key] }).uniq
|
|
683
704
|
|
|
684
705
|
indexed = Introspectors::SchemaConventions.lookup_indexed_columns(table_data)
|
|
706
|
+
polymorphic_pairs = (model_data[:associations] || [])
|
|
707
|
+
.select { |a| a[:type] == "belongs_to" && a[:polymorphic] && a[:foreign_key] }
|
|
708
|
+
.to_h { |a| [ a[:foreign_key].to_s, [ (a[:foreign_type] || a.dig(:options, :foreign_type) || "#{a[:name]}_type").to_s, a[:foreign_key].to_s ] ] }
|
|
685
709
|
|
|
686
710
|
fk_columns.each do |col|
|
|
687
|
-
|
|
711
|
+
# `t.references polymorphic: true` indexes [type, id], led by the type.
|
|
712
|
+
pair = polymorphic_pairs[col.to_s]
|
|
713
|
+
next if pair && Introspectors::SchemaConventions.leading_index?(table_data[:indexes], pair)
|
|
714
|
+
|
|
715
|
+
if col.is_a?(Array)
|
|
716
|
+
primary_key = { columns: Array(table_data[:primary_key] || table_data.dig(:options, :primary_key)) }
|
|
717
|
+
next if Introspectors::SchemaConventions.leading_index?(Array(table_data[:indexes]) + [ primary_key ], col)
|
|
718
|
+
|
|
719
|
+
warnings << "#{Introspectors::SchemaConventions.key_text(col)} in #{table_name} - foreign key without an index leading with its columns (slow queries). " \
|
|
720
|
+
"Fix: `add_index :#{table_name}, [#{col.map { |c| ":#{c}" }.join(", ")}]`"
|
|
721
|
+
elsif !indexed.include?(col)
|
|
688
722
|
warnings << "#{col} in #{table_name} - foreign key without index (slow queries). Fix: `rails g migration AddIndexTo#{table_name.camelize} #{col}:index`"
|
|
689
723
|
end
|
|
690
724
|
end
|
|
691
725
|
warnings
|
|
692
726
|
end
|
|
693
727
|
|
|
728
|
+
# A key the source names as columns, on a declaration the class itself makes.
|
|
729
|
+
private_class_method def self.column_key?(assoc)
|
|
730
|
+
return false if assoc[:computed_foreign_key] || assoc[:scope_uncertain]
|
|
731
|
+
|
|
732
|
+
columns = Array(assoc[:foreign_key])
|
|
733
|
+
columns.any? && columns.all? { |c| c.to_s.match?(/\A\w+\z/) }
|
|
734
|
+
end
|
|
735
|
+
|
|
694
736
|
# ── CHECK 9: Stimulus controller existence ───────────────────────
|
|
695
737
|
|
|
696
738
|
private_class_method def self.check_stimulus_controllers(content, context)
|
data/server.json
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails-ai-context
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.30.
|
|
4
|
+
version: 5.30.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- crisnahine
|
|
@@ -398,6 +398,7 @@ files:
|
|
|
398
398
|
- lib/rails_ai_context/introspectors/node_source.rb
|
|
399
399
|
- lib/rails_ai_context/introspectors/observability_introspector.rb
|
|
400
400
|
- lib/rails_ai_context/introspectors/performance_introspector.rb
|
|
401
|
+
- lib/rails_ai_context/introspectors/pg_partitions.rb
|
|
401
402
|
- lib/rails_ai_context/introspectors/rake_task_introspector.rb
|
|
402
403
|
- lib/rails_ai_context/introspectors/retry_policy.rb
|
|
403
404
|
- lib/rails_ai_context/introspectors/route_introspector.rb
|