codebase_index 0.3.2 → 0.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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/lib/codebase_index.rb +3 -243
  3. metadata +28 -223
  4. data/CHANGELOG.md +0 -89
  5. data/CODE_OF_CONDUCT.md +0 -83
  6. data/CONTRIBUTING.md +0 -65
  7. data/LICENSE.txt +0 -21
  8. data/README.md +0 -325
  9. data/exe/codebase-console +0 -59
  10. data/exe/codebase-console-mcp +0 -22
  11. data/exe/codebase-index-mcp +0 -34
  12. data/exe/codebase-index-mcp-http +0 -37
  13. data/exe/codebase-index-mcp-start +0 -58
  14. data/lib/codebase_index/ast/call_site_extractor.rb +0 -106
  15. data/lib/codebase_index/ast/method_extractor.rb +0 -71
  16. data/lib/codebase_index/ast/node.rb +0 -116
  17. data/lib/codebase_index/ast/parser.rb +0 -614
  18. data/lib/codebase_index/ast.rb +0 -6
  19. data/lib/codebase_index/builder.rb +0 -200
  20. data/lib/codebase_index/cache/cache_middleware.rb +0 -199
  21. data/lib/codebase_index/cache/cache_store.rb +0 -264
  22. data/lib/codebase_index/cache/redis_cache_store.rb +0 -116
  23. data/lib/codebase_index/cache/solid_cache_store.rb +0 -111
  24. data/lib/codebase_index/chunking/chunk.rb +0 -84
  25. data/lib/codebase_index/chunking/semantic_chunker.rb +0 -295
  26. data/lib/codebase_index/console/adapters/cache_adapter.rb +0 -58
  27. data/lib/codebase_index/console/adapters/good_job_adapter.rb +0 -33
  28. data/lib/codebase_index/console/adapters/job_adapter.rb +0 -68
  29. data/lib/codebase_index/console/adapters/sidekiq_adapter.rb +0 -33
  30. data/lib/codebase_index/console/adapters/solid_queue_adapter.rb +0 -33
  31. data/lib/codebase_index/console/audit_logger.rb +0 -75
  32. data/lib/codebase_index/console/bridge.rb +0 -177
  33. data/lib/codebase_index/console/confirmation.rb +0 -90
  34. data/lib/codebase_index/console/connection_manager.rb +0 -173
  35. data/lib/codebase_index/console/console_response_renderer.rb +0 -74
  36. data/lib/codebase_index/console/embedded_executor.rb +0 -373
  37. data/lib/codebase_index/console/model_validator.rb +0 -81
  38. data/lib/codebase_index/console/rack_middleware.rb +0 -87
  39. data/lib/codebase_index/console/safe_context.rb +0 -82
  40. data/lib/codebase_index/console/server.rb +0 -612
  41. data/lib/codebase_index/console/sql_validator.rb +0 -172
  42. data/lib/codebase_index/console/tools/tier1.rb +0 -118
  43. data/lib/codebase_index/console/tools/tier2.rb +0 -117
  44. data/lib/codebase_index/console/tools/tier3.rb +0 -110
  45. data/lib/codebase_index/console/tools/tier4.rb +0 -79
  46. data/lib/codebase_index/coordination/pipeline_lock.rb +0 -109
  47. data/lib/codebase_index/cost_model/embedding_cost.rb +0 -88
  48. data/lib/codebase_index/cost_model/estimator.rb +0 -128
  49. data/lib/codebase_index/cost_model/provider_pricing.rb +0 -67
  50. data/lib/codebase_index/cost_model/storage_cost.rb +0 -52
  51. data/lib/codebase_index/cost_model.rb +0 -22
  52. data/lib/codebase_index/db/migrations/001_create_units.rb +0 -38
  53. data/lib/codebase_index/db/migrations/002_create_edges.rb +0 -35
  54. data/lib/codebase_index/db/migrations/003_create_embeddings.rb +0 -37
  55. data/lib/codebase_index/db/migrations/004_create_snapshots.rb +0 -45
  56. data/lib/codebase_index/db/migrations/005_create_snapshot_units.rb +0 -40
  57. data/lib/codebase_index/db/migrator.rb +0 -71
  58. data/lib/codebase_index/db/schema_version.rb +0 -73
  59. data/lib/codebase_index/dependency_graph.rb +0 -236
  60. data/lib/codebase_index/embedding/indexer.rb +0 -140
  61. data/lib/codebase_index/embedding/openai.rb +0 -126
  62. data/lib/codebase_index/embedding/provider.rb +0 -162
  63. data/lib/codebase_index/embedding/text_preparer.rb +0 -112
  64. data/lib/codebase_index/evaluation/baseline_runner.rb +0 -115
  65. data/lib/codebase_index/evaluation/evaluator.rb +0 -139
  66. data/lib/codebase_index/evaluation/metrics.rb +0 -79
  67. data/lib/codebase_index/evaluation/query_set.rb +0 -148
  68. data/lib/codebase_index/evaluation/report_generator.rb +0 -90
  69. data/lib/codebase_index/extracted_unit.rb +0 -145
  70. data/lib/codebase_index/extractor.rb +0 -1028
  71. data/lib/codebase_index/extractors/action_cable_extractor.rb +0 -201
  72. data/lib/codebase_index/extractors/ast_source_extraction.rb +0 -46
  73. data/lib/codebase_index/extractors/behavioral_profile.rb +0 -309
  74. data/lib/codebase_index/extractors/caching_extractor.rb +0 -261
  75. data/lib/codebase_index/extractors/callback_analyzer.rb +0 -246
  76. data/lib/codebase_index/extractors/concern_extractor.rb +0 -292
  77. data/lib/codebase_index/extractors/configuration_extractor.rb +0 -219
  78. data/lib/codebase_index/extractors/controller_extractor.rb +0 -404
  79. data/lib/codebase_index/extractors/database_view_extractor.rb +0 -278
  80. data/lib/codebase_index/extractors/decorator_extractor.rb +0 -253
  81. data/lib/codebase_index/extractors/engine_extractor.rb +0 -223
  82. data/lib/codebase_index/extractors/event_extractor.rb +0 -211
  83. data/lib/codebase_index/extractors/factory_extractor.rb +0 -289
  84. data/lib/codebase_index/extractors/graphql_extractor.rb +0 -892
  85. data/lib/codebase_index/extractors/i18n_extractor.rb +0 -117
  86. data/lib/codebase_index/extractors/job_extractor.rb +0 -374
  87. data/lib/codebase_index/extractors/lib_extractor.rb +0 -218
  88. data/lib/codebase_index/extractors/mailer_extractor.rb +0 -269
  89. data/lib/codebase_index/extractors/manager_extractor.rb +0 -188
  90. data/lib/codebase_index/extractors/middleware_extractor.rb +0 -133
  91. data/lib/codebase_index/extractors/migration_extractor.rb +0 -469
  92. data/lib/codebase_index/extractors/model_extractor.rb +0 -988
  93. data/lib/codebase_index/extractors/phlex_extractor.rb +0 -252
  94. data/lib/codebase_index/extractors/policy_extractor.rb +0 -191
  95. data/lib/codebase_index/extractors/poro_extractor.rb +0 -229
  96. data/lib/codebase_index/extractors/pundit_extractor.rb +0 -223
  97. data/lib/codebase_index/extractors/rails_source_extractor.rb +0 -473
  98. data/lib/codebase_index/extractors/rake_task_extractor.rb +0 -343
  99. data/lib/codebase_index/extractors/route_extractor.rb +0 -181
  100. data/lib/codebase_index/extractors/scheduled_job_extractor.rb +0 -331
  101. data/lib/codebase_index/extractors/serializer_extractor.rb +0 -339
  102. data/lib/codebase_index/extractors/service_extractor.rb +0 -217
  103. data/lib/codebase_index/extractors/shared_dependency_scanner.rb +0 -91
  104. data/lib/codebase_index/extractors/shared_utility_methods.rb +0 -281
  105. data/lib/codebase_index/extractors/state_machine_extractor.rb +0 -398
  106. data/lib/codebase_index/extractors/test_mapping_extractor.rb +0 -225
  107. data/lib/codebase_index/extractors/validator_extractor.rb +0 -211
  108. data/lib/codebase_index/extractors/view_component_extractor.rb +0 -311
  109. data/lib/codebase_index/extractors/view_template_extractor.rb +0 -261
  110. data/lib/codebase_index/feedback/gap_detector.rb +0 -89
  111. data/lib/codebase_index/feedback/store.rb +0 -119
  112. data/lib/codebase_index/filename_utils.rb +0 -32
  113. data/lib/codebase_index/flow_analysis/operation_extractor.rb +0 -206
  114. data/lib/codebase_index/flow_analysis/response_code_mapper.rb +0 -154
  115. data/lib/codebase_index/flow_assembler.rb +0 -290
  116. data/lib/codebase_index/flow_document.rb +0 -191
  117. data/lib/codebase_index/flow_precomputer.rb +0 -102
  118. data/lib/codebase_index/formatting/base.rb +0 -30
  119. data/lib/codebase_index/formatting/claude_adapter.rb +0 -98
  120. data/lib/codebase_index/formatting/generic_adapter.rb +0 -56
  121. data/lib/codebase_index/formatting/gpt_adapter.rb +0 -64
  122. data/lib/codebase_index/formatting/human_adapter.rb +0 -78
  123. data/lib/codebase_index/graph_analyzer.rb +0 -374
  124. data/lib/codebase_index/mcp/bootstrapper.rb +0 -96
  125. data/lib/codebase_index/mcp/index_reader.rb +0 -394
  126. data/lib/codebase_index/mcp/renderers/claude_renderer.rb +0 -81
  127. data/lib/codebase_index/mcp/renderers/json_renderer.rb +0 -17
  128. data/lib/codebase_index/mcp/renderers/markdown_renderer.rb +0 -353
  129. data/lib/codebase_index/mcp/renderers/plain_renderer.rb +0 -240
  130. data/lib/codebase_index/mcp/server.rb +0 -961
  131. data/lib/codebase_index/mcp/tool_response_renderer.rb +0 -85
  132. data/lib/codebase_index/model_name_cache.rb +0 -51
  133. data/lib/codebase_index/notion/client.rb +0 -217
  134. data/lib/codebase_index/notion/exporter.rb +0 -219
  135. data/lib/codebase_index/notion/mapper.rb +0 -40
  136. data/lib/codebase_index/notion/mappers/column_mapper.rb +0 -57
  137. data/lib/codebase_index/notion/mappers/migration_mapper.rb +0 -39
  138. data/lib/codebase_index/notion/mappers/model_mapper.rb +0 -161
  139. data/lib/codebase_index/notion/mappers/shared.rb +0 -22
  140. data/lib/codebase_index/notion/rate_limiter.rb +0 -68
  141. data/lib/codebase_index/observability/health_check.rb +0 -79
  142. data/lib/codebase_index/observability/instrumentation.rb +0 -34
  143. data/lib/codebase_index/observability/structured_logger.rb +0 -57
  144. data/lib/codebase_index/operator/error_escalator.rb +0 -81
  145. data/lib/codebase_index/operator/pipeline_guard.rb +0 -92
  146. data/lib/codebase_index/operator/status_reporter.rb +0 -80
  147. data/lib/codebase_index/railtie.rb +0 -38
  148. data/lib/codebase_index/resilience/circuit_breaker.rb +0 -99
  149. data/lib/codebase_index/resilience/index_validator.rb +0 -167
  150. data/lib/codebase_index/resilience/retryable_provider.rb +0 -108
  151. data/lib/codebase_index/retrieval/context_assembler.rb +0 -261
  152. data/lib/codebase_index/retrieval/query_classifier.rb +0 -133
  153. data/lib/codebase_index/retrieval/ranker.rb +0 -277
  154. data/lib/codebase_index/retrieval/search_executor.rb +0 -316
  155. data/lib/codebase_index/retriever.rb +0 -152
  156. data/lib/codebase_index/ruby_analyzer/class_analyzer.rb +0 -170
  157. data/lib/codebase_index/ruby_analyzer/dataflow_analyzer.rb +0 -77
  158. data/lib/codebase_index/ruby_analyzer/fqn_builder.rb +0 -18
  159. data/lib/codebase_index/ruby_analyzer/mermaid_renderer.rb +0 -280
  160. data/lib/codebase_index/ruby_analyzer/method_analyzer.rb +0 -143
  161. data/lib/codebase_index/ruby_analyzer/trace_enricher.rb +0 -143
  162. data/lib/codebase_index/ruby_analyzer.rb +0 -87
  163. data/lib/codebase_index/session_tracer/file_store.rb +0 -104
  164. data/lib/codebase_index/session_tracer/middleware.rb +0 -143
  165. data/lib/codebase_index/session_tracer/redis_store.rb +0 -106
  166. data/lib/codebase_index/session_tracer/session_flow_assembler.rb +0 -254
  167. data/lib/codebase_index/session_tracer/session_flow_document.rb +0 -223
  168. data/lib/codebase_index/session_tracer/solid_cache_store.rb +0 -139
  169. data/lib/codebase_index/session_tracer/store.rb +0 -81
  170. data/lib/codebase_index/storage/graph_store.rb +0 -120
  171. data/lib/codebase_index/storage/metadata_store.rb +0 -196
  172. data/lib/codebase_index/storage/pgvector.rb +0 -195
  173. data/lib/codebase_index/storage/qdrant.rb +0 -205
  174. data/lib/codebase_index/storage/vector_store.rb +0 -167
  175. data/lib/codebase_index/temporal/json_snapshot_store.rb +0 -245
  176. data/lib/codebase_index/temporal/snapshot_store.rb +0 -345
  177. data/lib/codebase_index/token_utils.rb +0 -19
  178. data/lib/codebase_index/version.rb +0 -5
  179. data/lib/generators/codebase_index/install_generator.rb +0 -32
  180. data/lib/generators/codebase_index/pgvector_generator.rb +0 -37
  181. data/lib/generators/codebase_index/templates/add_pgvector_to_codebase_index.rb.erb +0 -15
  182. data/lib/generators/codebase_index/templates/create_codebase_index_tables.rb.erb +0 -43
  183. data/lib/tasks/codebase_index.rake +0 -597
  184. data/lib/tasks/codebase_index_evaluation.rake +0 -115
@@ -1,133 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CodebaseIndex
4
- module Extractors
5
- # MiddlewareExtractor handles Rack middleware stack extraction via runtime introspection.
6
- #
7
- # Reads the middleware stack from `Rails.application.middleware` and produces
8
- # a single ExtractedUnit representing the full ordered stack. Each middleware
9
- # entry includes the class name, arguments, and insertion point.
10
- #
11
- # @example
12
- # extractor = MiddlewareExtractor.new
13
- # units = extractor.extract_all
14
- # stack = units.find { |u| u.identifier == "MiddlewareStack" }
15
- #
16
- class MiddlewareExtractor
17
- def initialize
18
- # No directories to scan — this is runtime introspection
19
- end
20
-
21
- # Extract the middleware stack as a single ExtractedUnit
22
- #
23
- # @return [Array<ExtractedUnit>] Array containing one unit for the stack
24
- def extract_all
25
- return [] unless middleware_available?
26
-
27
- stack = Rails.application.middleware
28
- entries = extract_middleware_entries(stack)
29
-
30
- return [] if entries.empty?
31
-
32
- unit = ExtractedUnit.new(
33
- type: :middleware,
34
- identifier: 'MiddlewareStack',
35
- file_path: nil
36
- )
37
-
38
- unit.namespace = nil
39
- unit.source_code = build_stack_source(entries)
40
- unit.metadata = build_stack_metadata(entries)
41
- unit.dependencies = []
42
-
43
- [unit]
44
- rescue StandardError => e
45
- Rails.logger.error("Failed to extract middleware stack: #{e.message}")
46
- []
47
- end
48
-
49
- private
50
-
51
- # Check if Rails middleware stack is available.
52
- #
53
- # @return [Boolean]
54
- def middleware_available?
55
- defined?(Rails) &&
56
- Rails.respond_to?(:application) &&
57
- Rails.application.respond_to?(:middleware)
58
- end
59
-
60
- # Extract individual middleware entries from the stack.
61
- #
62
- # @param stack [ActionDispatch::MiddlewareStack] The middleware stack
63
- # @return [Array<Hash>] List of middleware entry hashes
64
- def extract_middleware_entries(stack)
65
- entries = []
66
- position = 0
67
-
68
- stack.each do |middleware|
69
- entry = extract_single_middleware(middleware, position)
70
- entries << entry if entry
71
- position += 1
72
- end
73
-
74
- entries
75
- end
76
-
77
- # Extract info from a single middleware entry.
78
- #
79
- # @param middleware [ActionDispatch::MiddlewareStack::Middleware] A middleware entry
80
- # @param position [Integer] Position in the stack
81
- # @return [Hash, nil]
82
- def extract_single_middleware(middleware, position)
83
- name = if middleware.respond_to?(:name)
84
- middleware.name
85
- elsif middleware.respond_to?(:klass)
86
- middleware.klass.to_s
87
- else
88
- middleware.to_s
89
- end
90
-
91
- args = if middleware.respond_to?(:args)
92
- middleware.args.map(&:to_s)
93
- else
94
- []
95
- end
96
-
97
- { name: name, args: args, position: position }
98
- rescue StandardError
99
- nil
100
- end
101
-
102
- # Build a human-readable source representation of the middleware stack.
103
- #
104
- # @param entries [Array<Hash>] Middleware entries
105
- # @return [String]
106
- def build_stack_source(entries)
107
- lines = []
108
- lines << '# Rack Middleware Stack'
109
- lines << "# #{entries.size} middleware(s)"
110
- lines << '#'
111
-
112
- entries.each do |entry|
113
- args_str = entry[:args].any? ? " (#{entry[:args].join(', ')})" : ''
114
- lines << "# [#{entry[:position]}] #{entry[:name]}#{args_str}"
115
- end
116
-
117
- lines.join("\n")
118
- end
119
-
120
- # Build metadata hash for the middleware stack.
121
- #
122
- # @param entries [Array<Hash>] Middleware entries
123
- # @return [Hash]
124
- def build_stack_metadata(entries)
125
- {
126
- middleware_count: entries.size,
127
- middleware_list: entries.map { |e| e[:name] },
128
- middleware_details: entries
129
- }
130
- end
131
- end
132
- end
133
- end
@@ -1,469 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'shared_utility_methods'
4
- require_relative 'shared_dependency_scanner'
5
-
6
- module CodebaseIndex
7
- module Extractors
8
- # MigrationExtractor handles ActiveRecord migration file extraction.
9
- #
10
- # Scans `db/migrate/*.rb` for migration files and produces one
11
- # ExtractedUnit per migration. Extracts DDL metadata (tables, columns,
12
- # indexes, references), reversibility, risk indicators (data migrations,
13
- # raw SQL), and links to affected models via table name classification.
14
- #
15
- # @example
16
- # extractor = MigrationExtractor.new
17
- # units = extractor.extract_all
18
- # create_users = units.find { |u| u.identifier == "CreateUsers" }
19
- # create_users.metadata[:tables_affected] # => ["users"]
20
- #
21
- class MigrationExtractor
22
- include SharedUtilityMethods
23
- include SharedDependencyScanner
24
-
25
- # Rails internal tables that should not generate model dependencies
26
- INTERNAL_TABLES = %w[
27
- schema_migrations
28
- ar_internal_metadata
29
- active_storage_blobs
30
- active_storage_attachments
31
- active_storage_variant_records
32
- action_text_rich_texts
33
- action_mailbox_inbound_emails
34
- ].freeze
35
-
36
- # DDL operations that take a table name as the first symbol argument
37
- TABLE_OPERATIONS = %w[
38
- create_table
39
- drop_table
40
- rename_table
41
- add_column
42
- remove_column
43
- change_column
44
- rename_column
45
- add_index
46
- remove_index
47
- add_reference
48
- remove_reference
49
- add_belongs_to
50
- remove_belongs_to
51
- add_foreign_key
52
- remove_foreign_key
53
- add_timestamps
54
- remove_timestamps
55
- change_column_default
56
- change_column_null
57
- ].freeze
58
-
59
- # Column type methods used inside create_table blocks
60
- COLUMN_TYPE_METHODS = %w[
61
- string integer float decimal boolean binary text
62
- date datetime time timestamp
63
- bigint numeric json jsonb uuid inet cidr
64
- hstore ltree point polygon
65
- ].freeze
66
-
67
- # Patterns indicating data migration (not just DDL)
68
- DATA_MIGRATION_PATTERNS = [
69
- /\.update_all\b/,
70
- /\.find_each\b/,
71
- /\.find_in_batches\b/,
72
- /\.update!\b/,
73
- /\.update\b/,
74
- /\.save!\b/,
75
- /\.save\b/,
76
- /\.delete_all\b/,
77
- /\.destroy_all\b/
78
- ].freeze
79
-
80
- def initialize
81
- @migrate_dir = Rails.root.join('db/migrate')
82
- @has_directory = @migrate_dir.directory?
83
- end
84
-
85
- # Extract all migration files from db/migrate/
86
- #
87
- # @return [Array<ExtractedUnit>] List of migration units, sorted by timestamp
88
- def extract_all
89
- return [] unless @has_directory
90
-
91
- files = Dir[@migrate_dir.join('*.rb')]
92
- files.filter_map { |file| extract_migration_file(file) }
93
- end
94
-
95
- # Extract a single migration file
96
- #
97
- # @param file_path [String] Path to the migration file
98
- # @return [ExtractedUnit, nil] The extracted unit or nil if not a migration
99
- def extract_migration_file(file_path)
100
- source = File.read(file_path)
101
- class_name = extract_class_name(source)
102
-
103
- return nil unless class_name
104
- return nil unless migration_class?(source)
105
-
106
- unit = ExtractedUnit.new(
107
- type: :migration,
108
- identifier: class_name,
109
- file_path: file_path
110
- )
111
-
112
- unit.namespace = extract_namespace(class_name)
113
- unit.metadata = extract_metadata(source, file_path)
114
- unit.source_code = annotate_source(source, class_name, unit.metadata)
115
- unit.dependencies = extract_dependencies(source, unit.metadata)
116
-
117
- unit
118
- rescue StandardError => e
119
- Rails.logger.error("Failed to extract migration #{file_path}: #{e.message}")
120
- nil
121
- end
122
-
123
- private
124
-
125
- # ──────────────────────────────────────────────────────────────────────
126
- # Class Discovery
127
- # ──────────────────────────────────────────────────────────────────────
128
-
129
- # Extract the class name from migration source code.
130
- #
131
- # @param source [String] Ruby source code
132
- # @return [String, nil] The class name or nil
133
- def extract_class_name(source)
134
- # Match namespaced or plain class declarations
135
- namespaces = source.scan(/^\s*module\s+([\w:]+)/).flatten
136
- class_match = source.match(/^\s*class\s+([\w:]+)\s*</)
137
- return nil unless class_match
138
-
139
- base_class = class_match[1]
140
- if namespaces.any? && !base_class.include?('::')
141
- "#{namespaces.join('::')}::#{base_class}"
142
- else
143
- base_class
144
- end
145
- end
146
-
147
- # Check whether the source defines an ActiveRecord::Migration subclass.
148
- #
149
- # @param source [String] Ruby source code
150
- # @return [Boolean]
151
- def migration_class?(source)
152
- source.match?(/class\s+\w+\s*<\s*ActiveRecord::Migration/)
153
- end
154
-
155
- # ──────────────────────────────────────────────────────────────────────
156
- # Metadata Extraction
157
- # ──────────────────────────────────────────────────────────────────────
158
-
159
- # @param source [String] Ruby source code
160
- # @param file_path [String] Path to the migration file
161
- # @return [Hash] Migration metadata
162
- def extract_metadata(source, file_path)
163
- tables = extract_tables_affected(source)
164
- direction = detect_direction(source)
165
-
166
- {
167
- migration_version: extract_migration_version(file_path),
168
- rails_version: extract_rails_version(source),
169
- reversible: %w[change up_down].include?(direction),
170
- direction: direction,
171
- tables_affected: tables,
172
- columns_added: extract_columns_added(source),
173
- columns_removed: extract_columns_removed(source),
174
- indexes_added: extract_indexes_added(source),
175
- indexes_removed: extract_indexes_removed(source),
176
- references_added: extract_references_added(source),
177
- references_removed: extract_references_removed(source),
178
- operations: extract_operations(source),
179
- has_data_migration: data_migration?(source),
180
- has_execute_sql: source.match?(/\bexecute\s/),
181
- loc: source.lines.count { |l| l.strip.length.positive? && !l.strip.start_with?('#') }
182
- }
183
- end
184
-
185
- # Extract migration timestamp from filename.
186
- #
187
- # @param file_path [String] Path to the migration file
188
- # @return [String, nil] The timestamp or nil
189
- def extract_migration_version(file_path)
190
- basename = File.basename(file_path)
191
- match = basename.match(/\A(\d{14})_/)
192
- match ? match[1] : nil
193
- end
194
-
195
- # Extract Rails version from migration bracket notation.
196
- #
197
- # @param source [String] Ruby source code
198
- # @return [String, nil] The Rails version or nil
199
- def extract_rails_version(source)
200
- match = source.match(/ActiveRecord::Migration\[(\d+\.\d+)\]/)
201
- match ? match[1] : nil
202
- end
203
-
204
- # ──────────────────────────────────────────────────────────────────────
205
- # Direction / Reversibility
206
- # ──────────────────────────────────────────────────────────────────────
207
-
208
- # Detect migration direction from method definitions.
209
- #
210
- # @param source [String] Ruby source code
211
- # @return [String] One of "change", "up_down", "up_only", "unknown"
212
- def detect_direction(source)
213
- has_change = source.match?(/^\s*def\s+change\b/)
214
- has_up = source.match?(/^\s*def\s+up\b/)
215
- has_down = source.match?(/^\s*def\s+down\b/)
216
-
217
- if has_change
218
- 'change'
219
- elsif has_up && has_down
220
- 'up_down'
221
- elsif has_up
222
- 'up_only'
223
- else
224
- 'unknown'
225
- end
226
- end
227
-
228
- # ──────────────────────────────────────────────────────────────────────
229
- # DDL Extraction
230
- # ──────────────────────────────────────────────────────────────────────
231
-
232
- # Extract all tables affected by DDL operations.
233
- #
234
- # @param source [String] Ruby source code
235
- # @return [Array<String>] Deduplicated table names
236
- def extract_tables_affected(source)
237
- tables = []
238
-
239
- TABLE_OPERATIONS.each do |op|
240
- source.scan(/#{op}\s+:(\w+)/).each do |match|
241
- tables << match[0]
242
- end
243
- end
244
-
245
- # rename_table has two table arguments
246
- source.scan(/rename_table\s+:\w+\s*,\s*:(\w+)/).each do |match|
247
- tables << match[0]
248
- end
249
-
250
- tables.uniq
251
- end
252
-
253
- # Extract columns added via add_column and create_table block columns.
254
- #
255
- # @param source [String] Ruby source code
256
- # @return [Array<Hash>] Column info hashes with :table, :column, :type
257
- def extract_columns_added(source)
258
- # add_column :table, :column, :type
259
- columns = source.scan(/add_column\s+:(\w+)\s*,\s*:(\w+)\s*,\s*:(\w+)/).map do |table, column, type|
260
- { table: table, column: column, type: type }
261
- end
262
-
263
- # t.type :column inside create_table blocks
264
- extract_block_columns(source, columns)
265
-
266
- # t.column :name, :type
267
- extract_explicit_column_calls(source, columns)
268
-
269
- columns
270
- end
271
-
272
- # Extract columns removed via remove_column.
273
- #
274
- # @param source [String] Ruby source code
275
- # @return [Array<Hash>] Column info hashes
276
- def extract_columns_removed(source)
277
- source.scan(/remove_column\s+:(\w+)\s*,\s*:(\w+)(?:\s*,\s*:(\w+))?/).map do |table, column, type|
278
- { table: table, column: column, type: type || 'unknown' }
279
- end
280
- end
281
-
282
- # Extract indexes added via add_index.
283
- #
284
- # @param source [String] Ruby source code
285
- # @return [Array<Hash>] Index info hashes with :table, :column
286
- def extract_indexes_added(source)
287
- source.scan(/add_index\s+:(\w+)\s*,\s*(.+?)(?:\s*,\s*\w+:|$)/m).map do |table, column_expr|
288
- column = column_expr.strip.sub(/\s*,\s*\w+:.*\z/m, '').strip
289
- # Handle array syntax for composite indexes: [:user_id, :created_at]
290
- column = if column.start_with?('[')
291
- column.gsub(/[\[\]:"\s]/, '')
292
- else
293
- column.delete(':').strip
294
- end
295
- { table: table, column: column }
296
- end
297
- end
298
-
299
- # Extract indexes removed via remove_index.
300
- #
301
- # @param source [String] Ruby source code
302
- # @return [Array<Hash>] Index info hashes
303
- def extract_indexes_removed(source)
304
- source.scan(/remove_index\s+:(\w+)\s*,\s*:(\w+)/).map do |table, column|
305
- { table: table, column: column }
306
- end
307
- end
308
-
309
- # Extract references added via add_reference or t.references.
310
- #
311
- # @param source [String] Ruby source code
312
- # @return [Array<Hash>] Reference info hashes with :table, :reference
313
- def extract_references_added(source)
314
- # add_reference :table, :reference
315
- refs = source.scan(/add_reference\s+:(\w+)\s*,\s*:(\w+)/).map do |table, reference|
316
- { table: table, reference: reference }
317
- end
318
-
319
- # t.references :ref inside create_table blocks
320
- extract_block_references(source, refs)
321
-
322
- refs
323
- end
324
-
325
- # Extract references removed via remove_reference.
326
- #
327
- # @param source [String] Ruby source code
328
- # @return [Array<Hash>] Reference info hashes
329
- def extract_references_removed(source)
330
- source.scan(/remove_reference\s+:(\w+)\s*,\s*:(\w+)/).map do |table, reference|
331
- { table: table, reference: reference }
332
- end
333
- end
334
-
335
- # ──────────────────────────────────────────────────────────────────────
336
- # Block Column / Reference Parsing
337
- # ──────────────────────────────────────────────────────────────────────
338
-
339
- # Extract t.type :column declarations inside create_table blocks.
340
- #
341
- # @param source [String] Ruby source code
342
- # @param columns [Array<Hash>] Accumulator array
343
- # @return [void]
344
- def extract_block_columns(source, columns)
345
- # Find create_table blocks and parse t.type :column patterns
346
- source.scan(/create_table\s+:(\w+).*?do\s*\|(\w+)\|(.+?)^\s*end/m).each do |table, var, block|
347
- type_pattern = COLUMN_TYPE_METHODS.join('|')
348
- block.scan(/#{var}\.(#{type_pattern})\s+:(\w+)/).each do |type, column|
349
- columns << { table: table, column: column, type: type }
350
- end
351
- end
352
- end
353
-
354
- # Extract t.column :name, :type declarations inside create_table blocks.
355
- #
356
- # @param source [String] Ruby source code
357
- # @param columns [Array<Hash>] Accumulator array
358
- # @return [void]
359
- def extract_explicit_column_calls(source, columns)
360
- source.scan(/create_table\s+:(\w+).*?do\s*\|(\w+)\|(.+?)^\s*end/m).each do |table, var, block|
361
- block.scan(/#{var}\.column\s+:(\w+)\s*,\s*:(\w+)/).each do |column, type|
362
- columns << { table: table, column: column, type: type }
363
- end
364
- end
365
- end
366
-
367
- # Extract t.references declarations inside create_table blocks.
368
- #
369
- # @param source [String] Ruby source code
370
- # @param refs [Array<Hash>] Accumulator array
371
- # @return [void]
372
- def extract_block_references(source, refs)
373
- source.scan(/create_table\s+:(\w+).*?do\s*\|(\w+)\|(.+?)^\s*end/m).each do |table, var, block|
374
- block.scan(/#{var}\.references\s+:(\w+)/).each do |reference,|
375
- refs << { table: table, reference: reference }
376
- end
377
- end
378
- end
379
-
380
- # ──────────────────────────────────────────────────────────────────────
381
- # Operations Tracking
382
- # ──────────────────────────────────────────────────────────────────────
383
-
384
- # Extract operation counts from migration source.
385
- #
386
- # @param source [String] Ruby source code
387
- # @return [Array<Hash>] Operation hashes with :operation, :count
388
- def extract_operations(source)
389
- ops = Hash.new(0)
390
-
391
- TABLE_OPERATIONS.each do |op|
392
- count = source.scan(/#{op}\s+:/).size
393
- ops[op] = count if count.positive?
394
- end
395
-
396
- ops.map { |op, count| { operation: op, count: count } }
397
- end
398
-
399
- # ──────────────────────────────────────────────────────────────────────
400
- # Risk Indicators
401
- # ──────────────────────────────────────────────────────────────────────
402
-
403
- # Detect data migration patterns in source.
404
- #
405
- # @param source [String] Ruby source code
406
- # @return [Boolean]
407
- def data_migration?(source)
408
- DATA_MIGRATION_PATTERNS.any? { |pattern| source.match?(pattern) }
409
- end
410
-
411
- # ──────────────────────────────────────────────────────────────────────
412
- # Source Annotation
413
- # ──────────────────────────────────────────────────────────────────────
414
-
415
- # @param source [String] Ruby source code
416
- # @param class_name [String] The migration class name
417
- # @param metadata [Hash] Extracted metadata
418
- # @return [String] Annotated source
419
- def annotate_source(source, class_name, metadata)
420
- version = metadata[:migration_version] || 'none'
421
- tables = metadata[:tables_affected].join(', ')
422
- tables_display = tables.length > 59 ? "#{tables[0, 56]}..." : tables
423
- direction = metadata[:direction]
424
-
425
- <<~ANNOTATION
426
- # ╔═══════════════════════════════════════════════════════════════════════╗
427
- # ║ Migration: #{class_name.ljust(57)}║
428
- # ║ Version: #{version.ljust(59)}║
429
- # ║ Direction: #{direction.ljust(57)}║
430
- # ║ Tables: #{tables_display.ljust(60)}║
431
- # ╚═══════════════════════════════════════════════════════════════════════╝
432
-
433
- #{source}
434
- ANNOTATION
435
- end
436
-
437
- # ──────────────────────────────────────────────────────────────────────
438
- # Dependency Extraction
439
- # ──────────────────────────────────────────────────────────────────────
440
-
441
- # @param source [String] Ruby source code
442
- # @param metadata [Hash] Extracted metadata
443
- # @return [Array<Hash>] Dependency hashes
444
- def extract_dependencies(source, metadata)
445
- deps = []
446
-
447
- # Link tables to models via classify
448
- metadata[:tables_affected].each do |table|
449
- next if INTERNAL_TABLES.include?(table)
450
-
451
- model_name = table.classify
452
- deps << { type: :model, target: model_name, via: :table_name }
453
- end
454
-
455
- # Link references to models
456
- all_refs = (metadata[:references_added] + metadata[:references_removed]).uniq
457
- all_refs.each do |ref|
458
- model_name = ref[:reference].classify
459
- deps << { type: :model, target: model_name, via: :reference }
460
- end
461
-
462
- # Scan data migration code for common dependencies
463
- deps.concat(scan_common_dependencies(source))
464
-
465
- deps.uniq { |d| [d[:type], d[:target]] }
466
- end
467
- end
468
- end
469
- end