tng 0.5.4 → 0.5.5

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.
data/bin/tng CHANGED
@@ -7,10 +7,13 @@ lib_path = File.expand_path("../lib", __dir__)
7
7
  $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
8
8
 
9
9
  require "tng"
10
+ require "json"
10
11
  require "tng/services/direct_generation"
11
12
  require "tng/services/extract_methods"
12
13
  require "tng/services/file_type_detector"
14
+ require "tng/services/file_discovery"
13
15
  require "tng/services/fix_orchestrator"
16
+ require "tng/services/installer"
14
17
 
15
18
  require "tty-screen"
16
19
  require "tty-option"
@@ -77,6 +80,9 @@ class CLI
77
80
  example ""
78
81
  example "Call Sites:", ""
79
82
  example " bundle exec tng --file=app/services/payment_service.rb --method=process --callsites", ""
83
+ example ""
84
+ example "Initialize configuration:", ""
85
+ example " bundle exec tng init", ""
80
86
  end
81
87
 
82
88
  option :file do
@@ -183,6 +189,11 @@ class CLI
183
189
  end
184
190
 
185
191
  def start
192
+ if ARGV.first == "init" || ARGV.first == "install"
193
+ run_init_command
194
+ return
195
+ end
196
+
186
197
  normalized_argv = preprocess_arguments(ARGV.dup)
187
198
 
188
199
  parse(normalized_argv)
@@ -192,9 +203,13 @@ class CLI
192
203
  return
193
204
  end
194
205
 
195
- rails_loaded = load_rails_environment
206
+ rails_root = find_rails_root || Dir.pwd
207
+ @rails_project = Tng::Utils.rails_project?(rails_root)
208
+ @generic_mode = !@rails_project
196
209
 
197
- initialize_config_and_clients if rails_loaded
210
+ rails_loaded = @rails_project ? load_rails_environment : false
211
+
212
+ initialize_config_and_clients if rails_loaded || @generic_mode
198
213
 
199
214
  if params[:fix]
200
215
  handle_fix_command
@@ -211,7 +226,7 @@ class CLI
211
226
  elsif params[:file]
212
227
  run_direct_generation
213
228
  else
214
- if find_rails_root && !rails_loaded && !defined?(Rails)
229
+ if @rails_project && !rails_loaded && !defined?(Rails)
215
230
  puts @pastel.decorate("#{Tng::UI::Theme.icon(:error)} Failed to load Rails environment.", Tng::UI::Theme.color(:error))
216
231
  puts @pastel.decorate("Please ensure you're running this command from a Rails application directory.", Tng::UI::Theme.color(:warning))
217
232
  puts @pastel.decorate("Try running: bundle exec tng", Tng::UI::Theme.color(:muted))
@@ -344,7 +359,46 @@ class CLI
344
359
  @go_ui.stop
345
360
  end
346
361
 
362
+ def run_init_command
363
+ rails_root = find_rails_root
364
+ project_root = rails_root || Dir.pwd
365
+ rails_project = Tng::Utils.rails_project?(project_root)
366
+ target = if rails_project
367
+ File.join(project_root, "config", "initializers", "tng.rb")
368
+ else
369
+ File.join(project_root, "tng.rb")
370
+ end
371
+
372
+ if File.exist?(target)
373
+ puts "Configuration file already exists at #{target}"
374
+ print "Do you want to overwrite it? (y/n): "
375
+ answer = STDIN.gets&.strip&.downcase
376
+ if answer == "y" || answer == "yes"
377
+ _created, path = Tng::Services::Installer.install(
378
+ project_root: project_root,
379
+ rails_project: rails_project,
380
+ force: true
381
+ )
382
+ puts "TNG configuration file updated at #{path}"
383
+ else
384
+ puts "Skipping configuration file creation."
385
+ end
386
+ else
387
+ _created, path = Tng::Services::Installer.install(
388
+ project_root: project_root,
389
+ rails_project: rails_project,
390
+ force: false
391
+ )
392
+ puts "TNG configuration file created at #{path}"
393
+ end
394
+ end
395
+
347
396
  def handle_test_generation
397
+ if @generic_mode
398
+ handle_generic_test_generation
399
+ return
400
+ end
401
+
348
402
  choice = @go_ui.show_test_type_menu
349
403
 
350
404
  case choice
@@ -360,6 +414,11 @@ class CLI
360
414
  end
361
415
 
362
416
  def handle_clone_detection
417
+ if @generic_mode
418
+ handle_generic_clone_detection
419
+ return
420
+ end
421
+
363
422
  # Pick a file type first to narrow down
364
423
  choice = @go_ui.show_test_type_menu("clones")
365
424
  return if choice == "back"
@@ -496,6 +555,11 @@ class CLI
496
555
  end
497
556
 
498
557
  def handle_audit_method
558
+ if @generic_mode
559
+ handle_generic_audit
560
+ return
561
+ end
562
+
499
563
  choice = @go_ui.show_test_type_menu("audit")
500
564
 
501
565
  case choice
@@ -563,32 +627,40 @@ class CLI
563
627
  end
564
628
 
565
629
  def run_audit_for_controller_method(controller, method_info)
566
- result = nil
630
+ audit_result = nil
567
631
  @go_ui.show_progress("Auditing #{controller[:name]}##{method_info[:name]}") do |progress|
568
632
  progress.update("Analyzing method context...", 25)
569
633
  progress.update("Running logical analysis...", 50)
570
634
  progress.update("Detecting issues and behaviours...", 75)
571
635
 
572
- result = Tng::Services::TestGenerator.new(@http_client).run_audit_for_controller_method(
636
+ audit_result = Tng::Services::TestGenerator.new(@http_client).run_audit_for_controller_method(
573
637
  controller, method_info, progress: progress
574
638
  )
575
639
 
576
640
  progress.update("Processing results...", 100)
577
641
 
578
- if result&.dig(:error)
579
- { message: result[:message] || "Audit failed", error: result[:error] }
642
+ if audit_result&.dig(:error)
643
+ { message: audit_result[:message] || "Audit failed", error: audit_result[:error] }
580
644
  else
581
645
  { message: "Audit complete!" }
582
646
  end
583
647
  end
584
648
 
585
- return @go_ui.show_auth_error(result[:message] || "Audit failed") if result&.dig(:error)
649
+ return @go_ui.show_auth_error(audit_result[:message] || "Audit failed") if audit_result&.dig(:error)
586
650
 
587
- display_audit_results(result)
651
+ display_audit_results(audit_result)
588
652
  end
589
653
 
590
654
  def display_audit_results(result)
591
- audit_data = result[:audit_results]
655
+ audit_data = result.is_a?(Hash) ? (result[:audit_results] || result["audit_results"] || result[:result] || result["result"] || result) : result
656
+ if audit_data.is_a?(String)
657
+ begin
658
+ audit_data = JSON.parse(audit_data)
659
+ rescue JSON::ParserError
660
+ audit_data = nil
661
+ end
662
+ end
663
+ return if audit_data.nil?
592
664
 
593
665
  # Send entire audit_data to go-ui (includes issues, behaviours, method_name, class_name, method_source_with_lines)
594
666
  @go_ui.show_audit_results(audit_data, "issues")
@@ -784,29 +856,29 @@ class CLI
784
856
  end
785
857
 
786
858
  def run_audit_for_model_method(model, method_info)
787
- result = nil
859
+ audit_result = nil
788
860
  @go_ui.show_progress("Auditing #{model[:name]}##{method_info[:name]}") do |progress|
789
861
  progress.update("Parsing method details...", 25)
790
862
  progress.update("Analyzing method context...", 25)
791
863
  progress.update("Running logical analysis...", 50)
792
864
  progress.update("Detecting issues and behaviours...", 75)
793
865
 
794
- result = Tng::Services::TestGenerator.new(@http_client).run_audit_for_model_method(
866
+ audit_result = Tng::Services::TestGenerator.new(@http_client).run_audit_for_model_method(
795
867
  model, method_info, progress: progress
796
868
  )
797
869
 
798
870
  progress.update("Processing results...", 100)
799
871
 
800
- if result&.dig(:error)
801
- { message: result[:message] || "Audit failed", error: result[:error] }
872
+ if audit_result&.dig(:error)
873
+ { message: audit_result[:message] || "Audit failed", error: audit_result[:error] }
802
874
  else
803
875
  { message: "Audit complete!" }
804
876
  end
805
877
  end
806
878
 
807
- return @go_ui.show_auth_error(result[:message] || "Audit failed") if result&.dig(:error)
879
+ return @go_ui.show_auth_error(audit_result[:message] || "Audit failed") if audit_result&.dig(:error)
808
880
 
809
- display_audit_results(result)
881
+ display_audit_results(audit_result)
810
882
  end
811
883
 
812
884
  def audit_service_method
@@ -860,29 +932,29 @@ class CLI
860
932
  end
861
933
 
862
934
  def run_audit_for_service_method(service, method_info)
863
- result = nil
935
+ audit_result = nil
864
936
  @go_ui.show_progress("Auditing #{service[:name]}##{method_info[:name]}") do |progress|
865
937
  progress.update("Parsing method details...", 25)
866
938
  progress.update("Analyzing method context...", 25)
867
939
  progress.update("Running logical analysis...", 50)
868
940
  progress.update("Detecting issues and behaviours...", 75)
869
941
 
870
- result = Tng::Services::TestGenerator.new(@http_client).run_audit_for_service_method(
942
+ audit_result = Tng::Services::TestGenerator.new(@http_client).run_audit_for_service_method(
871
943
  service, method_info, progress: progress
872
944
  )
873
945
 
874
946
  progress.update("Processing results...", 100)
875
947
 
876
- if result&.dig(:error)
877
- { message: result[:message] || "Audit failed", error: result[:error] }
948
+ if audit_result&.dig(:error)
949
+ { message: audit_result[:message] || "Audit failed", error: audit_result[:error] }
878
950
  else
879
951
  { message: "Audit complete!" }
880
952
  end
881
953
  end
882
954
 
883
- return @go_ui.show_auth_error(result[:message] || "Audit failed") if result&.dig(:error)
955
+ return @go_ui.show_auth_error(audit_result[:message] || "Audit failed") if audit_result&.dig(:error)
884
956
 
885
- display_audit_results(result)
957
+ display_audit_results(audit_result)
886
958
  end
887
959
 
888
960
  def audit_other_method
@@ -934,29 +1006,29 @@ class CLI
934
1006
  end
935
1007
 
936
1008
  def run_audit_for_other_method(other_file, method_info)
937
- result = nil
1009
+ audit_result = nil
938
1010
  @go_ui.show_progress("Auditing #{other_file[:name]}##{method_info[:name]}") do |progress|
939
1011
  progress.update("Parsing method details...", 25)
940
1012
  progress.update("Analyzing method context...", 25)
941
1013
  progress.update("Running logical analysis...", 50)
942
1014
  progress.update("Detecting issues and behaviours...", 75)
943
1015
 
944
- result = Tng::Services::TestGenerator.new(@http_client).run_audit_for_other_method(
1016
+ audit_result = Tng::Services::TestGenerator.new(@http_client).run_audit_for_other_method(
945
1017
  other_file, method_info, progress: progress
946
1018
  )
947
1019
 
948
1020
  progress.update("Processing results...", 100)
949
1021
 
950
- if result&.dig(:error)
951
- { message: result[:message] || "Audit failed", error: result[:error] }
1022
+ if audit_result&.dig(:error)
1023
+ { message: audit_result[:message] || "Audit failed", error: audit_result[:error] }
952
1024
  else
953
1025
  { message: "Audit complete!" }
954
1026
  end
955
1027
  end
956
1028
 
957
- return @go_ui.show_auth_error(result[:message] || "Audit failed") if result&.dig(:error)
1029
+ return @go_ui.show_auth_error(audit_result[:message] || "Audit failed") if audit_result&.dig(:error)
958
1030
 
959
- display_audit_results(result)
1031
+ display_audit_results(audit_result)
960
1032
  end
961
1033
 
962
1034
  def generate_controller_tests
@@ -1467,6 +1539,11 @@ class CLI
1467
1539
  def initialize_config_and_clients
1468
1540
  @config_initialized = true
1469
1541
 
1542
+ if @generic_mode
1543
+ config_path = File.join(Dir.pwd, "tng.rb")
1544
+ load config_path if File.exist?(config_path)
1545
+ end
1546
+
1470
1547
  unless Tng::Services::UserAppConfig.configured?
1471
1548
  missing = Tng::Services::UserAppConfig.missing_config
1472
1549
  @go_ui.show_config_missing(missing)
@@ -1493,6 +1570,11 @@ class CLI
1493
1570
  end
1494
1571
 
1495
1572
  def handle_trace_method
1573
+ if @generic_mode
1574
+ handle_generic_trace
1575
+ return
1576
+ end
1577
+
1496
1578
  choice = @go_ui.show_test_type_menu("trace")
1497
1579
 
1498
1580
  case choice
@@ -1508,6 +1590,11 @@ class CLI
1508
1590
  end
1509
1591
 
1510
1592
  def handle_impact_method
1593
+ if @generic_mode
1594
+ handle_generic_impact
1595
+ return
1596
+ end
1597
+
1511
1598
  choice = @go_ui.show_test_type_menu("impact")
1512
1599
 
1513
1600
  case choice
@@ -1523,6 +1610,11 @@ class CLI
1523
1610
  end
1524
1611
 
1525
1612
  def handle_xray_method
1613
+ if @generic_mode
1614
+ handle_generic_xray
1615
+ return
1616
+ end
1617
+
1526
1618
  choice = @go_ui.show_test_type_menu("xray")
1527
1619
 
1528
1620
  case choice
@@ -1614,16 +1706,7 @@ class CLI
1614
1706
 
1615
1707
  @go_ui.show_spinner("Tracing #{method_info[:name]}...") do
1616
1708
  begin
1617
- project_root = Dir.pwd
1618
- path = File.expand_path(file_info[:path])
1619
-
1620
- class_name = method_info[:class_name] || file_info[:name]
1621
- trace_json = Tng::Analyzer::Context.analyze_symbolic_trace(
1622
- project_root,
1623
- path,
1624
- method_info[:name],
1625
- class_name
1626
- )
1709
+ trace_json = fetch_trace_json(file_info, method_info, v2: false)
1627
1710
 
1628
1711
  f = Tempfile.new(['trace', '.json'])
1629
1712
  f.write(JSON.generate(trace_json))
@@ -1642,21 +1725,101 @@ class CLI
1642
1725
  end
1643
1726
  end
1644
1727
 
1645
- def run_impact_for_method(file_info, method_info)
1728
+ def run_trace_for_method_v2(file_info, method_info)
1646
1729
  result_path = nil
1647
1730
 
1648
- @go_ui.show_spinner("Running regression check for #{method_info[:name]}...") do
1731
+ @go_ui.show_spinner("Tracing #{method_info[:name]} (v2)...") do
1649
1732
  begin
1650
- project_root = Dir.pwd
1651
- path = File.expand_path(file_info[:path])
1733
+ trace_json = fetch_trace_json(file_info, method_info, v2: true)
1652
1734
 
1653
- class_name = method_info[:class_name] || file_info[:name]
1654
- impact_json = Tng::Analyzer::Context.analyze_impact(
1735
+ f = Tempfile.new(['trace_v2', '.json'])
1736
+ f.write(JSON.generate(trace_json))
1737
+ f.close
1738
+ result_path = f.path
1739
+
1740
+ { success: true, message: "Trace v2 generated" }
1741
+ rescue => e
1742
+ { success: false, message: e.message }
1743
+ end
1744
+ end
1745
+
1746
+ if result_path
1747
+ @go_ui.show_trace_results(result_path)
1748
+ File.unlink(result_path) if File.exist?(result_path)
1749
+ end
1750
+ end
1751
+
1752
+ def fetch_trace_json(file_info, method_info, v2:)
1753
+ project_root = Dir.pwd
1754
+ path = File.expand_path(file_info[:path])
1755
+ # For direct trace runs, avoid forcing a class name from file path.
1756
+ # In many codebases (e.g. service/agent folders), path-derived constants
1757
+ # do not match the real Ruby namespace and would hide valid methods.
1758
+ class_name = method_info[:class_name]
1759
+
1760
+ if @generic_mode
1761
+ if v2
1762
+ Tng::Analyzer::Context.analyze_symbolic_trace_ruby_v2(
1655
1763
  project_root,
1656
1764
  path,
1657
1765
  method_info[:name],
1658
1766
  class_name
1659
1767
  )
1768
+ else
1769
+ Tng::Analyzer::Context.analyze_symbolic_trace_ruby(
1770
+ project_root,
1771
+ path,
1772
+ method_info[:name],
1773
+ class_name
1774
+ )
1775
+ end
1776
+ elsif v2
1777
+ Tng::Analyzer::Context.analyze_symbolic_trace_v2(
1778
+ project_root,
1779
+ path,
1780
+ method_info[:name],
1781
+ class_name
1782
+ )
1783
+ else
1784
+ Tng::Analyzer::Context.analyze_symbolic_trace(
1785
+ project_root,
1786
+ path,
1787
+ method_info[:name],
1788
+ class_name
1789
+ )
1790
+ end
1791
+ end
1792
+
1793
+ def run_impact_for_method(file_info, method_info)
1794
+ result_path = nil
1795
+
1796
+ @go_ui.show_spinner("Running regression check for #{method_info[:name]}...") do
1797
+ begin
1798
+ project_root = Dir.pwd
1799
+ path = File.expand_path(file_info[:path])
1800
+
1801
+ class_name =
1802
+ if @generic_mode
1803
+ method_info[:class_name]
1804
+ else
1805
+ method_info[:class_name] || file_info[:name]
1806
+ end
1807
+ impact_json =
1808
+ if @generic_mode
1809
+ Tng::Analyzer::Context.analyze_impact_ruby(
1810
+ project_root,
1811
+ path,
1812
+ method_info[:name],
1813
+ class_name
1814
+ )
1815
+ else
1816
+ Tng::Analyzer::Context.analyze_impact(
1817
+ project_root,
1818
+ path,
1819
+ method_info[:name],
1820
+ class_name
1821
+ )
1822
+ end
1660
1823
 
1661
1824
  f = Tempfile.new(['impact', '.json'])
1662
1825
  f.write(JSON.generate(impact_json))
@@ -1696,9 +1859,43 @@ class CLI
1696
1859
  file_info = { path: full_path, name: namespaced_name }
1697
1860
  method_info = { name: method_name, class_name: params[:class_name] }
1698
1861
 
1862
+ if params[:json]
1863
+ puts JSON.generate(fetch_trace_json(file_info, method_info, v2: false))
1864
+ return
1865
+ end
1866
+
1699
1867
  run_trace_for_method(file_info, method_info)
1700
1868
  end
1701
1869
 
1870
+ def run_direct_trace_v2
1871
+ file_path = params[:file]
1872
+ method_name = params[:method]
1873
+
1874
+ unless method_name
1875
+ puts @pastel.decorate("Please specify a method name with --method or -m", Tng::UI::Theme.color(:error))
1876
+ return
1877
+ end
1878
+
1879
+ full_path = File.expand_path(file_path)
1880
+ unless File.exist?(full_path)
1881
+ puts @pastel.decorate("File not found: #{full_path}", Tng::UI::Theme.color(:error))
1882
+ return
1883
+ end
1884
+ return unless ensure_not_ignored(full_path)
1885
+
1886
+ relative_path = full_path.gsub("#{Dir.pwd}/", "")
1887
+ namespaced_name = relative_path.sub(/\.rb\z/, "").split("/").map(&:camelize).join("::")
1888
+ file_info = { path: full_path, name: namespaced_name }
1889
+ method_info = { name: method_name, class_name: params[:class_name] }
1890
+
1891
+ if params[:json]
1892
+ puts JSON.generate(fetch_trace_json(file_info, method_info, v2: true))
1893
+ return
1894
+ end
1895
+
1896
+ run_trace_for_method_v2(file_info, method_info)
1897
+ end
1898
+
1702
1899
  def run_direct_call_sites
1703
1900
  file_path = params[:file]
1704
1901
  method_name = params[:method]
@@ -1805,20 +2002,27 @@ class CLI
1805
2002
 
1806
2003
  @go_ui.show_spinner("Generating X-Ray for #{method_info[:name]}...") do
1807
2004
  begin
1808
- type = Tng::Services::FileTypeDetector.detect_type(file_info[:path])
1809
- file_object = Tng::Services::FileTypeDetector.build_file_object(file_info[:path], type)
1810
2005
  generator = Tng::Services::TestGenerator.new(@http_client)
1811
2006
 
1812
- xray_result = case type
1813
- when "controller"
1814
- generator.run_xray_for_controller_method(file_object, method_info)
1815
- when "model"
1816
- generator.run_xray_for_model_method(file_object, method_info)
1817
- when "service"
1818
- generator.run_xray_for_service_method(file_object, method_info)
1819
- else
1820
- generator.run_xray_for_other_method(file_object, method_info)
1821
- end
2007
+ if @generic_mode
2008
+ method_info = method_info.merge(test_type: method_info[:test_type] || "other")
2009
+ file_object = generic_file_object(file_info)
2010
+ xray_result = generator.run_xray_for_ruby_method(file_object, method_info)
2011
+ else
2012
+ type = Tng::Services::FileTypeDetector.detect_type(file_info[:path])
2013
+ file_object = Tng::Services::FileTypeDetector.build_file_object(file_info[:path], type)
2014
+
2015
+ xray_result = case type
2016
+ when "controller"
2017
+ generator.run_xray_for_controller_method(file_object, method_info)
2018
+ when "model"
2019
+ generator.run_xray_for_model_method(file_object, method_info)
2020
+ when "service"
2021
+ generator.run_xray_for_service_method(file_object, method_info)
2022
+ else
2023
+ generator.run_xray_for_other_method(file_object, method_info)
2024
+ end
2025
+ end
1822
2026
 
1823
2027
  if xray_result&.dig(:error)
1824
2028
  { success: false, message: xray_result[:message] || "X-Ray failed" }
@@ -1996,11 +2200,196 @@ class CLI
1996
2200
  next unless m_choice
1997
2201
  m_choice = m_choice.merge(class_name: selected_class || choice[:name])
1998
2202
 
1999
- yield(choice, m_choice)
2203
+ yield(choice, m_choice)
2204
+ end
2205
+ end
2206
+
2207
+ def generic_file_object(file_info)
2208
+ name = file_info[:name].sub(/\.rb\z/, "")
2209
+ {
2210
+ name: name.split("/").map(&:camelize).join("::"),
2211
+ path: file_info[:path],
2212
+ type: "other"
2213
+ }
2214
+ end
2215
+
2216
+ def list_generic_files
2217
+ root = Dir.pwd
2218
+ files = Tng::Services::FileDiscovery.list_ruby_files(root)
2219
+ files.map do |path|
2220
+ {
2221
+ name: path.gsub(%r{\A#{Regexp.escape(root)}/?}, ""),
2222
+ path: path
2223
+ }
2224
+ end
2225
+ end
2226
+
2227
+ def select_generic_file(action_name)
2228
+ files = nil
2229
+ @go_ui.show_spinner("Analyzing files...") do
2230
+ files = list_generic_files
2231
+ { success: true, message: "Found #{files.length} files" }
2232
+ end
2233
+
2234
+ return @go_ui.show_no_items("ruby files") if files.empty?
2235
+
2236
+ items = files.map { |f| { name: f[:name], path: f[:path] } }
2237
+ selected = @go_ui.show_list_view("Select File to #{action_name}", items)
2238
+ return if selected == "back"
2239
+
2240
+ files.find { |f| f[:name] == selected }
2241
+ end
2242
+
2243
+ def select_generic_method(file_info, action_name)
2244
+ file_object = generic_file_object(file_info)
2245
+ methods = extract_other_methods(file_object)
2246
+ if methods.empty?
2247
+ @go_ui.show_no_items("methods in #{file_info[:name]}")
2248
+ return
2249
+ end
2250
+
2251
+ items = methods.map { |m| { name: m[:name], path: file_info[:name] } }
2252
+ selected = @go_ui.show_list_view("Select Method to #{action_name}", items)
2253
+ return if selected == "back"
2254
+
2255
+ methods.find { |m| m[:name] == selected }
2256
+ end
2257
+
2258
+ def select_generic_method_and_type(action_name)
2259
+ file_info = select_generic_file(action_name)
2260
+ return if file_info.nil?
2261
+
2262
+ method_info = select_generic_method(file_info, action_name)
2263
+ return if method_info.nil?
2264
+
2265
+ test_type = @go_ui.show_ruby_test_type_menu
2266
+ return if test_type == "back"
2267
+
2268
+ [file_info, method_info.merge(test_type: test_type)]
2269
+ end
2270
+
2271
+ def handle_generic_test_generation
2272
+ selection = select_generic_method_and_type("Generate Tests")
2273
+ return if selection.nil?
2274
+
2275
+ file_info, method_info = selection
2276
+ file_object = generic_file_object(file_info)
2277
+
2278
+ @go_ui.display_info(@pastel.bright_white("🎯 Generating test for #{file_info[:name]}##{method_info[:name]}..."))
2279
+
2280
+ result = @go_ui.show_progress("Processing...") do |progress|
2281
+ Tng::Services::TestGenerator.new(@http_client).run_for_ruby_method(
2282
+ file_object,
2283
+ method_info,
2284
+ progress: progress
2285
+ )
2286
+ end
2287
+
2288
+ if result&.dig(:error)
2289
+ @go_ui.display_error(@pastel.red("❌ Test generation failed: #{result[:message]}"))
2290
+ return
2291
+ end
2292
+
2293
+ show_post_generation_menu(result) if result && result[:file_path]
2294
+ end
2295
+
2296
+ def handle_generic_audit
2297
+ selection = select_generic_method_and_type("Audit")
2298
+ return if selection.nil?
2299
+
2300
+ file_info, method_info = selection
2301
+ file_object = generic_file_object(file_info)
2302
+
2303
+ audit_result = nil
2304
+ result = @go_ui.show_progress("Auditing #{file_info[:name]}##{method_info[:name]}") do |progress|
2305
+ progress.update("Analyzing method context...", 25)
2306
+ progress.update("Running logical analysis...", 50)
2307
+ progress.update("Detecting issues and behaviours...", 75)
2308
+
2309
+ audit_result = Tng::Services::TestGenerator.new(@http_client).run_audit_for_ruby_method(
2310
+ file_object,
2311
+ method_info,
2312
+ progress: progress
2313
+ )
2314
+
2315
+ progress.update("Processing results...", 100)
2316
+ audit_result&.dig(:error) ? { message: audit_result[:message] || "Audit failed", error: audit_result[:error] } : { message: "Audit complete!" }
2317
+ end
2318
+
2319
+ return @go_ui.show_auth_error(audit_result[:message] || "Audit failed") if audit_result&.dig(:error)
2320
+
2321
+ display_audit_results(audit_result || result)
2322
+ end
2323
+
2324
+ def handle_generic_xray
2325
+ selection = select_generic_method_and_type("X-Ray")
2326
+ return if selection.nil?
2327
+
2328
+ file_info, method_info = selection
2329
+ file_object = generic_file_object(file_info)
2330
+
2331
+ xray_result = nil
2332
+ @go_ui.show_spinner("Generating X-Ray for #{method_info[:name]}...") do
2333
+ xray_result = Tng::Services::TestGenerator.new(@http_client).run_xray_for_ruby_method(
2334
+ file_object,
2335
+ method_info
2336
+ )
2337
+
2338
+ if xray_result&.dig(:error)
2339
+ { success: false, message: xray_result[:message] || "X-Ray failed" }
2340
+ else
2341
+ { success: true, message: "X-Ray generated" }
2000
2342
  end
2343
+ end
2344
+
2345
+ return @go_ui.display_error(@pastel.red("❌ X-Ray failed: #{xray_result[:message]}")) if xray_result&.dig(:error)
2346
+
2347
+ @go_ui.show_xray_results(xray_result, method_info) if xray_result
2348
+ end
2349
+
2350
+ def handle_generic_trace
2351
+ file_info = select_generic_file("Trace")
2352
+ return if file_info.nil?
2353
+
2354
+ method_info = select_generic_method(file_info, "Trace")
2355
+ return if method_info.nil?
2356
+
2357
+ run_trace_for_method(file_info, method_info)
2358
+ end
2359
+
2360
+ def handle_generic_impact
2361
+ file_info = select_generic_file("Regression Check")
2362
+ return if file_info.nil?
2363
+
2364
+ method_info = select_generic_method(file_info, "Regression Check")
2365
+ return if method_info.nil?
2366
+
2367
+ run_impact_for_method(file_info, method_info)
2368
+ end
2369
+
2370
+ def handle_generic_clone_detection
2371
+ file_info = select_generic_file("Check Duplicates")
2372
+ return if file_info.nil?
2373
+
2374
+ level = @go_ui.show_clone_menu
2375
+ return if level == "back"
2376
+
2377
+ run_clone_detection_for_file(file_info, level)
2378
+ end
2379
+
2380
+ def handle_generic_dead_code
2381
+ file_info = select_generic_file("Analyze Dead Code")
2382
+ return if file_info.nil?
2383
+
2384
+ run_dead_code_detection_interactive(file_info[:path])
2001
2385
  end
2002
2386
 
2003
2387
  def handle_dead_code_detection_menu
2388
+ if @generic_mode
2389
+ handle_generic_dead_code
2390
+ return
2391
+ end
2392
+
2004
2393
  # Pick a file type first to narrow down
2005
2394
  choice = @go_ui.show_test_type_menu("deadcode")
2006
2395
  return if choice == "back"