tng 0.5.0 → 0.5.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c8aa338f5dbdebc9d44b422799ac8d45863aa4542a37bb1a7e430d3919009a3
4
- data.tar.gz: 1dd202876c75519dea0e2bd7726d11de98fd884b7fda1a2a391dab666d9cd5d6
3
+ metadata.gz: d34efac07c952da91c1d7247d8a3ee215bc9978af6cfcb12ca54d7b4977f599c
4
+ data.tar.gz: 6818c82d0d8d0694eb3f9d347478514574a34ed2d5b5e2dc2139e8de312d74b3
5
5
  SHA512:
6
- metadata.gz: 5d703fa9fb959d57ae13401efea2eb445311d81a49c701f5fd10761dc82b9bcb170d84235575712cf32b00f5d836c93572e4e535e78a2be5f71f076877b63d4e
7
- data.tar.gz: 311dc4309cf7670e65cb8ba235d6733195f0ea427f32331c91c04f300b3a69c7533e5797f134b64ea78b029bc08beeff804a57dcd16bb03001600f45681e6ceb
6
+ metadata.gz: 57f305b07faa097c6ff48b96cbca21b2cde388bc372d134d48ac8fbbc03dc808af60fdb1cd03482527812ccf52cf9b6f0ef0ecac00b4f33c3b6c7080ccc69618
7
+ data.tar.gz: 9529a0f62366e87fd7b6e44c120f53f1019037e9683c3271af184bae8015d4dbae211365b0dcf3b12ed81228840370173118256ffb28f3a8cf2125f8c9d91417
data/README.md CHANGED
@@ -144,6 +144,25 @@ bundle exec tng -f order.rb -m calculate_total -t
144
144
  - Method call chains
145
145
  - Mermaid diagram generation
146
146
 
147
+ #### Impact Audit
148
+
149
+ Compare a method against its committed (`HEAD`) version and surface blast radius:
150
+
151
+ ```bash
152
+ # Run impact audit for a method
153
+ bundle exec tng --file app/services/payment_service.rb --method process_payment --impact
154
+
155
+ # JSON output for automation/CI
156
+ bundle exec tng --file app/services/payment_service.rb --method process_payment --impact --json
157
+ ```
158
+
159
+ **Impact Signals:**
160
+ - Added/removed required parameters
161
+ - Parameter kind/requirement changes
162
+ - Return type changes
163
+ - Variable type drift (inferred)
164
+ - Impacted call sites (files/lines)
165
+
147
166
  #### Clone Detection
148
167
 
149
168
  Find duplicate code with 3 levels of detection:
@@ -196,6 +215,7 @@ Get machine-readable output for CI/CD integration:
196
215
  bundle exec tng --file app/models/user.rb --clones --json
197
216
  bundle exec tng --file app/services/payment.rb --deadcode --json
198
217
  bundle exec tng --file app/controllers/api_controller.rb --trace --json
218
+ bundle exec tng --file app/services/payment.rb --method process_payment --impact --json
199
219
  ```
200
220
 
201
221
  ## Advanced Features
@@ -298,6 +318,7 @@ Path 3: !discount_code.present? → calculate_tax → total
298
318
  | `--method` | `-m` | Method name to analyze |
299
319
  | `--audit` | `-a` | Run audit mode |
300
320
  | `--trace` | `-t` | Run symbolic trace mode |
321
+ | `--impact` | | Run impact audit mode |
301
322
  | `--clones` | `-c` | Run clone detection |
302
323
  | `--deadcode` | `-d` | Run dead code detection |
303
324
  | `--level` | `-l` | Clone detection level (1, 2, 3, or all) |
@@ -316,6 +337,9 @@ bundle exec tng -f payment_service.rb -m process -a
316
337
  # Symbolic trace
317
338
  bundle exec tng -f order.rb -m calculate_total -t
318
339
 
340
+ # Impact audit
341
+ bundle exec tng -f payment_service.rb -m process --impact
342
+
319
343
  # Clone detection (all levels)
320
344
  bundle exec tng -f pricing_service.rb -c
321
345
 
data/bin/tng CHANGED
@@ -56,6 +56,9 @@ class CLI
56
56
  example "X-Ray Visualization:", ""
57
57
  example " bundle exec tng app/services/payment_processor.rb process_payment --xray", ""
58
58
  example " bundle exec tng --file=users_controller.rb --method=create --xray --json", ""
59
+ example ""
60
+ example "Impact Audit:", ""
61
+ example " bundle exec tng --file=app/services/payment_service.rb --method=process --impact", ""
59
62
  end
60
63
 
61
64
  option :file do
@@ -82,6 +85,11 @@ class CLI
82
85
  desc "Run in dead code detection mode (unreachable code, unused variables)"
83
86
  end
84
87
 
88
+ flag :all do
89
+ long "--all"
90
+ desc "Run dead code detection across the entire repo (respects .gitignore)"
91
+ end
92
+
85
93
  option :level do
86
94
  short "-l"
87
95
  long "--level=LEVEL"
@@ -99,6 +107,11 @@ class CLI
99
107
  desc "Generate and visualize a symbolic trace"
100
108
  end
101
109
 
110
+ flag :impact do
111
+ long "--impact"
112
+ desc "Run impact audit (compare against Git HEAD)"
113
+ end
114
+
102
115
  flag :xray do
103
116
  long "--xray"
104
117
  desc "Generate X-Ray visualization (Mermaid flowchart)"
@@ -157,6 +170,8 @@ class CLI
157
170
 
158
171
  if params[:fix]
159
172
  handle_fix_command
173
+ elsif params[:impact] && params[:file]
174
+ run_direct_impact
160
175
  elsif params[:trace] && params[:file]
161
176
  run_direct_trace
162
177
  elsif params[:deadcode] && params[:file]
@@ -201,6 +216,8 @@ class CLI
201
216
  normalized << "--audit"
202
217
  when /^(?:--)?(trace)$/
203
218
  normalized << "--trace"
219
+ when /^(?:--)?(impact)$/
220
+ normalized << "--impact"
204
221
  when /^(?:--)?(xray)$/
205
222
  normalized << "--xray"
206
223
  when /^(?:--)?(json|j)$/
@@ -211,6 +228,8 @@ class CLI
211
228
  normalized << "--clones"
212
229
  when /^(?:--)?(deadcode|d)$/
213
230
  normalized << "--deadcode"
231
+ when /^(?:--)?(all)$/
232
+ normalized << "--all"
214
233
  when /^(?:--)?(level|l)=(.+)$/
215
234
  normalized << "--level=#{::Regexp.last_match(2)}"
216
235
  when /^(help|h)=(.+)$/
@@ -270,6 +289,8 @@ class CLI
270
289
  handle_audit_method
271
290
  when "trace"
272
291
  handle_trace_method
292
+ when "impact"
293
+ handle_impact_method
273
294
  when "xray"
274
295
  handle_xray_method
275
296
  when "clones"
@@ -542,6 +563,11 @@ class CLI
542
563
 
543
564
  def run_dead_code_detection
544
565
  file_path = params[:file]
566
+ if params[:all] || file_path.nil?
567
+ run_dead_code_detection_repo
568
+ return
569
+ end
570
+
545
571
  unless File.exist?(file_path)
546
572
  puts @pastel.red("Error: File '#{file_path}' not found.")
547
573
  return
@@ -564,6 +590,114 @@ class CLI
564
590
  end
565
591
  end
566
592
 
593
+ def run_dead_code_detection_repo
594
+ project_root = Dir.pwd
595
+ files = deadcode_repo_files(project_root)
596
+
597
+ if files.empty?
598
+ puts @pastel.yellow("No files found for dead code analysis.")
599
+ return
600
+ end
601
+
602
+ aggregate = []
603
+ total = files.length
604
+ start = Time.now
605
+
606
+ def render_deadcode_bar(current, total_count)
607
+ width = 20
608
+ ratio = total_count == 0 ? 1.0 : current.to_f / total_count.to_f
609
+ filled = (ratio * width).round
610
+ bar = "#" * filled + "-" * (width - filled)
611
+ percent = (ratio * 100).round
612
+ "Scanning project... [#{bar}] #{percent}%"
613
+ end
614
+
615
+ if params[:json]
616
+ # Suppress progress output in JSON mode
617
+ else
618
+ print render_deadcode_bar(0, total)
619
+ $stdout.flush
620
+ end
621
+
622
+ files.each_with_index do |file, idx|
623
+ rel = file.sub(%r{\A#{Regexp.escape(project_root)}/?}, "")
624
+ begin
625
+ result_json = Tng.detect_dead_code(file, true)
626
+ parsed = JSON.parse(result_json)
627
+ issues = parsed["dead_code"] || []
628
+ issues.each do |issue|
629
+ aggregate << {
630
+ "type" => issue["type"] || "unknown",
631
+ "line" => issue["line"] || 0,
632
+ "message" => issue["message"] || "",
633
+ "code" => issue["code"] || "",
634
+ "file" => rel
635
+ }
636
+ end
637
+ rescue => e
638
+ aggregate << {
639
+ "type" => "analysis_error",
640
+ "line" => 0,
641
+ "message" => "Failed to analyze: #{e.message}",
642
+ "code" => "",
643
+ "file" => rel
644
+ }
645
+ end
646
+
647
+ if !params[:json] && (idx % 10 == 0 || idx == total - 1)
648
+ print "\r" + render_deadcode_bar(idx + 1, total)
649
+ $stdout.flush
650
+ end
651
+ end
652
+
653
+ print "\n" unless params[:json]
654
+
655
+ result_payload = {
656
+ "file" => project_root,
657
+ "dead_code" => aggregate.map { |i|
658
+ {
659
+ "type" => i["type"],
660
+ "line" => i["line"],
661
+ "message" => "[#{i["file"]}] #{i["message"]}",
662
+ "code" => i["code"],
663
+ "file" => i["file"]
664
+ }
665
+ }
666
+ }
667
+
668
+ if params[:json]
669
+ @go_ui.show_dead_code_results(project_root, result_payload.to_json)
670
+ else
671
+ if aggregate.empty?
672
+ puts "No dead code detected."
673
+ elapsed = Time.now - start
674
+ puts format("Summary: Found 0 dead items in %.2fs.", elapsed)
675
+ return
676
+ end
677
+
678
+ puts "Found dead code:"
679
+ aggregate.each do |issue|
680
+ snippet = issue["code"].to_s.strip
681
+ snippet = snippet.empty? ? "" : " (#{snippet})"
682
+ puts "- #{issue["file"]}:#{issue["line"]}#{snippet}"
683
+ end
684
+ elapsed = Time.now - start
685
+ puts format("Summary: Found %d dead items in %.2fs.", aggregate.length, elapsed)
686
+ puts "[Tip] Run 'tng audit' to fix logic bugs in the remaining code."
687
+ end
688
+ end
689
+
690
+ def deadcode_repo_files(root)
691
+ begin
692
+ files_json = Tng.list_deadcode_files(root)
693
+ files = JSON.parse(files_json)
694
+ return files.select { |f| File.file?(f) }
695
+ rescue => e
696
+ puts @pastel.red("Error listing repo files: #{e.message}")
697
+ []
698
+ end
699
+ end
700
+
567
701
  # Placeholder methods for other component types
568
702
  def audit_model_method
569
703
  models = nil
@@ -1318,6 +1452,21 @@ class CLI
1318
1452
  end
1319
1453
  end
1320
1454
 
1455
+ def handle_impact_method
1456
+ choice = @go_ui.show_test_type_menu("impact")
1457
+
1458
+ case choice
1459
+ when "controller"
1460
+ impact_controller_method
1461
+ when "model"
1462
+ impact_model_method
1463
+ when "service"
1464
+ impact_service_method
1465
+ when "other"
1466
+ impact_other_method
1467
+ end
1468
+ end
1469
+
1321
1470
  def handle_xray_method
1322
1471
  choice = @go_ui.show_test_type_menu("xray")
1323
1472
 
@@ -1357,6 +1506,30 @@ class CLI
1357
1506
  end
1358
1507
  end
1359
1508
 
1509
+ def impact_controller_method
1510
+ select_controller_and_method("Impact Audit") do |controller, method_info|
1511
+ run_impact_for_method(controller, method_info)
1512
+ end
1513
+ end
1514
+
1515
+ def impact_model_method
1516
+ select_model_and_method("Impact Audit") do |model, method_info|
1517
+ run_impact_for_method(model, method_info)
1518
+ end
1519
+ end
1520
+
1521
+ def impact_service_method
1522
+ select_service_and_method("Impact Audit") do |service, method_info|
1523
+ run_impact_for_method(service, method_info)
1524
+ end
1525
+ end
1526
+
1527
+ def impact_other_method
1528
+ select_other_and_method("Impact Audit") do |file, method_info|
1529
+ run_impact_for_method(file, method_info)
1530
+ end
1531
+ end
1532
+
1360
1533
  def xray_controller_method
1361
1534
  select_controller_and_method("X-Ray") do |controller, method_info|
1362
1535
  run_xray_for_method(controller, method_info)
@@ -1413,6 +1586,39 @@ class CLI
1413
1586
  end
1414
1587
  end
1415
1588
 
1589
+ def run_impact_for_method(file_info, method_info)
1590
+ result_path = nil
1591
+
1592
+ @go_ui.show_spinner("Running impact audit for #{method_info[:name]}...") do
1593
+ begin
1594
+ project_root = Dir.pwd
1595
+ path = File.expand_path(file_info[:path])
1596
+
1597
+ class_name = file_info[:name]
1598
+ impact_json = Tng::Analyzer::Context.analyze_impact(
1599
+ project_root,
1600
+ path,
1601
+ method_info[:name],
1602
+ class_name
1603
+ )
1604
+
1605
+ f = Tempfile.new(['impact', '.json'])
1606
+ f.write(JSON.generate(impact_json))
1607
+ f.close
1608
+ result_path = f.path
1609
+
1610
+ { success: true, message: "Impact audit completed" }
1611
+ rescue => e
1612
+ { success: false, message: e.message }
1613
+ end
1614
+ end
1615
+
1616
+ if result_path
1617
+ @go_ui.show_impact_results(result_path)
1618
+ File.unlink(result_path) if File.exist?(result_path)
1619
+ end
1620
+ end
1621
+
1416
1622
  def run_direct_trace
1417
1623
  file_path = params[:file]
1418
1624
  method_name = params[:method]
@@ -1428,12 +1634,37 @@ class CLI
1428
1634
  return
1429
1635
  end
1430
1636
 
1431
- file_info = { path: full_path, name: File.basename(full_path) }
1637
+ relative_path = full_path.gsub("#{Dir.pwd}/", "")
1638
+ namespaced_name = relative_path.sub(/\.rb\z/, "").split("/").map(&:camelize).join("::")
1639
+ file_info = { path: full_path, name: namespaced_name }
1432
1640
  method_info = { name: method_name }
1433
1641
 
1434
1642
  run_trace_for_method(file_info, method_info)
1435
1643
  end
1436
1644
 
1645
+ def run_direct_impact
1646
+ file_path = params[:file]
1647
+ method_name = params[:method]
1648
+
1649
+ unless method_name
1650
+ puts @pastel.decorate("Please specify a method name with --method or -m", Tng::UI::Theme.color(:error))
1651
+ return
1652
+ end
1653
+
1654
+ full_path = File.expand_path(file_path)
1655
+ unless File.exist?(full_path)
1656
+ puts @pastel.decorate("File not found: #{full_path}", Tng::UI::Theme.color(:error))
1657
+ return
1658
+ end
1659
+
1660
+ relative_path = full_path.gsub("#{Dir.pwd}/", "")
1661
+ namespaced_name = relative_path.sub(/\.rb\z/, "").split("/").map(&:camelize).join("::")
1662
+ file_info = { path: full_path, name: namespaced_name }
1663
+ method_info = { name: method_name }
1664
+
1665
+ run_impact_for_method(file_info, method_info)
1666
+ end
1667
+
1437
1668
  def run_direct_xray
1438
1669
  return unless check_configuration
1439
1670
 
@@ -1453,7 +1684,9 @@ class CLI
1453
1684
  return
1454
1685
  end
1455
1686
 
1456
- file_info = { path: full_path, name: File.basename(full_path) }
1687
+ relative_path = full_path.gsub("#{Dir.pwd}/", "")
1688
+ namespaced_name = relative_path.sub(/\.rb\z/, "").split("/").map(&:camelize).join("::")
1689
+ file_info = { path: full_path, name: namespaced_name }
1457
1690
  method_info = { name: method_name }
1458
1691
 
1459
1692
  run_xray_for_method(file_info, method_info)
@@ -1634,15 +1867,20 @@ class CLI
1634
1867
  choice = files.find { |f| f[:name] == selected }
1635
1868
  next unless choice
1636
1869
 
1637
- # For dead code, we don't need to select a method, just the file.
1638
- # But this helper yields (file_choice, method_choice).
1639
- # We might need a simpler selector for dead code if it's file-based only.
1640
- # Re-using method selector for now if we want per-method, but dead code is usually per-file.
1870
+ methods = extract_other_methods(choice)
1871
+ if methods.empty?
1872
+ @go_ui.show_no_items("methods in #{choice[:name]}")
1873
+ next
1874
+ end
1641
1875
 
1642
- # Actually, dead code analysis is whole-file.
1643
- # So we should probably just return the file choice.
1876
+ m_items = methods.map { |m| { name: m[:name], path: choice[:name] } }
1877
+ m_selected = @go_ui.show_list_view("Select Method to #{action_name}", m_items)
1878
+ next if m_selected == "back"
1644
1879
 
1645
- # Let's implement specific helpers for dead code that just pick files.
1880
+ m_choice = methods.find { |m| m[:name] == m_selected }
1881
+ next unless m_choice
1882
+
1883
+ yield(choice, m_choice)
1646
1884
  end
1647
1885
  end
1648
1886
 
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
data/binaries/tng.bundle CHANGED
Binary file
@@ -256,6 +256,14 @@ module Tng
256
256
  puts "Trace results error: #{e.message}"
257
257
  end
258
258
 
259
+ # Show impact audit results
260
+ # @param impact_file_path [String] Path to JSON impact file
261
+ def show_impact_results(impact_file_path)
262
+ system(@binary_path, "ruby-impact-results", "--file", impact_file_path)
263
+ rescue StandardError => e
264
+ puts "Impact results error: #{e.message}"
265
+ end
266
+
259
267
  # Show X-Ray visualization results
260
268
  # @param xray_result [Hash] X-Ray result with mermaid_code
261
269
  # @param method_info [Hash] Optional method info for context
@@ -316,7 +324,8 @@ module Tng
316
324
  type: issue["type"],
317
325
  line: issue["line"],
318
326
  message: issue["message"],
319
- code: issue["code"]
327
+ code: issue["code"],
328
+ file: issue["file"]
320
329
  }
321
330
  end
322
331
  }
@@ -406,6 +415,7 @@ module Tng
406
415
  { cmd: "bundle exec tng --file=FILE --method=METHOD", desc: "Direct test generation" },
407
416
  { cmd: "bundle exec tng --file=FILE --method=METHOD --audit", desc: "Direct audit mode" },
408
417
  { cmd: "bundle exec tng --file=FILE --method=METHOD --trace", desc: "Symbolic trace mode" },
418
+ { cmd: "bundle exec tng --file=FILE --method=METHOD --impact", desc: "Impact audit mode" },
409
419
  { cmd: "bundle exec tng --file=FILE --clones", desc: "Check for code duplicates" },
410
420
  { cmd: "bundle exec tng --file=FILE --deadcode", desc: "Run dead code detection" },
411
421
  { cmd: "bundle exec tng --file=FILE --method=METHOD --xray", desc: "X-Ray visualization" }
@@ -416,6 +426,7 @@ module Tng
416
426
  "Per-method test generation",
417
427
  "Code audit for issues & behaviors",
418
428
  "Symbolic execution traces",
429
+ "Impact audit against Git HEAD",
419
430
  "Duplicate code detection (Clones)",
420
431
  "Dead code detection (Rust-powered)",
421
432
  "X-Ray Logic Flow Visualization",
@@ -427,6 +438,7 @@ module Tng
427
438
  { flag: "--method=NAME", desc: "Method name to generate test for" },
428
439
  { flag: "--audit", desc: "Run audit mode instead of test generation" },
429
440
  { flag: "--trace", desc: "Run symbolic trace mode" },
441
+ { flag: "--impact", desc: "Run impact audit mode" },
430
442
  { flag: "--clones", desc: "Run clone detection mode" },
431
443
  { flag: "--level=1|2|3|all", desc: "Set clone detection level (default: all)" },
432
444
  { flag: "--deadcode", desc: "Run dead code detection" },
@@ -101,6 +101,11 @@ module Tng
101
101
  emit_event("result", trace_json)
102
102
  end
103
103
 
104
+ def show_impact_results(impact_file_path)
105
+ impact_json = JSON.parse(File.read(impact_file_path))
106
+ emit_event("result", impact_json)
107
+ end
108
+
104
109
  def show_dead_code_results(file_path, results_json)
105
110
  parsed = JSON.parse(results_json)
106
111
  emit_event("dead_code", parsed)
data/lib/tng/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tng
4
- VERSION = "0.5.0"
4
+ VERSION = "0.5.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tng
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - ralucab
@@ -222,7 +222,7 @@ post_install_message: "┌ TNG ────────────────
222
222
  \ │\n│ • bundle exec
223
223
  tng --help - Show help information │\n│ │\n│
224
224
  \ \U0001F4A1 Generate tests for individual methods with precision │\n└────────────────────────────────────────────────────────────
225
- v0.5.0 ┘\n"
225
+ v0.5.1 ┘\n"
226
226
  rdoc_options: []
227
227
  require_paths:
228
228
  - lib