kumi 0.0.7 โ 0.0.8
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/CLAUDE.md +1 -1
- data/README.md +8 -5
- data/examples/game_of_life.rb +1 -1
- data/examples/static_analysis_errors.rb +7 -7
- data/lib/kumi/analyzer.rb +15 -15
- data/lib/kumi/compiler.rb +6 -6
- data/lib/kumi/core/analyzer/analysis_state.rb +39 -0
- data/lib/kumi/core/analyzer/constant_evaluator.rb +59 -0
- data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +248 -0
- data/lib/kumi/core/analyzer/passes/declaration_validator.rb +45 -0
- data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +153 -0
- data/lib/kumi/core/analyzer/passes/input_collector.rb +139 -0
- data/lib/kumi/core/analyzer/passes/name_indexer.rb +26 -0
- data/lib/kumi/core/analyzer/passes/pass_base.rb +52 -0
- data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +111 -0
- data/lib/kumi/core/analyzer/passes/toposorter.rb +110 -0
- data/lib/kumi/core/analyzer/passes/type_checker.rb +162 -0
- data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +48 -0
- data/lib/kumi/core/analyzer/passes/type_inferencer.rb +236 -0
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +406 -0
- data/lib/kumi/core/analyzer/passes/visitor_pass.rb +44 -0
- data/lib/kumi/core/atom_unsat_solver.rb +396 -0
- data/lib/kumi/core/compiled_schema.rb +43 -0
- data/lib/kumi/core/constraint_relationship_solver.rb +641 -0
- data/lib/kumi/core/domain/enum_analyzer.rb +55 -0
- data/lib/kumi/core/domain/range_analyzer.rb +85 -0
- data/lib/kumi/core/domain/validator.rb +82 -0
- data/lib/kumi/core/domain/violation_formatter.rb +42 -0
- data/lib/kumi/core/error_reporter.rb +166 -0
- data/lib/kumi/core/error_reporting.rb +97 -0
- data/lib/kumi/core/errors.rb +120 -0
- data/lib/kumi/core/evaluation_wrapper.rb +40 -0
- data/lib/kumi/core/explain.rb +295 -0
- data/lib/kumi/core/export/deserializer.rb +41 -0
- data/lib/kumi/core/export/errors.rb +14 -0
- data/lib/kumi/core/export/node_builders.rb +142 -0
- data/lib/kumi/core/export/node_registry.rb +54 -0
- data/lib/kumi/core/export/node_serializers.rb +158 -0
- data/lib/kumi/core/export/serializer.rb +25 -0
- data/lib/kumi/core/export.rb +35 -0
- data/lib/kumi/core/function_registry/collection_functions.rb +202 -0
- data/lib/kumi/core/function_registry/comparison_functions.rb +33 -0
- data/lib/kumi/core/function_registry/conditional_functions.rb +38 -0
- data/lib/kumi/core/function_registry/function_builder.rb +95 -0
- data/lib/kumi/core/function_registry/logical_functions.rb +44 -0
- data/lib/kumi/core/function_registry/math_functions.rb +74 -0
- data/lib/kumi/core/function_registry/string_functions.rb +57 -0
- data/lib/kumi/core/function_registry/type_functions.rb +53 -0
- data/lib/kumi/{function_registry.rb โ core/function_registry.rb} +28 -36
- data/lib/kumi/core/input/type_matcher.rb +97 -0
- data/lib/kumi/core/input/validator.rb +51 -0
- data/lib/kumi/core/input/violation_creator.rb +52 -0
- data/lib/kumi/core/json_schema/generator.rb +65 -0
- data/lib/kumi/core/json_schema/validator.rb +27 -0
- data/lib/kumi/core/json_schema.rb +16 -0
- data/lib/kumi/core/ruby_parser/build_context.rb +27 -0
- data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +38 -0
- data/lib/kumi/core/ruby_parser/dsl.rb +14 -0
- data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +138 -0
- data/lib/kumi/core/ruby_parser/expression_converter.rb +128 -0
- data/lib/kumi/core/ruby_parser/guard_rails.rb +45 -0
- data/lib/kumi/core/ruby_parser/input_builder.rb +127 -0
- data/lib/kumi/core/ruby_parser/input_field_proxy.rb +48 -0
- data/lib/kumi/core/ruby_parser/input_proxy.rb +31 -0
- data/lib/kumi/core/ruby_parser/nested_input.rb +17 -0
- data/lib/kumi/core/ruby_parser/parser.rb +71 -0
- data/lib/kumi/core/ruby_parser/schema_builder.rb +175 -0
- data/lib/kumi/core/ruby_parser/sugar.rb +263 -0
- data/lib/kumi/core/ruby_parser.rb +12 -0
- data/lib/kumi/core/schema_instance.rb +111 -0
- data/lib/kumi/core/types/builder.rb +23 -0
- data/lib/kumi/core/types/compatibility.rb +96 -0
- data/lib/kumi/core/types/formatter.rb +26 -0
- data/lib/kumi/core/types/inference.rb +42 -0
- data/lib/kumi/core/types/normalizer.rb +72 -0
- data/lib/kumi/core/types/validator.rb +37 -0
- data/lib/kumi/core/types.rb +66 -0
- data/lib/kumi/core/vectorization_metadata.rb +110 -0
- data/lib/kumi/errors.rb +1 -112
- data/lib/kumi/registry.rb +37 -0
- data/lib/kumi/schema.rb +5 -5
- data/lib/kumi/schema_metadata.rb +3 -3
- data/lib/kumi/syntax/array_expression.rb +6 -6
- data/lib/kumi/syntax/call_expression.rb +4 -4
- data/lib/kumi/syntax/cascade_expression.rb +4 -4
- data/lib/kumi/syntax/case_expression.rb +4 -4
- data/lib/kumi/syntax/declaration_reference.rb +4 -4
- data/lib/kumi/syntax/hash_expression.rb +4 -4
- data/lib/kumi/syntax/input_declaration.rb +5 -5
- data/lib/kumi/syntax/input_element_reference.rb +5 -5
- data/lib/kumi/syntax/input_reference.rb +5 -5
- data/lib/kumi/syntax/literal.rb +4 -4
- data/lib/kumi/syntax/node.rb +34 -34
- data/lib/kumi/syntax/root.rb +6 -6
- data/lib/kumi/syntax/trait_declaration.rb +4 -4
- data/lib/kumi/syntax/value_declaration.rb +4 -4
- data/lib/kumi/version.rb +1 -1
- data/migrate_to_core_iterative.rb +938 -0
- data/scripts/generate_function_docs.rb +9 -9
- metadata +75 -72
- data/lib/kumi/analyzer/analysis_state.rb +0 -37
- data/lib/kumi/analyzer/constant_evaluator.rb +0 -57
- data/lib/kumi/analyzer/passes/broadcast_detector.rb +0 -246
- data/lib/kumi/analyzer/passes/declaration_validator.rb +0 -43
- data/lib/kumi/analyzer/passes/dependency_resolver.rb +0 -151
- data/lib/kumi/analyzer/passes/input_collector.rb +0 -137
- data/lib/kumi/analyzer/passes/name_indexer.rb +0 -24
- data/lib/kumi/analyzer/passes/pass_base.rb +0 -50
- data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +0 -109
- data/lib/kumi/analyzer/passes/toposorter.rb +0 -108
- data/lib/kumi/analyzer/passes/type_checker.rb +0 -160
- data/lib/kumi/analyzer/passes/type_consistency_checker.rb +0 -46
- data/lib/kumi/analyzer/passes/type_inferencer.rb +0 -232
- data/lib/kumi/analyzer/passes/unsat_detector.rb +0 -404
- data/lib/kumi/analyzer/passes/visitor_pass.rb +0 -42
- data/lib/kumi/atom_unsat_solver.rb +0 -394
- data/lib/kumi/compiled_schema.rb +0 -41
- data/lib/kumi/constraint_relationship_solver.rb +0 -638
- data/lib/kumi/domain/enum_analyzer.rb +0 -53
- data/lib/kumi/domain/range_analyzer.rb +0 -83
- data/lib/kumi/domain/validator.rb +0 -80
- data/lib/kumi/domain/violation_formatter.rb +0 -40
- data/lib/kumi/error_reporter.rb +0 -164
- data/lib/kumi/error_reporting.rb +0 -95
- data/lib/kumi/evaluation_wrapper.rb +0 -38
- data/lib/kumi/explain.rb +0 -293
- data/lib/kumi/export/deserializer.rb +0 -39
- data/lib/kumi/export/errors.rb +0 -12
- data/lib/kumi/export/node_builders.rb +0 -140
- data/lib/kumi/export/node_registry.rb +0 -52
- data/lib/kumi/export/node_serializers.rb +0 -156
- data/lib/kumi/export/serializer.rb +0 -23
- data/lib/kumi/export.rb +0 -33
- data/lib/kumi/function_registry/collection_functions.rb +0 -200
- data/lib/kumi/function_registry/comparison_functions.rb +0 -31
- data/lib/kumi/function_registry/conditional_functions.rb +0 -36
- data/lib/kumi/function_registry/function_builder.rb +0 -93
- data/lib/kumi/function_registry/logical_functions.rb +0 -42
- data/lib/kumi/function_registry/math_functions.rb +0 -72
- data/lib/kumi/function_registry/string_functions.rb +0 -54
- data/lib/kumi/function_registry/type_functions.rb +0 -51
- data/lib/kumi/input/type_matcher.rb +0 -95
- data/lib/kumi/input/validator.rb +0 -49
- data/lib/kumi/input/violation_creator.rb +0 -50
- data/lib/kumi/json_schema/generator.rb +0 -63
- data/lib/kumi/json_schema/validator.rb +0 -25
- data/lib/kumi/json_schema.rb +0 -14
- data/lib/kumi/ruby_parser/build_context.rb +0 -25
- data/lib/kumi/ruby_parser/declaration_reference_proxy.rb +0 -36
- data/lib/kumi/ruby_parser/dsl.rb +0 -12
- data/lib/kumi/ruby_parser/dsl_cascade_builder.rb +0 -136
- data/lib/kumi/ruby_parser/expression_converter.rb +0 -126
- data/lib/kumi/ruby_parser/guard_rails.rb +0 -43
- data/lib/kumi/ruby_parser/input_builder.rb +0 -125
- data/lib/kumi/ruby_parser/input_field_proxy.rb +0 -46
- data/lib/kumi/ruby_parser/input_proxy.rb +0 -29
- data/lib/kumi/ruby_parser/nested_input.rb +0 -15
- data/lib/kumi/ruby_parser/parser.rb +0 -69
- data/lib/kumi/ruby_parser/schema_builder.rb +0 -173
- data/lib/kumi/ruby_parser/sugar.rb +0 -261
- data/lib/kumi/ruby_parser.rb +0 -10
- data/lib/kumi/schema_instance.rb +0 -109
- data/lib/kumi/types/builder.rb +0 -21
- data/lib/kumi/types/compatibility.rb +0 -94
- data/lib/kumi/types/formatter.rb +0 -24
- data/lib/kumi/types/inference.rb +0 -40
- data/lib/kumi/types/normalizer.rb +0 -70
- data/lib/kumi/types/validator.rb +0 -35
- data/lib/kumi/types.rb +0 -64
- data/lib/kumi/vectorization_metadata.rb +0 -108
@@ -0,0 +1,938 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "fileutils"
|
5
|
+
require "open3"
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
class IterativeKumiCoreMigrator
|
9
|
+
attr_reader :errors, :metadata, :change_tracker
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@errors = []
|
13
|
+
@change_tracker = {}
|
14
|
+
@change_thresholds = {
|
15
|
+
warning: 10,
|
16
|
+
critical: 25,
|
17
|
+
suspicious: 50
|
18
|
+
}
|
19
|
+
@start_time = Time.now
|
20
|
+
@phases = []
|
21
|
+
@rollback_points = []
|
22
|
+
@current_phase = nil
|
23
|
+
@stats = {
|
24
|
+
files_to_migrate: 0,
|
25
|
+
files_updated: 0,
|
26
|
+
files_moved: 0,
|
27
|
+
total_changes: 0
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def migrate!
|
32
|
+
log_phase("๐ Starting Iterative Kumi to Kumi::Core Migration")
|
33
|
+
|
34
|
+
begin
|
35
|
+
# Iterative approach to handle Zeitwerk properly
|
36
|
+
run_phase("Phase 1: Setup & Analysis") { phase_1_setup_and_analysis }
|
37
|
+
run_phase("Phase 2: Update Module Declarations In-Place") { phase_2_update_modules_in_place }
|
38
|
+
run_phase("Phase 3: Move Files to Core") { phase_3_move_files_to_core }
|
39
|
+
run_phase("Phase 4: Update External References") { phase_4_update_references }
|
40
|
+
run_phase("Phase 5: Final Validation") { phase_5_final_validation }
|
41
|
+
|
42
|
+
finalize_migration
|
43
|
+
log_completion_summary
|
44
|
+
log_phase("โ
Migration completed successfully!", :success)
|
45
|
+
rescue StandardError => e
|
46
|
+
log_phase("โ Migration failed: #{e.message}", :error)
|
47
|
+
handle_failure(e)
|
48
|
+
restore_to_initial_state
|
49
|
+
raise e
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# ====================
|
56
|
+
# PHASE MANAGEMENT
|
57
|
+
# ====================
|
58
|
+
|
59
|
+
def run_phase(phase_name, &block)
|
60
|
+
@current_phase = phase_name
|
61
|
+
log_phase("Starting #{phase_name}")
|
62
|
+
|
63
|
+
# Create rollback point before each phase
|
64
|
+
create_rollback_point(phase_name)
|
65
|
+
|
66
|
+
begin
|
67
|
+
result = yield
|
68
|
+
log_phase("โ
#{phase_name} completed successfully")
|
69
|
+
@phases << { name: phase_name, status: :success }
|
70
|
+
result
|
71
|
+
rescue StandardError => e
|
72
|
+
log_phase("โ #{phase_name} FAILED: #{e.message}", :error)
|
73
|
+
@phases << { name: phase_name, status: :failed, error: e.message }
|
74
|
+
raise e
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def log_phase(message, level = :info)
|
79
|
+
timestamp = Time.now.strftime("%H:%M:%S")
|
80
|
+
icon = case level
|
81
|
+
when :success then "โ
"
|
82
|
+
when :error then "โ"
|
83
|
+
when :warning then "โ ๏ธ"
|
84
|
+
else "โน๏ธ"
|
85
|
+
end
|
86
|
+
|
87
|
+
puts "[#{timestamp}] #{icon} #{message}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_rollback_point(phase_name)
|
91
|
+
# Store initial commit for potential rollback
|
92
|
+
if @rollback_points.empty?
|
93
|
+
initial_commit = `git rev-parse HEAD`.strip
|
94
|
+
@rollback_points << { phase: "Initial State", commit: initial_commit }
|
95
|
+
log_phase("๐ Initial commit stored: #{initial_commit[0..7]}")
|
96
|
+
end
|
97
|
+
|
98
|
+
commit_msg = "Rollback point before #{phase_name}"
|
99
|
+
system("git add -A && git commit -m '#{commit_msg}' --allow-empty")
|
100
|
+
commit_hash = `git rev-parse HEAD`.strip
|
101
|
+
|
102
|
+
@rollback_points << { phase: phase_name, commit: commit_hash }
|
103
|
+
|
104
|
+
log_phase("๐ Created rollback point: #{commit_hash[0..7]} for #{phase_name}")
|
105
|
+
end
|
106
|
+
|
107
|
+
# ====================
|
108
|
+
# PHASE 1: SETUP & ANALYSIS
|
109
|
+
# ====================
|
110
|
+
|
111
|
+
def phase_1_setup_and_analysis
|
112
|
+
log_phase("๐ Analyzing current structure...")
|
113
|
+
|
114
|
+
# Analyze current files
|
115
|
+
files_to_migrate = analyze_files_to_migrate
|
116
|
+
@stats[:files_to_migrate] = files_to_migrate.length
|
117
|
+
log_phase("๐ Found #{files_to_migrate.length} files to migrate to core/")
|
118
|
+
|
119
|
+
# Show file breakdown by type
|
120
|
+
file_types = files_to_migrate.group_by { |f| f.split("/")[2] || "root" }
|
121
|
+
file_types.each do |type, files|
|
122
|
+
log_phase(" โโ #{files.length} #{type} files")
|
123
|
+
end
|
124
|
+
|
125
|
+
# Analyze Zeitwerk expectations
|
126
|
+
expected_constants = analyze_zeitwerk_structure
|
127
|
+
log_phase("๐ Zeitwerk will expect #{expected_constants} new Core:: constants")
|
128
|
+
|
129
|
+
# Create core directory
|
130
|
+
FileUtils.mkdir_p("lib/kumi/core")
|
131
|
+
log_phase("๐ Created lib/kumi/core/ directory structure")
|
132
|
+
|
133
|
+
# Test that everything still works before changes
|
134
|
+
log_phase("๐งช Running pre-migration tests...")
|
135
|
+
run_basic_tests("Phase 1 - Pre-migration baseline")
|
136
|
+
log_phase("โ
Baseline tests passed - ready to proceed")
|
137
|
+
end
|
138
|
+
|
139
|
+
def analyze_files_to_migrate
|
140
|
+
exclude_files = [
|
141
|
+
"lib/kumi/cli.rb",
|
142
|
+
"lib/kumi/version.rb",
|
143
|
+
"lib/kumi/schema.rb"
|
144
|
+
]
|
145
|
+
|
146
|
+
Dir.glob("lib/kumi/**/*.rb").reject do |file|
|
147
|
+
exclude_files.include?(file) ||
|
148
|
+
file.include?("/core/") ||
|
149
|
+
File.directory?(file)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def analyze_zeitwerk_structure
|
154
|
+
# Check what constants Zeitwerk will expect after file moves
|
155
|
+
expected_count = 0
|
156
|
+
|
157
|
+
Dir.glob("lib/kumi/**/*.rb").each do |file|
|
158
|
+
next if file.include?("/core/")
|
159
|
+
next if ["cli.rb", "version.rb", "schema.rb"].any? { |skip| file.end_with?(skip) }
|
160
|
+
|
161
|
+
expected_count += 1
|
162
|
+
end
|
163
|
+
|
164
|
+
expected_count
|
165
|
+
end
|
166
|
+
|
167
|
+
def camelize(string)
|
168
|
+
string.split("/").map do |part|
|
169
|
+
part.split("_").map(&:capitalize).join
|
170
|
+
end.join("::")
|
171
|
+
end
|
172
|
+
|
173
|
+
# ====================
|
174
|
+
# PHASE 2: UPDATE MODULE DECLARATIONS IN-PLACE
|
175
|
+
# ====================
|
176
|
+
|
177
|
+
def phase_2_update_modules_in_place
|
178
|
+
log_phase("๐ Updating module declarations in-place (Zeitwerk compatibility)...")
|
179
|
+
|
180
|
+
files_to_update = Dir.glob("lib/kumi/**/*.rb").reject do |file|
|
181
|
+
file.include?("/core/") ||
|
182
|
+
["cli.rb", "version.rb", "schema.rb"].any? { |skip| file.end_with?(skip) }
|
183
|
+
end
|
184
|
+
|
185
|
+
log_phase(" Processing #{files_to_update.length} files...")
|
186
|
+
|
187
|
+
files_updated = 0
|
188
|
+
files_to_update.each_with_index do |file, index|
|
189
|
+
files_updated += 1 if update_file_module_declaration(file)
|
190
|
+
end
|
191
|
+
|
192
|
+
@stats[:files_updated] = files_updated
|
193
|
+
log_phase("โ
Updated #{files_updated} files: Kumi:: โ Kumi::Core::")
|
194
|
+
|
195
|
+
# Critical test - ensure Zeitwerk can load updated modules
|
196
|
+
log_phase("๐งช Testing Zeitwerk compatibility...")
|
197
|
+
run_basic_tests("Phase 2 - Zeitwerk compatibility check")
|
198
|
+
log_phase("โ
Zeitwerk autoloading working correctly")
|
199
|
+
end
|
200
|
+
|
201
|
+
def update_file_module_declaration(file)
|
202
|
+
content = File.read(file)
|
203
|
+
original_content = content.dup
|
204
|
+
|
205
|
+
# Pattern 1: Simple "module Kumi" -> "module Kumi::Core"
|
206
|
+
content.gsub!(/^(\s*)module Kumi(\s*$)/) { "#{::Regexp.last_match(1)}module Kumi::Core#{::Regexp.last_match(2)}" }
|
207
|
+
content.gsub!(/^(\s*)module Kumi(\s*#.*)$/) { "#{::Regexp.last_match(1)}module Kumi::Core#{::Regexp.last_match(2)}" }
|
208
|
+
|
209
|
+
# Pattern 2: Nested modules "module Kumi::Something" -> "module Kumi::Core::Something"
|
210
|
+
content.gsub!(/^(\s*)module Kumi::([A-Z]\w*)/) { "#{::Regexp.last_match(1)}module Kumi::Core::#{::Regexp.last_match(2)}" }
|
211
|
+
|
212
|
+
# INLINE VALIDATION: Fix common issues while reading
|
213
|
+
content = apply_inline_fixes(content, file, :namespace_update)
|
214
|
+
|
215
|
+
if content != original_content
|
216
|
+
track_file_changes(file, original_content, content, :in_place_module_update)
|
217
|
+
File.write(file, content)
|
218
|
+
return true
|
219
|
+
end
|
220
|
+
|
221
|
+
false
|
222
|
+
end
|
223
|
+
|
224
|
+
# ====================
|
225
|
+
# PHASE 3: MOVE FILES TO CORE
|
226
|
+
# ====================
|
227
|
+
|
228
|
+
def phase_3_move_files_to_core
|
229
|
+
log_phase("Moving files to core directory...")
|
230
|
+
|
231
|
+
files_to_move = analyze_files_to_migrate
|
232
|
+
moved_count = 0
|
233
|
+
|
234
|
+
files_to_move.each do |file|
|
235
|
+
moved_count += 1 if move_file_to_core_with_git(file)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Clean up empty directories
|
239
|
+
clean_empty_directories
|
240
|
+
|
241
|
+
log_phase("Moved #{moved_count} files to core")
|
242
|
+
|
243
|
+
# CRITICAL: Re-apply module declarations to moved files
|
244
|
+
# (git mv preserves original content, so we need to re-update the modules)
|
245
|
+
log_phase("๐ Re-applying module declarations to moved files...")
|
246
|
+
reapply_module_declarations_to_core_files
|
247
|
+
|
248
|
+
# Test that Zeitwerk can find all the moved modules
|
249
|
+
run_basic_tests("Phase 3 - After file moves")
|
250
|
+
|
251
|
+
@stats[:files_moved] = moved_count
|
252
|
+
end
|
253
|
+
|
254
|
+
def move_file_to_core_with_git(file)
|
255
|
+
relative_path = file.sub("lib/kumi/", "")
|
256
|
+
new_path = "lib/kumi/core/#{relative_path}"
|
257
|
+
|
258
|
+
# Create directory if needed
|
259
|
+
FileUtils.mkdir_p(File.dirname(new_path))
|
260
|
+
|
261
|
+
# Use git mv to preserve history
|
262
|
+
result = system("git mv '#{file}' '#{new_path}' 2>/dev/null")
|
263
|
+
if result
|
264
|
+
log_phase(" Moved #{file} -> #{new_path}")
|
265
|
+
true
|
266
|
+
else
|
267
|
+
record_error("Failed to move #{file} to #{new_path}")
|
268
|
+
false
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def reapply_module_declarations_to_core_files
|
273
|
+
core_files = Dir.glob("lib/kumi/core/**/*.rb")
|
274
|
+
updated_count = 0
|
275
|
+
|
276
|
+
core_files.each do |file|
|
277
|
+
updated_count += 1 if update_file_module_declaration(file)
|
278
|
+
end
|
279
|
+
|
280
|
+
log_phase(" โ
Re-applied module declarations to #{updated_count} core files")
|
281
|
+
end
|
282
|
+
|
283
|
+
def clean_empty_directories
|
284
|
+
Dir.glob("lib/kumi/*/").each do |dir|
|
285
|
+
next if dir.include?("/core/")
|
286
|
+
|
287
|
+
if Dir.empty?(dir)
|
288
|
+
Dir.rmdir(dir)
|
289
|
+
log_phase(" Removed empty directory #{dir}")
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# ====================
|
295
|
+
# PHASE 4: UPDATE EXTERNAL REFERENCES
|
296
|
+
# ====================
|
297
|
+
|
298
|
+
def phase_4_update_references
|
299
|
+
log_phase("Updating external references...")
|
300
|
+
|
301
|
+
# Update public interface files
|
302
|
+
update_public_interface_files
|
303
|
+
|
304
|
+
# Update spec files
|
305
|
+
update_spec_files
|
306
|
+
|
307
|
+
# Update other files (examples, scripts, etc.)
|
308
|
+
update_other_files
|
309
|
+
|
310
|
+
# Test that all references work
|
311
|
+
run_basic_tests("Phase 4 - After reference updates")
|
312
|
+
end
|
313
|
+
|
314
|
+
def update_public_interface_files
|
315
|
+
log_phase(" Updating public interface files...")
|
316
|
+
|
317
|
+
core_modules = %w[
|
318
|
+
Analyzer Compiler Types Syntax Export Input Domain RubyParser
|
319
|
+
Kumi::Registry.SchemaInstance SchemaMetadata Explain CompiledSchema
|
320
|
+
EvaluationWrapper ErrorReporter ErrorReporting VectorizationMetadata
|
321
|
+
JsonSchema AtomUnsatSolver ConstraintRelationshipSolver
|
322
|
+
]
|
323
|
+
|
324
|
+
["lib/kumi/schema.rb", "lib/kumi.rb"].each do |file|
|
325
|
+
next unless File.exist?(file)
|
326
|
+
|
327
|
+
update_file_references(file, core_modules, :public_interface_update)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def update_spec_files
|
332
|
+
log_phase(" Updating spec files...")
|
333
|
+
|
334
|
+
spec_files = Dir.glob("{spec,test}/**/*.rb")
|
335
|
+
spec_files.each { |file| update_spec_file_references(file) }
|
336
|
+
end
|
337
|
+
|
338
|
+
def update_other_files
|
339
|
+
log_phase(" Updating other files...")
|
340
|
+
|
341
|
+
other_files = Dir.glob("{examples,docs,scripts}/**/*.rb")
|
342
|
+
other_files.each { |file| update_spec_file_references(file) if File.exist?(file) }
|
343
|
+
end
|
344
|
+
|
345
|
+
def update_file_references(file, core_modules, change_type)
|
346
|
+
content = File.read(file)
|
347
|
+
original_content = content.dup
|
348
|
+
|
349
|
+
core_modules.each do |mod|
|
350
|
+
content.gsub!(/\b#{mod}\./) { "Core::#{mod}." }
|
351
|
+
content.gsub!(/\b#{mod}::/) { "Core::#{mod}::" }
|
352
|
+
content.gsub!(/(\W)#{mod}(\s*\.)/) { "#{::Regexp.last_match(1)}Core::#{mod}#{::Regexp.last_match(2)}" }
|
353
|
+
end
|
354
|
+
|
355
|
+
# INLINE VALIDATION: Apply additional fixes
|
356
|
+
content = apply_inline_fixes(content, file, :reference_update)
|
357
|
+
|
358
|
+
return unless content != original_content
|
359
|
+
|
360
|
+
track_file_changes(file, original_content, content, change_type)
|
361
|
+
File.write(file, content)
|
362
|
+
end
|
363
|
+
|
364
|
+
def update_spec_file_references(file)
|
365
|
+
content = File.read(file)
|
366
|
+
original_content = content.dup
|
367
|
+
|
368
|
+
core_modules = %w[
|
369
|
+
Analyzer Compiler Types Syntax Export Input Domain RubyParser
|
370
|
+
Kumi::Registry.SchemaInstance SchemaMetadata Explain CompiledSchema
|
371
|
+
EvaluationWrapper ErrorReporter ErrorReporting VectorizationMetadata
|
372
|
+
JsonSchema AtomUnsatSolver ConstraintRelationshipSolver
|
373
|
+
]
|
374
|
+
|
375
|
+
core_modules.each do |mod|
|
376
|
+
# Apply Core prefix, but skip VERSION patterns
|
377
|
+
content.gsub!(/\bKumi::#{mod}(?!::[A-Z][a-zA-Z0-9]*::VERSION\b|::VERSION\b)/) { "Kumi::Core::#{mod}" }
|
378
|
+
end
|
379
|
+
|
380
|
+
# Special cases
|
381
|
+
content.gsub!("include Kumi::Syntax", "include Kumi::Core::Syntax")
|
382
|
+
content.gsub!("include Kumi::ErrorReporting", "include Kumi::Core::ErrorReporting")
|
383
|
+
|
384
|
+
core_modules.each do |mod|
|
385
|
+
content.gsub!(/^(\s*)#{mod}\./) { "#{::Regexp.last_match(1)}Kumi::Core::#{mod}." }
|
386
|
+
end
|
387
|
+
|
388
|
+
# INLINE VALIDATION: Apply additional fixes
|
389
|
+
content = apply_inline_fixes(content, file, :spec_update)
|
390
|
+
|
391
|
+
return unless content != original_content
|
392
|
+
|
393
|
+
track_file_changes(file, original_content, content, :spec_reference_update)
|
394
|
+
File.write(file, content)
|
395
|
+
end
|
396
|
+
|
397
|
+
# ====================
|
398
|
+
# PHASE 5: FINAL VALIDATION
|
399
|
+
# ====================
|
400
|
+
|
401
|
+
def phase_5_final_validation
|
402
|
+
log_phase("Running comprehensive validation...")
|
403
|
+
|
404
|
+
# Test 1: Basic loading
|
405
|
+
run_basic_tests("Final - Basic loading")
|
406
|
+
|
407
|
+
# Test 2: Core module structure
|
408
|
+
test_core_module_structure
|
409
|
+
|
410
|
+
# Test 3: Final cleanup pass for Errors references
|
411
|
+
fix_remaining_errors_references
|
412
|
+
|
413
|
+
# Test 4: Full test suite (after cleanup)
|
414
|
+
run_full_test_suite
|
415
|
+
|
416
|
+
# Analyze change statistics
|
417
|
+
analyze_final_statistics
|
418
|
+
end
|
419
|
+
|
420
|
+
def test_core_module_structure
|
421
|
+
log_phase(" Testing core module structure...")
|
422
|
+
|
423
|
+
test_script = <<~RUBY
|
424
|
+
require "./lib/kumi"
|
425
|
+
|
426
|
+
# Test that Core module exists
|
427
|
+
raise "Kumi::Core not defined" unless defined?(Kumi::Core)
|
428
|
+
|
429
|
+
# Test a few key modules
|
430
|
+
core_modules = %w[Analyzer Compiler Syntax]
|
431
|
+
core_modules.each do |mod|
|
432
|
+
const_name = "Kumi::Core::\#{mod}"
|
433
|
+
raise "\#{const_name} not available" unless Object.const_defined?(const_name)
|
434
|
+
end
|
435
|
+
|
436
|
+
puts "โ
Core module structure validated"
|
437
|
+
RUBY
|
438
|
+
|
439
|
+
result = system("ruby -e '#{test_script}' 2>/dev/null")
|
440
|
+
return if result
|
441
|
+
|
442
|
+
record_error("Core module structure validation failed")
|
443
|
+
raise "Core module structure is invalid"
|
444
|
+
end
|
445
|
+
|
446
|
+
def fix_remaining_errors_references
|
447
|
+
log_phase(" Final cleanup pass for VERSION references...")
|
448
|
+
|
449
|
+
fixed_count = 0
|
450
|
+
|
451
|
+
# Find all files that might have incorrect VERSION references
|
452
|
+
files_to_check = Dir.glob("lib/**/*.rb") + Dir.glob("spec/**/*.rb") + Dir.glob("examples/**/*.rb")
|
453
|
+
|
454
|
+
files_to_check.each do |file|
|
455
|
+
content = File.read(file)
|
456
|
+
original_content = content.dup
|
457
|
+
|
458
|
+
# Only fix VERSION references like Kumi::Core::Export::Serializer::VERSION -> Kumi::VERSION
|
459
|
+
content.gsub!(/Kumi::Core::[A-Z]\w*(?:::[A-Z]\w*)*::VERSION\b/, "Kumi::VERSION")
|
460
|
+
|
461
|
+
if content != original_content
|
462
|
+
File.write(file, content)
|
463
|
+
fixed_count += 1
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
if fixed_count > 0
|
468
|
+
log_phase(" ๐ง Fixed VERSION references in #{fixed_count} files")
|
469
|
+
else
|
470
|
+
log_phase(" โ
No incorrect VERSION references found")
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
def run_full_test_suite
|
475
|
+
log_phase("๐งช Running full test suite...")
|
476
|
+
|
477
|
+
stdout, stderr, status = Open3.capture3("bundle exec rspec")
|
478
|
+
|
479
|
+
if status.success?
|
480
|
+
# Extract test summary from output
|
481
|
+
summary_line = stdout.lines.find { |line| line.include?("examples") && line.include?("failures") }
|
482
|
+
log_phase(" โ
Full test suite passed!")
|
483
|
+
log_phase(" ๐ #{summary_line.chomp}") if summary_line
|
484
|
+
else
|
485
|
+
log_phase(" โ Full test suite FAILED", :error)
|
486
|
+
|
487
|
+
# Analyze uninitialized constant errors before writing logs
|
488
|
+
test_output = stderr.empty? ? stdout : stderr
|
489
|
+
analyze_uninitialized_constant_errors(test_output)
|
490
|
+
|
491
|
+
# Write failed test logs to test_rspec.logs
|
492
|
+
File.write("test_rspec.logs", test_output)
|
493
|
+
log_phase(" ๐ Test failure logs written to test_rspec.logs")
|
494
|
+
|
495
|
+
record_error("Full test suite failed")
|
496
|
+
raise "Full test suite failed"
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
def analyze_uninitialized_constant_errors(test_output)
|
501
|
+
log_phase(" ๐ Analyzing uninitialized constant errors...")
|
502
|
+
|
503
|
+
# Extract all uninitialized constant errors
|
504
|
+
constant_errors = []
|
505
|
+
test_output.lines.each do |line|
|
506
|
+
if line.match?(/NameError.*uninitialized constant ([A-Z][a-zA-Z0-9:]*[A-Z][a-zA-Z0-9]*)/)
|
507
|
+
constant = line.match(/NameError.*uninitialized constant ([A-Z][a-zA-Z0-9:]*[A-Z][a-zA-Z0-9]*)/)[1]
|
508
|
+
constant_errors << constant
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
if constant_errors.any?
|
513
|
+
unique_errors = constant_errors.uniq.sort
|
514
|
+
log_phase(" ๐ Found #{constant_errors.length} uninitialized constant errors (#{unique_errors.length} unique):")
|
515
|
+
unique_errors.each do |const|
|
516
|
+
count = constant_errors.count(const)
|
517
|
+
log_phase(" #{count}x #{const}")
|
518
|
+
end
|
519
|
+
else
|
520
|
+
log_phase(" โ
No uninitialized constant errors found")
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
def analyze_final_statistics
|
525
|
+
return if @change_tracker.empty?
|
526
|
+
|
527
|
+
@stats[:total_changes] = @change_tracker.values.sum { |stats| stats[:total_changes] }
|
528
|
+
total_files = @change_tracker.length
|
529
|
+
|
530
|
+
log_phase("๐ Change Statistics:")
|
531
|
+
log_phase(" Files modified: #{total_files}")
|
532
|
+
log_phase(" Total changes: #{@stats[:total_changes]}")
|
533
|
+
|
534
|
+
# Show files with high change counts
|
535
|
+
flagged_files = []
|
536
|
+
@change_tracker.each do |file, stats|
|
537
|
+
total = stats[:total_changes]
|
538
|
+
|
539
|
+
if total >= @change_thresholds[:suspicious]
|
540
|
+
flagged_files << { file: file, count: total, level: :suspicious }
|
541
|
+
elsif total >= @change_thresholds[:critical]
|
542
|
+
flagged_files << { file: file, count: total, level: :critical }
|
543
|
+
elsif total >= @change_thresholds[:warning]
|
544
|
+
flagged_files << { file: file, count: total, level: :warning }
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
return unless flagged_files.any?
|
549
|
+
|
550
|
+
log_phase("๐จ #{flagged_files.length} files need review:")
|
551
|
+
flagged_files.each { |f| log_phase(" #{f[:file]} (#{f[:count]} changes)") }
|
552
|
+
end
|
553
|
+
|
554
|
+
def run_cleanup_validation
|
555
|
+
log_phase("๐ Running cleanup validation...")
|
556
|
+
|
557
|
+
issues_found = 0
|
558
|
+
|
559
|
+
# Check for double Core:: patterns
|
560
|
+
issues_found += fix_double_core_patterns
|
561
|
+
|
562
|
+
# Check for missing Core:: in moved files
|
563
|
+
issues_found += fix_missing_core_references
|
564
|
+
|
565
|
+
if issues_found > 0
|
566
|
+
log_phase("๐ง Fixed #{issues_found} reference issues automatically")
|
567
|
+
|
568
|
+
# Re-test basic loading after fixes
|
569
|
+
run_basic_tests("Post-cleanup validation")
|
570
|
+
else
|
571
|
+
log_phase("โ
No cleanup issues found")
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
def fix_double_core_patterns
|
576
|
+
log_phase(" Checking for double Core:: patterns...")
|
577
|
+
fixes = 0
|
578
|
+
|
579
|
+
# Fix module declarations: Kumi::Core::Core -> Kumi::Core
|
580
|
+
Dir.glob("lib/kumi/core/**/*.rb").each do |file|
|
581
|
+
content = File.read(file)
|
582
|
+
original_content = content.dup
|
583
|
+
|
584
|
+
# Fix double Core in module declarations
|
585
|
+
content.gsub!("module Kumi::Core::Core", "module Kumi::Core")
|
586
|
+
|
587
|
+
# Fix double Core in references
|
588
|
+
content.gsub!("Kumi::Core::Core::", "Kumi::Core::")
|
589
|
+
content.gsub!("Core::Core::", "Core::")
|
590
|
+
|
591
|
+
next unless content != original_content
|
592
|
+
|
593
|
+
File.write(file, content)
|
594
|
+
fixes += 1
|
595
|
+
end
|
596
|
+
|
597
|
+
# Also check public interface files
|
598
|
+
["lib/kumi/schema.rb"].each do |file|
|
599
|
+
next unless File.exist?(file)
|
600
|
+
|
601
|
+
content = File.read(file)
|
602
|
+
original_content = content.dup
|
603
|
+
|
604
|
+
content.gsub!("Core::Core::", "Core::")
|
605
|
+
|
606
|
+
next unless content != original_content
|
607
|
+
|
608
|
+
File.write(file, content)
|
609
|
+
fixes += 1
|
610
|
+
end
|
611
|
+
|
612
|
+
fixes
|
613
|
+
end
|
614
|
+
|
615
|
+
def fix_missing_core_references
|
616
|
+
log_phase(" Checking for missing Core:: references in moved files...")
|
617
|
+
fixes = 0
|
618
|
+
|
619
|
+
# Pattern: files in core/ should reference other core modules with Core::
|
620
|
+
Dir.glob("lib/kumi/core/**/*.rb").each do |file|
|
621
|
+
content = File.read(file)
|
622
|
+
original_content = content.dup
|
623
|
+
|
624
|
+
# Core modules that moved and need Core:: prefix when referenced
|
625
|
+
core_modules = %w[
|
626
|
+
Types Syntax Export Input Domain FunctionRegistry
|
627
|
+
SchemaInstance SchemaMetadata Explain CompiledSchema
|
628
|
+
EvaluationWrapper ErrorReporter ErrorReporting VectorizationMetadata
|
629
|
+
JsonSchema AtomUnsatSolver ConstraintRelationshipSolver
|
630
|
+
]
|
631
|
+
|
632
|
+
# Fix bare Kumi::ModuleName references (but not Kumi::Core::)
|
633
|
+
core_modules.each do |mod|
|
634
|
+
# Only fix if it's clearly a reference to a moved module
|
635
|
+
content.gsub!(/\bKumi::#{mod}(?!::)/, "Kumi::Core::#{mod}")
|
636
|
+
end
|
637
|
+
|
638
|
+
# Fix string literal references (in eval, using statements, etc.)
|
639
|
+
content.gsub!(/"using Kumi::([A-Z]\w+(?:::[A-Z]\w+)*)"/) do |match|
|
640
|
+
module_path = ::Regexp.last_match(1)
|
641
|
+
"\"using Kumi::Core::#{module_path}\""
|
642
|
+
end
|
643
|
+
|
644
|
+
# Fix other Kumi:: references in string literals
|
645
|
+
content.gsub!(/(['"])([^'"]*?)Kumi::([A-Z]\w+(?:::[A-Z]\w+)*)([^'"]*?)\1/) do |match|
|
646
|
+
quote = ::Regexp.last_match(1)
|
647
|
+
prefix = ::Regexp.last_match(2)
|
648
|
+
module_path = ::Regexp.last_match(3)
|
649
|
+
suffix = ::Regexp.last_match(4)
|
650
|
+
"#{quote}#{prefix}Kumi::Core::#{module_path}#{suffix}#{quote}"
|
651
|
+
end
|
652
|
+
|
653
|
+
next unless content != original_content
|
654
|
+
|
655
|
+
File.write(file, content)
|
656
|
+
fixes += 1
|
657
|
+
end
|
658
|
+
|
659
|
+
fixes
|
660
|
+
end
|
661
|
+
|
662
|
+
def log_completion_summary
|
663
|
+
duration = Time.now - @start_time
|
664
|
+
|
665
|
+
log_phase("=" * 60)
|
666
|
+
log_phase("๐ MIGRATION COMPLETED SUCCESSFULLY!")
|
667
|
+
log_phase("=" * 60)
|
668
|
+
log_phase("๐ Summary:")
|
669
|
+
log_phase(" Files migrated: #{@stats[:files_to_migrate]}")
|
670
|
+
log_phase(" Files moved: #{@stats[:files_moved]}")
|
671
|
+
log_phase(" Total changes: #{@stats[:total_changes]}")
|
672
|
+
log_phase(" Duration: #{duration.round(2)}s")
|
673
|
+
log_phase(" Phases completed: #{@phases.count { |p| p[:status] == :success }}/#{@phases.length}")
|
674
|
+
end
|
675
|
+
|
676
|
+
def handle_failure(error)
|
677
|
+
log_phase("๐ Handling migration failure...")
|
678
|
+
|
679
|
+
# Show failure summary
|
680
|
+
log_phase("=" * 60)
|
681
|
+
log_phase("โ MIGRATION FAILED")
|
682
|
+
log_phase("=" * 60)
|
683
|
+
log_phase("๐ฅ Error: #{error.message}")
|
684
|
+
|
685
|
+
return unless @phases.any?
|
686
|
+
|
687
|
+
log_phase("๐ Phase Status:")
|
688
|
+
@phases.each do |phase|
|
689
|
+
status_icon = phase[:status] == :success ? "โ
" : "โ"
|
690
|
+
log_phase(" #{status_icon} #{phase[:name]}")
|
691
|
+
end
|
692
|
+
rescue StandardError => e
|
693
|
+
log_phase("โ ๏ธ Error during failure handling: #{e.message}", :error)
|
694
|
+
@errors << "Error during failure handling: #{e.message}"
|
695
|
+
end
|
696
|
+
|
697
|
+
def restore_to_initial_state
|
698
|
+
log_phase("๐ Restoring to initial state...")
|
699
|
+
|
700
|
+
# Stash the migration script to avoid restoring it
|
701
|
+
script_name = File.basename(__FILE__)
|
702
|
+
if File.exist?(script_name)
|
703
|
+
system("cp #{script_name} #{script_name}.backup")
|
704
|
+
log_phase("๐ Backed up migration script")
|
705
|
+
end
|
706
|
+
|
707
|
+
return
|
708
|
+
# Find the initial commit from the first rollback point
|
709
|
+
if @rollback_points.any?
|
710
|
+
initial_commit = @rollback_points.first[:commit]
|
711
|
+
log_phase("๐ Rolling back to initial commit #{initial_commit[0..7]}...")
|
712
|
+
result = system("git reset --hard #{initial_commit}")
|
713
|
+
if result
|
714
|
+
log_phase("โ
Repository restored to initial state")
|
715
|
+
|
716
|
+
# Restore the migration script
|
717
|
+
if File.exist?("#{script_name}.backup")
|
718
|
+
system("mv #{script_name}.backup #{script_name}")
|
719
|
+
log_phase("๐ Restored migration script")
|
720
|
+
end
|
721
|
+
else
|
722
|
+
log_phase("โ Failed to restore to initial state", :error)
|
723
|
+
end
|
724
|
+
else
|
725
|
+
log_phase("โ ๏ธ No initial commit stored - cannot restore")
|
726
|
+
end
|
727
|
+
end
|
728
|
+
|
729
|
+
# ====================
|
730
|
+
# INLINE VALIDATION
|
731
|
+
# ====================
|
732
|
+
|
733
|
+
def apply_inline_fixes(content, file_path, context = :general)
|
734
|
+
original_content = content.dup
|
735
|
+
|
736
|
+
# Fix 1: Double Core:: patterns
|
737
|
+
content.gsub!("module Kumi::Core::Core", "module Kumi::Core")
|
738
|
+
content.gsub!("Kumi::Core::Core::", "Kumi::Core::")
|
739
|
+
content.gsub!("Core::Core::", "Core::")
|
740
|
+
|
741
|
+
# Fix 2: For files in the core directory, be more selective about what gets the Core:: prefix
|
742
|
+
if file_path.include?("/core/")
|
743
|
+
# Only apply Core:: to modules that are actually in Core
|
744
|
+
core_modules = %w[
|
745
|
+
Analyzer Compiler Types Syntax Export Input Domain RubyParser
|
746
|
+
Kumi::Registry.SchemaInstance SchemaMetadata Explain CompiledSchema
|
747
|
+
EvaluationWrapper ErrorReporter ErrorReporting VectorizationMetadata
|
748
|
+
JsonSchema AtomUnsatSolver ConstraintRelationshipSolver
|
749
|
+
]
|
750
|
+
|
751
|
+
# Fix references to other core modules
|
752
|
+
core_modules.each do |mod|
|
753
|
+
# Replace Kumi::ModuleName with Core prefix, but skip VERSION references
|
754
|
+
content.gsub!(/\bKumi::#{mod}(?!::[A-Z][a-zA-Z0-9]*::VERSION\b|::VERSION\b)/) { "Kumi::Core::#{mod}" }
|
755
|
+
end
|
756
|
+
else
|
757
|
+
# For non-core files, apply broader fixes but exclude only top-level non-core modules
|
758
|
+
content.gsub!(/(?<!module\s)(?<!class\s)(?<!struct\s)\bKumi::([A-Z][a-zA-Z0-9]*(?:::[A-Z][a-zA-Z0-9]*)*)/) do |match|
|
759
|
+
module_path = ::Regexp.last_match(1)
|
760
|
+
# Skip if already has Core:: or if it's a root-level non-core module
|
761
|
+
first_part = module_path.split("::").first
|
762
|
+
if module_path.start_with?("Core::") ||
|
763
|
+
%w[Schema CLI VERSION].include?(first_part)
|
764
|
+
match
|
765
|
+
else
|
766
|
+
"Kumi::Core::#{module_path}"
|
767
|
+
end
|
768
|
+
end
|
769
|
+
end
|
770
|
+
|
771
|
+
# Fix 3: String literal references - be selective here too
|
772
|
+
content.gsub!(/"using Kumi::([A-Z]\w+(?:::[A-Z]\w+)*)"/) do |match|
|
773
|
+
module_path = ::Regexp.last_match(1)
|
774
|
+
# Don't move top-level non-core modules
|
775
|
+
if module_path.start_with?("Core::") || %w[Schema CLI VERSION].include?(module_path.split("::").first)
|
776
|
+
"\"using Kumi::#{module_path}\""
|
777
|
+
else
|
778
|
+
"\"using Kumi::Core::#{module_path}\""
|
779
|
+
end
|
780
|
+
end
|
781
|
+
|
782
|
+
content
|
783
|
+
end
|
784
|
+
|
785
|
+
def count_line_differences(original, updated)
|
786
|
+
original_lines = original.lines
|
787
|
+
updated_lines = updated.lines
|
788
|
+
|
789
|
+
differences = 0
|
790
|
+
max_lines = [original_lines.length, updated_lines.length].max
|
791
|
+
|
792
|
+
(0...max_lines).each do |i|
|
793
|
+
orig_line = original_lines[i]&.strip
|
794
|
+
new_line = updated_lines[i]&.strip
|
795
|
+
differences += 1 if orig_line != new_line
|
796
|
+
end
|
797
|
+
|
798
|
+
differences
|
799
|
+
end
|
800
|
+
|
801
|
+
# ====================
|
802
|
+
# UTILITIES
|
803
|
+
# ====================
|
804
|
+
|
805
|
+
def run_basic_tests(context)
|
806
|
+
log_phase("๐งช Running basic tests (#{context})...")
|
807
|
+
|
808
|
+
test_script = 'require "./lib/kumi"; puts "โ
Basic load successful"'
|
809
|
+
stdout, stderr, status = Open3.capture3("ruby -e '#{test_script}'")
|
810
|
+
|
811
|
+
if status.success?
|
812
|
+
log_phase(" โ
Basic loading test passed")
|
813
|
+
else
|
814
|
+
log_phase(" โ Basic loading test FAILED", :error)
|
815
|
+
log_test_error("Basic Load Test", stderr, stdout)
|
816
|
+
record_error("Basic test failed in #{context}")
|
817
|
+
raise "Basic test failed in #{context}"
|
818
|
+
end
|
819
|
+
end
|
820
|
+
|
821
|
+
def log_test_error(test_name, stderr, stdout)
|
822
|
+
log_phase("๐จ #{test_name} Error Details:", :error)
|
823
|
+
|
824
|
+
if stderr && !stderr.empty?
|
825
|
+
# Clean and extract key error information
|
826
|
+
error_lines = stderr.lines.first(5)
|
827
|
+
error_lines.each do |line|
|
828
|
+
cleaned_line = clean_path_from_error(line.chomp)
|
829
|
+
log_phase(" #{cleaned_line}", :error)
|
830
|
+
end
|
831
|
+
end
|
832
|
+
|
833
|
+
return unless stdout && !stdout.empty? && stdout != stderr
|
834
|
+
|
835
|
+
cleaned_output = clean_path_from_error(stdout.chomp)
|
836
|
+
log_phase(" Output: #{cleaned_output}")
|
837
|
+
end
|
838
|
+
|
839
|
+
def clean_path_from_error(message)
|
840
|
+
# Remove the current working directory from paths to make them relative
|
841
|
+
current_dir = Dir.pwd
|
842
|
+
message.gsub(current_dir + "/", "")
|
843
|
+
.gsub(current_dir, ".")
|
844
|
+
end
|
845
|
+
|
846
|
+
def track_file_changes(file_path, original_content, new_content, change_type)
|
847
|
+
return 0 if original_content == new_content
|
848
|
+
|
849
|
+
@change_tracker[file_path] ||= {
|
850
|
+
total_changes: 0,
|
851
|
+
change_types: Hash.new(0),
|
852
|
+
phases: []
|
853
|
+
}
|
854
|
+
|
855
|
+
# Simple line-diff count
|
856
|
+
original_lines = original_content.lines
|
857
|
+
new_lines = new_content.lines
|
858
|
+
|
859
|
+
changes_count = 0
|
860
|
+
max_lines = [original_lines.length, new_lines.length].max
|
861
|
+
(0...max_lines).each do |i|
|
862
|
+
orig_line = original_lines[i]&.strip
|
863
|
+
new_line = new_lines[i]&.strip
|
864
|
+
changes_count += 1 if orig_line != new_line
|
865
|
+
end
|
866
|
+
|
867
|
+
@change_tracker[file_path][:total_changes] += changes_count
|
868
|
+
@change_tracker[file_path][:change_types][change_type] += changes_count
|
869
|
+
@change_tracker[file_path][:phases] << {
|
870
|
+
phase: @current_phase,
|
871
|
+
type: change_type,
|
872
|
+
count: changes_count,
|
873
|
+
timestamp: Time.now
|
874
|
+
}
|
875
|
+
|
876
|
+
changes_count
|
877
|
+
end
|
878
|
+
|
879
|
+
def flag_file(file, change_count, severity)
|
880
|
+
icon = case severity
|
881
|
+
when :warning then "โ ๏ธ"
|
882
|
+
when :critical then "๐จ"
|
883
|
+
when :suspicious then "๐ด"
|
884
|
+
end
|
885
|
+
|
886
|
+
log_phase("#{icon} #{file}: #{change_count} changes (#{severity})", severity)
|
887
|
+
end
|
888
|
+
|
889
|
+
def finalize_migration
|
890
|
+
log_phase("Finalizing migration...")
|
891
|
+
|
892
|
+
system("git add -A")
|
893
|
+
commit_msg = "Complete Kumi to Kumi::Core migration\n\n" \
|
894
|
+
"Iterative migration completed successfully:\n" \
|
895
|
+
"- #{@metadata[:files_to_migrate]} files migrated\n" \
|
896
|
+
"- All tests passing\n" \
|
897
|
+
"- Zeitwerk autoloading working correctly"
|
898
|
+
|
899
|
+
system("git commit -m '#{commit_msg}'")
|
900
|
+
|
901
|
+
final_commit = `git rev-parse HEAD`.strip
|
902
|
+
@metadata[:final_commit] = final_commit
|
903
|
+
@metadata[:final_status] = :success
|
904
|
+
|
905
|
+
log_phase("Migration committed as #{final_commit[0..7]}")
|
906
|
+
end
|
907
|
+
|
908
|
+
def record_error(message)
|
909
|
+
@errors << {
|
910
|
+
timestamp: Time.now,
|
911
|
+
message: message,
|
912
|
+
phase: @current_phase
|
913
|
+
}
|
914
|
+
end
|
915
|
+
|
916
|
+
def save_migration_metadata
|
917
|
+
@metadata[:end_time] = Time.now
|
918
|
+
@metadata[:duration] = @metadata[:end_time] - @metadata[:start_time]
|
919
|
+
@metadata[:errors] = @errors
|
920
|
+
@metadata[:change_statistics] = @change_tracker
|
921
|
+
|
922
|
+
File.write("migration_metadata_iterative.json", JSON.pretty_generate(@metadata))
|
923
|
+
log_phase("Migration metadata saved")
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
927
|
+
# Run migration if script is executed directly
|
928
|
+
if __FILE__ == $0
|
929
|
+
begin
|
930
|
+
migrator = IterativeKumiCoreMigrator.new
|
931
|
+
migrator.migrate!
|
932
|
+
|
933
|
+
# Success summary already logged by log_completion_summary
|
934
|
+
rescue StandardError => e
|
935
|
+
# Error details already logged by handle_failure
|
936
|
+
exit 1
|
937
|
+
end
|
938
|
+
end
|