rails-ai-context 5.30.2 → 5.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +868 -0
- data/CONTEXT.md +7 -3
- data/README.md +1 -1
- data/docs/ARCHITECTURE.md +3 -3
- data/docs/CLI.md +1 -1
- data/docs/COMPATIBILITY.md +50 -14
- data/docs/CONFIGURATION.md +4 -1
- data/docs/FAQ.md +1 -1
- data/docs/GUIDE.md +14 -8
- data/docs/INTROSPECTORS.md +50 -23
- data/docs/SECURITY.md +56 -1
- data/docs/TOOLS.md +58 -12
- data/lib/generators/rails_ai_context/install/install_generator.rb +1 -0
- data/lib/rails_ai_context/action_filters.rb +121 -48
- data/lib/rails_ai_context/app_commands.rb +2 -2
- data/lib/rails_ai_context/app_kind.rb +65 -8
- data/lib/rails_ai_context/ast_cache.rb +53 -18
- data/lib/rails_ai_context/base_mixins.rb +46 -16
- data/lib/rails_ai_context/boot_manager.rb +5 -1
- data/lib/rails_ai_context/cli/entry_boot.rb +17 -2
- data/lib/rails_ai_context/cli/tool_runner.rb +15 -1
- data/lib/rails_ai_context/concern_macros.rb +791 -111
- data/lib/rails_ai_context/concern_membership.rb +25 -5
- data/lib/rails_ai_context/concern_paths.rb +63 -17
- data/lib/rails_ai_context/config_yaml.rb +40 -0
- data/lib/rails_ai_context/configuration.rb +3 -3
- data/lib/rails_ai_context/data/docs/index.json +324 -0
- data/lib/rails_ai_context/database_yml.rb +136 -0
- data/lib/rails_ai_context/doctor.rb +30 -37
- data/lib/rails_ai_context/erb_source.rb +40 -0
- data/lib/rails_ai_context/fingerprinter.rb +48 -22
- data/lib/rails_ai_context/fixture_keys.rb +42 -1
- data/lib/rails_ai_context/gem_lock.rb +275 -47
- data/lib/rails_ai_context/git_ignore.rb +13 -11
- data/lib/rails_ai_context/hydrators/hydration_formatter.rb +8 -2
- data/lib/rails_ai_context/hydrators/schema_hint_builder.rb +10 -5
- data/lib/rails_ai_context/introspector.rb +10 -1
- data/lib/rails_ai_context/introspectors/action_mailbox_introspector.rb +27 -19
- data/lib/rails_ai_context/introspectors/action_presence.rb +11 -8
- data/lib/rails_ai_context/introspectors/action_resolver.rb +38 -26
- data/lib/rails_ai_context/introspectors/action_text_introspector.rb +2 -2
- data/lib/rails_ai_context/introspectors/active_record_settings.rb +56 -0
- data/lib/rails_ai_context/introspectors/active_storage_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/active_support_introspector.rb +98 -22
- data/lib/rails_ai_context/introspectors/admin_resources.rb +101 -0
- data/lib/rails_ai_context/introspectors/anyway_configs.rb +64 -0
- data/lib/rails_ai_context/introspectors/apartment_config.rb +47 -0
- data/lib/rails_ai_context/introspectors/api_introspector.rb +125 -26
- data/lib/rails_ai_context/introspectors/asset_pipeline_introspector.rb +24 -19
- data/lib/rails_ai_context/introspectors/ast_walk.rb +18 -0
- data/lib/rails_ai_context/introspectors/auth_introspector.rb +71 -18
- data/lib/rails_ai_context/introspectors/autoload_introspector.rb +62 -15
- data/lib/rails_ai_context/introspectors/base.rb +38 -0
- data/lib/rails_ai_context/introspectors/call_site_expansion.rb +349 -39
- data/lib/rails_ai_context/introspectors/component_introspector.rb +26 -14
- data/lib/rails_ai_context/introspectors/config_introspector.rb +22 -9
- data/lib/rails_ai_context/introspectors/connection_pool_introspector.rb +1 -4
- data/lib/rails_ai_context/introspectors/controller_filters.rb +335 -31
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +315 -129
- data/lib/rails_ai_context/introspectors/controller_settings.rb +182 -0
- data/lib/rails_ai_context/introspectors/convention_introspector.rb +18 -15
- data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/declared_constant.rb +108 -23
- data/lib/rails_ai_context/introspectors/eager_load.rb +12 -7
- data/lib/rails_ai_context/introspectors/engine_introspector.rb +13 -5
- data/lib/rails_ai_context/introspectors/env_config_introspector.rb +62 -17
- data/lib/rails_ai_context/introspectors/env_introspector.rb +8 -6
- data/lib/rails_ai_context/introspectors/env_references.rb +21 -4
- data/lib/rails_ai_context/introspectors/frontend_framework_introspector.rb +37 -22
- data/lib/rails_ai_context/introspectors/gem_introspector.rb +3 -1
- data/lib/rails_ai_context/introspectors/gemfile_gems.rb +53 -5
- data/lib/rails_ai_context/introspectors/grape_endpoints.rb +109 -0
- data/lib/rails_ai_context/introspectors/helper_names.rb +12 -1
- data/lib/rails_ai_context/introspectors/i18n_introspector.rb +101 -46
- data/lib/rails_ai_context/introspectors/includers.rb +6 -1
- data/lib/rails_ai_context/introspectors/initializer_introspector.rb +2 -5
- data/lib/rails_ai_context/introspectors/job_introspector.rb +548 -103
- data/lib/rails_ai_context/introspectors/listener_registration.rb +11 -1
- data/lib/rails_ai_context/introspectors/listeners/associations_listener.rb +51 -4
- data/lib/rails_ai_context/introspectors/listeners/autoload_ignore_listener.rb +31 -0
- data/lib/rails_ai_context/introspectors/listeners/autoload_paths_listener.rb +4 -47
- data/lib/rails_ai_context/introspectors/listeners/base_listener.rb +80 -14
- data/lib/rails_ai_context/introspectors/listeners/branch_conditions.rb +156 -0
- data/lib/rails_ai_context/introspectors/listeners/callbacks_listener.rb +85 -23
- data/lib/rails_ai_context/introspectors/listeners/class_definition_listener.rb +35 -6
- data/lib/rails_ai_context/introspectors/listeners/component_structure_listener.rb +30 -2
- data/lib/rails_ai_context/introspectors/listeners/conditional_macro_listener.rb +19 -0
- data/lib/rails_ai_context/introspectors/listeners/config_assignment_listener.rb +255 -10
- data/lib/rails_ai_context/introspectors/listeners/constant_reference_listener.rb +34 -0
- data/lib/rails_ai_context/introspectors/listeners/constructor_macro_listener.rb +123 -0
- data/lib/rails_ai_context/introspectors/listeners/definition_file_paths_listener.rb +66 -0
- data/lib/rails_ai_context/introspectors/listeners/enums_listener.rb +2 -2
- data/lib/rails_ai_context/introspectors/listeners/env_access_listener.rb +31 -0
- data/lib/rails_ai_context/introspectors/listeners/filter_macro_listener.rb +54 -0
- data/lib/rails_ai_context/introspectors/listeners/fixture_paths_listener.rb +62 -0
- data/lib/rails_ai_context/introspectors/listeners/gemfile_dsl_listener.rb +42 -1
- data/lib/rails_ai_context/introspectors/listeners/generic_macro_listener.rb +28 -2
- data/lib/rails_ai_context/introspectors/listeners/grape_api_listener.rb +137 -0
- data/lib/rails_ai_context/introspectors/listeners/http_client_call_listener.rb +50 -0
- data/lib/rails_ai_context/introspectors/listeners/i18n_load_path_listener.rb +54 -0
- data/lib/rails_ai_context/introspectors/listeners/literal_paths.rb +123 -0
- data/lib/rails_ai_context/introspectors/listeners/macros_listener.rb +248 -4
- data/lib/rails_ai_context/introspectors/listeners/mailbox_routing_listener.rb +2 -1
- data/lib/rails_ai_context/introspectors/listeners/method_call_listener.rb +7 -1
- data/lib/rails_ai_context/introspectors/listeners/methods_listener.rb +352 -69
- data/lib/rails_ai_context/introspectors/listeners/migration_dsl_listener.rb +1 -1
- data/lib/rails_ai_context/introspectors/listeners/migration_replay_listener.rb +35 -26
- data/lib/rails_ai_context/introspectors/listeners/mixins_listener.rb +62 -29
- data/lib/rails_ai_context/introspectors/listeners/mount_listener.rb +8 -0
- data/lib/rails_ai_context/introspectors/listeners/namespaced_roots_listener.rb +35 -0
- data/lib/rails_ai_context/introspectors/listeners/nested_constants_listener.rb +32 -0
- data/lib/rails_ai_context/introspectors/listeners/owner_scope.rb +67 -0
- data/lib/rails_ai_context/introspectors/listeners/preview_paths_listener.rb +16 -7
- data/lib/rails_ai_context/introspectors/listeners/proc_literal_listener.rb +42 -0
- data/lib/rails_ai_context/introspectors/listeners/queue_assignment_listener.rb +54 -0
- data/lib/rails_ai_context/introspectors/listeners/rake_task_dsl_listener.rb +21 -8
- data/lib/rails_ai_context/introspectors/listeners/route_files_listener.rb +4 -11
- data/lib/rails_ai_context/introspectors/listeners/routes_dsl_listener.rb +461 -65
- data/lib/rails_ai_context/introspectors/listeners/schema_dsl_listener.rb +76 -14
- data/lib/rails_ai_context/introspectors/listeners/scopes_listener.rb +40 -1
- data/lib/rails_ai_context/introspectors/listeners/validations_listener.rb +10 -1
- data/lib/rails_ai_context/introspectors/listeners/view_paths_listener.rb +37 -0
- data/lib/rails_ai_context/introspectors/middleware_introspector.rb +47 -3
- data/lib/rails_ai_context/introspectors/migration_introspector.rb +3 -3
- data/lib/rails_ai_context/introspectors/migration_replay/helpers.rb +4 -3
- data/lib/rails_ai_context/introspectors/migration_replay/statements.rb +72 -12
- data/lib/rails_ai_context/introspectors/migration_replay.rb +110 -14
- data/lib/rails_ai_context/introspectors/model_introspector.rb +708 -214
- data/lib/rails_ai_context/introspectors/module_aliases.rb +11 -3
- data/lib/rails_ai_context/introspectors/multi_database_introspector.rb +9 -57
- data/lib/rails_ai_context/introspectors/observability_introspector.rb +5 -8
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +28 -8
- data/lib/rails_ai_context/introspectors/rake_task_introspector.rb +118 -51
- data/lib/rails_ai_context/introspectors/recurring_schedules.rb +150 -0
- data/lib/rails_ai_context/introspectors/rendered_record.rb +87 -0
- data/lib/rails_ai_context/introspectors/retry_policy.rb +8 -5
- data/lib/rails_ai_context/introspectors/route_introspector.rb +153 -42
- data/lib/rails_ai_context/introspectors/schema_conventions.rb +127 -40
- data/lib/rails_ai_context/introspectors/schema_dump_path.rb +80 -0
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +346 -112
- data/lib/rails_ai_context/introspectors/schema_reader.rb +69 -29
- data/lib/rails_ai_context/introspectors/service_classes.rb +21 -15
- data/lib/rails_ai_context/introspectors/source_calls.rb +1 -1
- data/lib/rails_ai_context/introspectors/source_introspector.rb +17 -2
- data/lib/rails_ai_context/introspectors/source_scan.rb +174 -26
- data/lib/rails_ai_context/introspectors/sqlite_virtual_tables.rb +28 -0
- data/lib/rails_ai_context/introspectors/stimulus_introspector.rb +16 -5
- data/lib/rails_ai_context/introspectors/structure_sql_reader.rb +372 -98
- data/lib/rails_ai_context/introspectors/superclass_chain.rb +14 -15
- data/lib/rails_ai_context/introspectors/table_name.rb +46 -45
- data/lib/rails_ai_context/introspectors/test_introspector.rb +289 -78
- data/lib/rails_ai_context/introspectors/turbo_introspector.rb +10 -5
- data/lib/rails_ai_context/introspectors/view_introspector.rb +5 -40
- data/lib/rails_ai_context/introspectors/view_template_introspector.rb +12 -10
- data/lib/rails_ai_context/method_name.rb +31 -0
- data/lib/rails_ai_context/migration_status.rb +5 -5
- data/lib/rails_ai_context/output_guard.rb +6 -4
- data/lib/rails_ai_context/package_json.rb +96 -5
- data/lib/rails_ai_context/path_resolver.rb +170 -15
- data/lib/rails_ai_context/payload.rb +48 -0
- data/lib/rails_ai_context/pending_migrations.rb +15 -18
- data/lib/rails_ai_context/portable_path.rb +76 -4
- data/lib/rails_ai_context/redaction.rb +8 -2
- data/lib/rails_ai_context/route_coverage.rb +5 -1
- data/lib/rails_ai_context/safe_file.rb +4 -1
- data/lib/rails_ai_context/safe_path.rb +61 -10
- data/lib/rails_ai_context/schema_hint.rb +8 -2
- data/lib/rails_ai_context/schema_version.rb +7 -9
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +1 -1
- data/lib/rails_ai_context/serializers/markdown_serializer.rb +13 -4
- data/lib/rails_ai_context/serializers/section_facts.rb +5 -1
- data/lib/rails_ai_context/serializers/stack_overview_helper.rb +2 -2
- data/lib/rails_ai_context/serializers/tool_guide_helper.rb +7 -4
- data/lib/rails_ai_context/server.rb +26 -7
- data/lib/rails_ai_context/test_framework.rb +32 -5
- data/lib/rails_ai_context/tools/analyze_feature.rb +22 -10
- data/lib/rails_ai_context/tools/base_tool.rb +69 -30
- data/lib/rails_ai_context/tools/diagnose.rb +1 -1
- data/lib/rails_ai_context/tools/generate_test.rb +8 -9
- data/lib/rails_ai_context/tools/get_active_support.rb +19 -4
- data/lib/rails_ai_context/tools/get_api.rb +17 -11
- data/lib/rails_ai_context/tools/get_autoload.rb +10 -5
- data/lib/rails_ai_context/tools/get_callbacks.rb +65 -14
- data/lib/rails_ai_context/tools/get_component_catalog.rb +7 -4
- data/lib/rails_ai_context/tools/get_concern.rb +31 -18
- data/lib/rails_ai_context/tools/get_config.rb +52 -18
- data/lib/rails_ai_context/tools/get_controllers.rb +57 -13
- data/lib/rails_ai_context/tools/get_conventions.rb +44 -22
- data/lib/rails_ai_context/tools/get_engines.rb +2 -0
- data/lib/rails_ai_context/tools/get_env.rb +209 -52
- data/lib/rails_ai_context/tools/get_env_config.rb +38 -7
- data/lib/rails_ai_context/tools/get_frontend_stack.rb +56 -7
- data/lib/rails_ai_context/tools/get_helper_methods.rb +136 -26
- data/lib/rails_ai_context/tools/get_job_pattern.rb +143 -87
- data/lib/rails_ai_context/tools/get_mailers.rb +59 -2
- data/lib/rails_ai_context/tools/get_model_details.rb +178 -28
- data/lib/rails_ai_context/tools/get_partial_interface.rb +101 -62
- data/lib/rails_ai_context/tools/get_routes.rb +35 -9
- data/lib/rails_ai_context/tools/get_schema.rb +160 -61
- data/lib/rails_ai_context/tools/get_service_pattern.rb +121 -25
- data/lib/rails_ai_context/tools/get_stimulus.rb +1 -1
- data/lib/rails_ai_context/tools/get_test_info.rb +152 -82
- data/lib/rails_ai_context/tools/get_view.rb +37 -20
- data/lib/rails_ai_context/tools/migration_advisor.rb +64 -24
- data/lib/rails_ai_context/tools/onboard.rb +174 -88
- data/lib/rails_ai_context/tools/performance_check.rb +5 -13
- data/lib/rails_ai_context/tools/query.rb +122 -28
- data/lib/rails_ai_context/tools/read_logs.rb +45 -17
- data/lib/rails_ai_context/tools/runtime_info.rb +1 -1
- data/lib/rails_ai_context/tools/safe_call.rb +20 -15
- data/lib/rails_ai_context/tools/search_code.rb +114 -40
- data/lib/rails_ai_context/tools/search_docs.rb +22 -11
- data/lib/rails_ai_context/tools/security_scan.rb +51 -4
- data/lib/rails_ai_context/tools/validate.rb +21 -4
- data/lib/rails_ai_context/tools/validate_semantics.rb +18 -6
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context/vfs.rb +2 -2
- data/lib/rails_ai_context/view_file.rb +53 -3
- data/lib/rails_ai_context.rb +10 -2
- data/server.json +1 -1
- metadata +34 -3
|
@@ -14,6 +14,8 @@ module RailsAiContext
|
|
|
14
14
|
include: %w[included append_features], prepend: %w[prepended prepend_features], extend: %w[extended]
|
|
15
15
|
}.freeze
|
|
16
16
|
MIXIN_HOOKS = HOOKS_BY_MACRO.values.flatten.freeze
|
|
17
|
+
# The macros that give the class a module's methods as class methods.
|
|
18
|
+
SINGLETON_MACROS = %i[extend singleton_include singleton_prepend].freeze
|
|
17
19
|
# The block ActiveSupport::Concern runs for each.
|
|
18
20
|
CONCERN_BLOCKS = { include: :included, prepend: :prepended }.freeze
|
|
19
21
|
FRAMEWORK_PREFIXES = %w[
|
|
@@ -60,12 +62,30 @@ module RailsAiContext
|
|
|
60
62
|
|
|
61
63
|
# Matched on the last segment: `module Edition; module Featurable` and
|
|
62
64
|
# `module Edition::Featurable` both write one constant.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
# An edition module (`prepend_mod_with`) counts only where `root` holds its file.
|
|
66
|
+
def owned_by(records, name, root: nil)
|
|
67
|
+
return Array(records) if Array(records).none? { |record| record.key?(:owner) }
|
|
65
68
|
|
|
66
|
-
own = own_owner(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
own = own_owner(records, name)
|
|
70
|
+
Array(records).filter_map do |record|
|
|
71
|
+
next if record[:edition] && !(root && ConcernPaths.find_file(root.to_s, record[:name]))
|
|
72
|
+
|
|
73
|
+
if record[:receiver]
|
|
74
|
+
mixed_into(record, name)
|
|
75
|
+
elsif own && Array(record[:owner]).join("::") == own
|
|
76
|
+
record
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# `Note.prepend X` mixes into Note as `prepend X` in its body would; `Other.include X` is Other's.
|
|
82
|
+
def mixed_into(record, name)
|
|
83
|
+
receiver = record[:receiver].to_s
|
|
84
|
+
target = receiver.start_with?("::") ? receiver.delete_prefix("::") : [ *Array(record[:owner]), receiver ].join("::")
|
|
85
|
+
return unless target == name.to_s
|
|
86
|
+
|
|
87
|
+
own = record.except(:receiver)
|
|
88
|
+
record.key?(:ancestor) ? own.merge(ancestor: CONCERN_BLOCKS.key?(record[:macro])) : own
|
|
69
89
|
end
|
|
70
90
|
|
|
71
91
|
def own_owner(records, name)
|
|
@@ -133,22 +133,48 @@ module RailsAiContext
|
|
|
133
133
|
# bare `include DebugConcern` inside Fasp::Provider resolves at runtime
|
|
134
134
|
# to Fasp::Provider::DebugConcern, which the literal spelling misses.
|
|
135
135
|
# @param dirs [Array<String>, nil] pre-resolved concern directories
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
136
|
+
# @param outer [Boolean] whether a module with no file of its own is found in its outer constant's file
|
|
137
|
+
def find_file(root, concern_name, prefer: nil, within: nil, dirs: nil, outer: true)
|
|
138
|
+
found = find_named(root, concern_name, prefer: prefer, within: within, dirs: dirs)
|
|
139
|
+
(found || (outer_named(root, concern_name, within: within, dirs: dirs) if outer))&.last
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# The source of the module a reference resolves to: its file, or its own node in its outer constant's file.
|
|
143
|
+
def module_source(root, concern_name, prefer: nil, within: nil)
|
|
144
|
+
path = find_file(root, concern_name, prefer: prefer, within: within, outer: false)
|
|
145
|
+
return SafeFile.read(path) if path
|
|
146
|
+
|
|
147
|
+
name, path = outer_named(root, concern_name, within: within)
|
|
148
|
+
path && Introspectors::DeclaredConstant.module_node(AstCache.parse(path).value, name)&.slice
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# [`Outer::Inner`, Outer's file] for a module with no file of its own, which Zeitwerk loads with Outer.
|
|
152
|
+
def outer_named(root, concern_name, within: nil, dirs: nil)
|
|
153
|
+
RunCache.fetch([ :outer_named, root, concern_name, within, dirs ]) do
|
|
154
|
+
candidate_names(concern_name, within).lazy.filter_map do |name|
|
|
155
|
+
outer = name.rpartition("::").first
|
|
156
|
+
path = !outer.empty? && find_file(root, outer, dirs: dirs)
|
|
157
|
+
[ name, path ] if path && Introspectors::DeclaredConstant.module_node(AstCache.parse(path).value, name)
|
|
158
|
+
end.first
|
|
149
159
|
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# [the constant the reference resolves to, its file] for `find_file`'s file, or nil.
|
|
163
|
+
def find_named(root, concern_name, prefer: nil, within: nil, dirs: nil)
|
|
164
|
+
# Every model asks for the same few names, each across hundreds of directories.
|
|
165
|
+
RunCache.fetch([ :find_named, root, concern_name, prefer, within, dirs ]) do
|
|
166
|
+
# Wherever Zeitwerk would look, plus an in-repo path gem's lib. A lib the app does
|
|
167
|
+
# not add to its autoload paths holds nothing it autoloads, so it is skipped.
|
|
168
|
+
searched = ordered_dirs(root, prefer, dirs) + PathResolver.app_roots(root) + PathResolver.declared_roots(root) +
|
|
169
|
+
PathResolver.path_gem_libs(root)
|
|
170
|
+
candidate_names(concern_name, within).lazy.filter_map do |name|
|
|
171
|
+
underscore = name.underscore
|
|
172
|
+
next if underscore.empty? || underscore.include?("..")
|
|
150
173
|
|
|
151
|
-
|
|
174
|
+
found = searched.find { |dir| file_exist?(dir, "#{underscore}.rb") }
|
|
175
|
+
[ name, File.join(found, "#{underscore}.rb") ] if found
|
|
176
|
+
end.first
|
|
177
|
+
end
|
|
152
178
|
end
|
|
153
179
|
|
|
154
180
|
# Each directory is listed once per run and a candidate is walked down
|
|
@@ -166,9 +192,28 @@ module RailsAiContext
|
|
|
166
192
|
end
|
|
167
193
|
RunCache.fetch([ :exist, current ]) { File.exist?(current) }
|
|
168
194
|
end
|
|
195
|
+
private_class_method :file_exist?
|
|
196
|
+
|
|
197
|
+
# `relative` as the disk spells it: an app acronym underscores `ActivityPub` to `activitypub`,
|
|
198
|
+
# where this process, which has none of the app's acronyms, writes `activity_pub`. Nil when absent.
|
|
199
|
+
def spelled(dir, relative)
|
|
200
|
+
return relative if file_exist?(dir, relative)
|
|
201
|
+
|
|
202
|
+
current = dir
|
|
203
|
+
segments = relative.split("/").map do |segment|
|
|
204
|
+
found = listing(current).squashed[segment.delete("_")] or return nil
|
|
205
|
+
current = File.join(current, found)
|
|
206
|
+
found
|
|
207
|
+
end
|
|
208
|
+
RunCache.fetch([ :exist, current ]) { File.exist?(current) } ? segments.join("/") : nil
|
|
209
|
+
end
|
|
169
210
|
|
|
170
211
|
Listing = Struct.new(:names, :folded) do
|
|
171
212
|
def include?(name) = names.include?(name)
|
|
213
|
+
|
|
214
|
+
def squashed
|
|
215
|
+
@squashed ||= names.each_with_object({}) { |name, found| found[name.delete("_")] ||= name }
|
|
216
|
+
end
|
|
172
217
|
end
|
|
173
218
|
|
|
174
219
|
def listing(dir)
|
|
@@ -189,7 +234,6 @@ module RailsAiContext
|
|
|
189
234
|
File.identical?(File.join(dir, name), File.join(dir, name.swapcase))
|
|
190
235
|
end
|
|
191
236
|
private_class_method :case_insensitive?
|
|
192
|
-
private_class_method :file_exist?
|
|
193
237
|
|
|
194
238
|
def ordered_dirs(root, prefer, dirs = nil)
|
|
195
239
|
dirs ||= resolve(root)
|
|
@@ -201,10 +245,12 @@ module RailsAiContext
|
|
|
201
245
|
# The enclosing namespaces from the innermost outward, then the reference
|
|
202
246
|
# itself - Ruby's own constant lookup order, which reaches the top level
|
|
203
247
|
# last. Bare-name-first would bind Fasp::Provider's `include DebugConcern`
|
|
204
|
-
# to a top-level DebugConcern the runtime never sees.
|
|
248
|
+
# to a top-level DebugConcern the runtime never sees. A qualified name's
|
|
249
|
+
# first segment is looked up the same way; only a leading `::` skips it.
|
|
205
250
|
def candidate_names(concern_name, within)
|
|
206
251
|
name = concern_name.to_s
|
|
207
|
-
return [ name ] if
|
|
252
|
+
return [ name.delete_prefix("::") ] if name.start_with?("::")
|
|
253
|
+
return [ name ] if within.nil?
|
|
208
254
|
|
|
209
255
|
scopes = within.to_s.split("::")
|
|
210
256
|
scopes.size.downto(1).map { |n| "#{scopes.first(n).join('::')}::#{name}" } + [ name ]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module RailsAiContext
|
|
7
|
+
# An app's config YAML read without running its ERB: string keys, and a failure
|
|
8
|
+
# logged under the caller's label and answered as nil.
|
|
9
|
+
module ConfigYaml
|
|
10
|
+
ERB_OUTPUT = "RAC_ERB_OUTPUT"
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# With `marker` an output tag reads as that marker, which marked? finds; without it, as empty.
|
|
15
|
+
def read(root, file, label:, marker: nil)
|
|
16
|
+
content = SafePath.read(file, under: root.to_s).first or return nil
|
|
17
|
+
content = marker ? ErbSource.with_output_marked(content, marker) : ErbSource.without_tags(content)
|
|
18
|
+
stringify_keys(YAML.safe_load(content, aliases: true, permitted_classes: [ Symbol, Date, Time ]))
|
|
19
|
+
rescue StandardError, ScriptError => e
|
|
20
|
+
RailsAiContext.debug_fail(e, nil, label: "#{label} #{file}")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def marked?(value)
|
|
24
|
+
value.is_a?(String) && value.include?(ERB_OUTPUT)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Each output tag reads as a numbered marker; shown, every one is `<%= ... %>` again.
|
|
28
|
+
def shown(text)
|
|
29
|
+
text.to_s.gsub(/#{ERB_OUTPUT}(?:_\d+_)?/o, "<%= ... %>")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def stringify_keys(value)
|
|
33
|
+
case value
|
|
34
|
+
when Hash then value.to_h { |key, inner| [ key.to_s.delete_prefix(":"), stringify_keys(inner) ] }
|
|
35
|
+
when Array then value.map { |inner| stringify_keys(inner) }
|
|
36
|
+
else value
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -362,7 +362,7 @@ module RailsAiContext
|
|
|
362
362
|
attr_accessor :concern_paths # Where to look for concern source files (default: nil, discovers app/*/concerns)
|
|
363
363
|
|
|
364
364
|
# Frontend framework detection (optional overrides - auto-detected if nil)
|
|
365
|
-
attr_accessor :frontend_paths # User-declared frontend dirs (e.g. ["app/frontend", "../web-client"])
|
|
365
|
+
attr_accessor :frontend_paths # User-declared frontend dirs (e.g. ["app/frontend", "../web-client"]; one outside the app root is read for manifests only)
|
|
366
366
|
|
|
367
367
|
# Additional app-root-relative directories that also contain Rails app
|
|
368
368
|
# code (e.g. ["src", "vendor/internal"]). Code discovery scans each
|
|
@@ -387,8 +387,8 @@ module RailsAiContext
|
|
|
387
387
|
@server_name = "rails-ai-context"
|
|
388
388
|
@introspectors = PRESETS[:full].dup
|
|
389
389
|
@excluded_paths = %w[node_modules tmp log vendor .git doc docs]
|
|
390
|
-
# Files each tool's own docs say to gitignore.
|
|
391
|
-
#
|
|
390
|
+
# Files each tool's own docs say to gitignore. A placeholder beside one
|
|
391
|
+
# (config/application.example.yml, .env.example) stays readable: see SafePath.sensitive?.
|
|
392
392
|
@sensitive_patterns = %w[
|
|
393
393
|
.env .env.* *.env .envrc
|
|
394
394
|
config/master.key
|
|
@@ -119,6 +119,7 @@
|
|
|
119
119
|
"id": "active_record_encryption",
|
|
120
120
|
"title": "Active Record Encryption",
|
|
121
121
|
"source": "rails",
|
|
122
|
+
"since": "7.0",
|
|
122
123
|
"summary": "Encrypt model attributes at rest with deterministic and non-deterministic encryption",
|
|
123
124
|
"keywords": [
|
|
124
125
|
"encryption",
|
|
@@ -502,6 +503,329 @@
|
|
|
502
503
|
],
|
|
503
504
|
"category": "api"
|
|
504
505
|
},
|
|
506
|
+
{
|
|
507
|
+
"id": "action_controller_advanced_topics",
|
|
508
|
+
"title": "Action Controller Advanced Topics",
|
|
509
|
+
"source": "rails",
|
|
510
|
+
"since": "8.0",
|
|
511
|
+
"summary": "CSRF protection, HTTP authentication, streaming, log parameter filtering, rescue_from, health check endpoint",
|
|
512
|
+
"keywords": [
|
|
513
|
+
"csrf",
|
|
514
|
+
"http authentication",
|
|
515
|
+
"streaming",
|
|
516
|
+
"send_data",
|
|
517
|
+
"filter parameters",
|
|
518
|
+
"rescue_from",
|
|
519
|
+
"health check"
|
|
520
|
+
],
|
|
521
|
+
"category": "controllers"
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
"id": "active_model_basics",
|
|
525
|
+
"title": "Active Model Basics",
|
|
526
|
+
"source": "rails",
|
|
527
|
+
"summary": "Active Model modules for plain Ruby objects: attributes, validations, callbacks, serialization, naming",
|
|
528
|
+
"keywords": [
|
|
529
|
+
"active model",
|
|
530
|
+
"activemodel",
|
|
531
|
+
"plain ruby object",
|
|
532
|
+
"form object",
|
|
533
|
+
"attributes",
|
|
534
|
+
"serialization"
|
|
535
|
+
],
|
|
536
|
+
"category": "models"
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
"id": "active_record_composite_primary_keys",
|
|
540
|
+
"title": "Composite Primary Keys",
|
|
541
|
+
"source": "rails",
|
|
542
|
+
"since": "7.1",
|
|
543
|
+
"summary": "Tables, queries, associations, forms and fixtures with composite primary keys",
|
|
544
|
+
"keywords": [
|
|
545
|
+
"composite primary key",
|
|
546
|
+
"primary_key",
|
|
547
|
+
"query_constraints",
|
|
548
|
+
"multi-column key"
|
|
549
|
+
],
|
|
550
|
+
"category": "models"
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
"id": "active_record_postgresql",
|
|
554
|
+
"title": "Active Record and PostgreSQL",
|
|
555
|
+
"source": "rails",
|
|
556
|
+
"summary": "PostgreSQL datatypes, UUID keys, deferrable foreign keys, exclusion constraints, full text search, views",
|
|
557
|
+
"keywords": [
|
|
558
|
+
"postgresql",
|
|
559
|
+
"postgres",
|
|
560
|
+
"jsonb",
|
|
561
|
+
"uuid",
|
|
562
|
+
"array",
|
|
563
|
+
"full text search",
|
|
564
|
+
"exclusion constraint",
|
|
565
|
+
"database views"
|
|
566
|
+
],
|
|
567
|
+
"category": "models"
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
"id": "active_support_core_extensions",
|
|
571
|
+
"title": "Active Support Core Extensions",
|
|
572
|
+
"source": "rails",
|
|
573
|
+
"summary": "Ruby language extensions Active Support adds: blank?, present?, try, String, Hash, Array, Date and Time helpers",
|
|
574
|
+
"keywords": [
|
|
575
|
+
"core extensions",
|
|
576
|
+
"core_ext",
|
|
577
|
+
"blank?",
|
|
578
|
+
"present?",
|
|
579
|
+
"try",
|
|
580
|
+
"string",
|
|
581
|
+
"hash",
|
|
582
|
+
"time"
|
|
583
|
+
],
|
|
584
|
+
"category": "core"
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
"id": "active_support_instrumentation",
|
|
588
|
+
"title": "Active Support Instrumentation",
|
|
589
|
+
"source": "rails",
|
|
590
|
+
"summary": "ActiveSupport::Notifications: subscribing to hooks, the framework's instrumentation events, custom instrumentation",
|
|
591
|
+
"keywords": [
|
|
592
|
+
"instrumentation",
|
|
593
|
+
"notifications",
|
|
594
|
+
"subscribe",
|
|
595
|
+
"instrument",
|
|
596
|
+
"events",
|
|
597
|
+
"activesupport::notifications"
|
|
598
|
+
],
|
|
599
|
+
"category": "core"
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
"id": "autoloading_and_reloading_constants",
|
|
603
|
+
"title": "Autoloading and Reloading Constants",
|
|
604
|
+
"source": "rails",
|
|
605
|
+
"summary": "Zeitwerk autoloading, reloading and eager loading, autoload paths, project structure, STI",
|
|
606
|
+
"keywords": [
|
|
607
|
+
"autoloading",
|
|
608
|
+
"autoload",
|
|
609
|
+
"zeitwerk",
|
|
610
|
+
"reloading",
|
|
611
|
+
"eager loading",
|
|
612
|
+
"autoload_paths",
|
|
613
|
+
"constants"
|
|
614
|
+
],
|
|
615
|
+
"category": "core"
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
"id": "debugging_rails_applications",
|
|
619
|
+
"title": "Debugging Rails Applications",
|
|
620
|
+
"source": "rails",
|
|
621
|
+
"summary": "Debug helpers, the logger, the debug gem, breakpoints, stack traces, SQL query sources",
|
|
622
|
+
"keywords": [
|
|
623
|
+
"debugging",
|
|
624
|
+
"debug",
|
|
625
|
+
"debugger",
|
|
626
|
+
"logger",
|
|
627
|
+
"breakpoint",
|
|
628
|
+
"stack trace",
|
|
629
|
+
"byebug"
|
|
630
|
+
],
|
|
631
|
+
"category": "core"
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
"id": "engines",
|
|
635
|
+
"title": "Getting Started with Engines",
|
|
636
|
+
"source": "rails",
|
|
637
|
+
"summary": "Generating and mounting engines, isolated namespaces, overriding engine code, load hooks",
|
|
638
|
+
"keywords": [
|
|
639
|
+
"engine",
|
|
640
|
+
"engines",
|
|
641
|
+
"mountable",
|
|
642
|
+
"isolate_namespace",
|
|
643
|
+
"mount",
|
|
644
|
+
"railtie"
|
|
645
|
+
],
|
|
646
|
+
"category": "core"
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
"id": "error_reporting",
|
|
650
|
+
"title": "Error Reporting in Rails Applications",
|
|
651
|
+
"source": "rails",
|
|
652
|
+
"since": "7.1",
|
|
653
|
+
"summary": "Rails.error reporter: handle, record, report, and custom error subscribers",
|
|
654
|
+
"keywords": [
|
|
655
|
+
"error reporting",
|
|
656
|
+
"error reporter",
|
|
657
|
+
"rails.error",
|
|
658
|
+
"exceptions",
|
|
659
|
+
"subscriber",
|
|
660
|
+
"error tracking"
|
|
661
|
+
],
|
|
662
|
+
"category": "core"
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
"id": "generators",
|
|
666
|
+
"title": "Rails Generators & Templates",
|
|
667
|
+
"source": "rails",
|
|
668
|
+
"summary": "Listing, creating and customizing generators, overriding scaffold templates, application templates",
|
|
669
|
+
"keywords": [
|
|
670
|
+
"generators",
|
|
671
|
+
"generator",
|
|
672
|
+
"rails generate",
|
|
673
|
+
"templates",
|
|
674
|
+
"scaffold templates",
|
|
675
|
+
"application template"
|
|
676
|
+
],
|
|
677
|
+
"category": "core"
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
"id": "getting_started_with_devcontainer",
|
|
681
|
+
"title": "Getting Started with Dev Containers",
|
|
682
|
+
"source": "rails",
|
|
683
|
+
"since": "7.2",
|
|
684
|
+
"summary": "rails-new and running a Rails app inside a development container",
|
|
685
|
+
"keywords": [
|
|
686
|
+
"devcontainer",
|
|
687
|
+
"dev container",
|
|
688
|
+
"docker",
|
|
689
|
+
"rails-new",
|
|
690
|
+
"development environment"
|
|
691
|
+
],
|
|
692
|
+
"category": "core"
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
"id": "initialization",
|
|
696
|
+
"title": "The Rails Initialization Process",
|
|
697
|
+
"source": "rails",
|
|
698
|
+
"summary": "The boot sequence: bin/rails, config/boot.rb, config/application.rb, Rails::Server",
|
|
699
|
+
"keywords": [
|
|
700
|
+
"initialization",
|
|
701
|
+
"boot",
|
|
702
|
+
"boot process",
|
|
703
|
+
"startup",
|
|
704
|
+
"rails server",
|
|
705
|
+
"config/application.rb"
|
|
706
|
+
],
|
|
707
|
+
"category": "core"
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
"id": "plugins",
|
|
711
|
+
"title": "The Basics of Creating Rails Plugins",
|
|
712
|
+
"source": "rails",
|
|
713
|
+
"summary": "Creating a plugin gem, extending core classes, adding methods to ApplicationRecord, publishing",
|
|
714
|
+
"keywords": [
|
|
715
|
+
"plugins",
|
|
716
|
+
"plugin",
|
|
717
|
+
"gem",
|
|
718
|
+
"extend",
|
|
719
|
+
"acts_as"
|
|
720
|
+
],
|
|
721
|
+
"category": "core"
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
"id": "product_reviews",
|
|
725
|
+
"title": "Product Reviews",
|
|
726
|
+
"source": "rails",
|
|
727
|
+
"since": "8.1",
|
|
728
|
+
"summary": "Getting Started tutorial series: collecting reviews, average ratings, filtering reviews",
|
|
729
|
+
"keywords": [
|
|
730
|
+
"reviews",
|
|
731
|
+
"ratings",
|
|
732
|
+
"tutorial",
|
|
733
|
+
"e-commerce"
|
|
734
|
+
],
|
|
735
|
+
"category": "core"
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
"id": "rails_on_rack",
|
|
739
|
+
"title": "Rails on Rack",
|
|
740
|
+
"source": "rails",
|
|
741
|
+
"summary": "Rack, the middleware stack, configuring middleware, Rails as a Rack application",
|
|
742
|
+
"keywords": [
|
|
743
|
+
"rack",
|
|
744
|
+
"middleware",
|
|
745
|
+
"middleware stack",
|
|
746
|
+
"config.ru",
|
|
747
|
+
"rack app"
|
|
748
|
+
],
|
|
749
|
+
"category": "core"
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
"id": "sign_up_and_settings",
|
|
753
|
+
"title": "Sign Up and Settings",
|
|
754
|
+
"source": "rails",
|
|
755
|
+
"since": "8.1",
|
|
756
|
+
"summary": "Getting Started tutorial series: user sign up, rate limiting, nested layouts, admin and user controllers",
|
|
757
|
+
"keywords": [
|
|
758
|
+
"sign up",
|
|
759
|
+
"signup",
|
|
760
|
+
"registration",
|
|
761
|
+
"settings",
|
|
762
|
+
"rate_limit",
|
|
763
|
+
"tutorial"
|
|
764
|
+
],
|
|
765
|
+
"category": "core"
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
"id": "threading_and_code_execution",
|
|
769
|
+
"title": "Threading and Code Execution in Rails",
|
|
770
|
+
"source": "rails",
|
|
771
|
+
"summary": "Concurrency in Rails, the executor and reloader, wrapping application code, load interlock",
|
|
772
|
+
"keywords": [
|
|
773
|
+
"threading",
|
|
774
|
+
"threads",
|
|
775
|
+
"concurrency",
|
|
776
|
+
"executor",
|
|
777
|
+
"reloader",
|
|
778
|
+
"load interlock"
|
|
779
|
+
],
|
|
780
|
+
"category": "core"
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
"id": "tuning_performance_for_deployment",
|
|
784
|
+
"title": "Tuning Performance for Deployment",
|
|
785
|
+
"source": "rails",
|
|
786
|
+
"since": "7.2",
|
|
787
|
+
"summary": "Puma workers and threads, YJIT, memory allocators, performance testing for production",
|
|
788
|
+
"keywords": [
|
|
789
|
+
"performance",
|
|
790
|
+
"puma",
|
|
791
|
+
"tuning",
|
|
792
|
+
"workers",
|
|
793
|
+
"threads",
|
|
794
|
+
"yjit",
|
|
795
|
+
"deployment"
|
|
796
|
+
],
|
|
797
|
+
"category": "core"
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
"id": "upgrading_ruby_on_rails",
|
|
801
|
+
"title": "Upgrading Ruby on Rails",
|
|
802
|
+
"source": "rails",
|
|
803
|
+
"summary": "Steps and per-version changes when upgrading an app to a newer Rails",
|
|
804
|
+
"keywords": [
|
|
805
|
+
"upgrading",
|
|
806
|
+
"upgrade",
|
|
807
|
+
"rails update",
|
|
808
|
+
"new framework defaults",
|
|
809
|
+
"deprecations",
|
|
810
|
+
"migration path"
|
|
811
|
+
],
|
|
812
|
+
"category": "core"
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
"id": "wishlists",
|
|
816
|
+
"title": "Wishlists",
|
|
817
|
+
"source": "rails",
|
|
818
|
+
"since": "8.1",
|
|
819
|
+
"summary": "Getting Started tutorial series: wishlists, counter caches, friendly URLs, filtering records",
|
|
820
|
+
"keywords": [
|
|
821
|
+
"wishlists",
|
|
822
|
+
"counter cache",
|
|
823
|
+
"friendly urls",
|
|
824
|
+
"to_param",
|
|
825
|
+
"tutorial"
|
|
826
|
+
],
|
|
827
|
+
"category": "core"
|
|
828
|
+
},
|
|
505
829
|
{
|
|
506
830
|
"id": "turbo_handbook_introduction",
|
|
507
831
|
"title": "Turbo Handbook: Introduction",
|