tryouts 3.5.0 → 3.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: 7b692cc3a63ac86e060b52e77c246305be0f209dbf06b31b1b08deefbc06434a
4
- data.tar.gz: 156e86aa4bde377aa2158f3a059983a6fe5f58796f71545c9895a9b57668b126
3
+ metadata.gz: ab0eb9c341b10dd5e505a3f62cc33a19a393cb294aecd756809b19c2484784f1
4
+ data.tar.gz: cddfd1a827b0ef37a9e69224124f2f696bbd8c2f098edbfce79b563b3017fbcd
5
5
  SHA512:
6
- metadata.gz: d289a9b5ccd6694bd63fe4ca2a0c8aaf610d702e74380bf4975ae78ffaf377d0f82a9ec481dbc51105de9f3b07681c32eaa8a1c3566a1eebd0a861796eaff1e3
7
- data.tar.gz: a0db3ebf76b5a291e6ea9d81f1f6e268a95320d28040a10f2225171b221f04bb2ed525bae3f79785d50f4269eb7c9be1dec242012bbca0a6d4588ac9e7fdd362
6
+ metadata.gz: 066b7697b04b3501f01a0994e9d10a66c5a94d593bdfa5cb1b618bb1264f228425bd7d8b4d98cf9a555a5e23bbe002f137c943d5195ad4ce972de30f03dfac9c
7
+ data.tar.gz: 82390327e8da8e35d0c9eaa4557800b09bba76e065704f9d704acab45ab24dd62e97edc7aa54d8104ee50cab31a9dab321c75449386f70243674c8f2d838716a
data/exe/try CHANGED
@@ -38,10 +38,16 @@ require_relative '../lib/tryouts'
38
38
  lib_glob = File.join(Dir.pwd, '{lib,../lib,.}')
39
39
  Tryouts.update_load_path(lib_glob) if Tryouts.respond_to?(:update_load_path)
40
40
 
41
+ # Capture original command for agent mode before ARGV gets modified
42
+ original_command = [$0] + ARGV
43
+
41
44
  # Parse args and run CLI
42
45
  begin
43
46
  files, options = Tryouts::CLI.parse_args(ARGV)
44
47
 
48
+ # Add original command to options for agent formatter
49
+ options[:original_command] = original_command
50
+
45
51
  # Expand files if directories are given, preserving line specs
46
52
  expanded_files = []
47
53
  files.each do |file_or_dir|
@@ -4,13 +4,42 @@ require_relative 'token_budget'
4
4
 
5
5
  class Tryouts
6
6
  class CLI
7
- # Agent-optimized formatter designed for LLM context management
8
- # Features:
9
- # - Token budget awareness
10
- # - Structured YAML-like output
11
- # - No redundant file paths
12
- # - Smart truncation
13
- # - Hierarchical organization
7
+ # TOPA (Test Output Protocol for AI) Formatter
8
+ #
9
+ # Language-agnostic test output format designed for LLM context management.
10
+ # This formatter implements the TOPA v1.0 specification for structured,
11
+ # token-efficient test result communication.
12
+ #
13
+ # TOPA Features:
14
+ # - Language-agnostic field naming (snake_case, hierarchical)
15
+ # - Standardized execution context (runtime, environment, VCS)
16
+ # - Token budget awareness with smart truncation
17
+ # - Cross-platform compatibility (CI/CD, package managers)
18
+ # - Structured failure reporting with diffs
19
+ # - Protocol versioning for forward compatibility
20
+ #
21
+ # Field Specifications:
22
+ # - command: Exact command executed
23
+ # - process_id: System process identifier
24
+ # - runtime: Language, version, platform info
25
+ # - package_manager: Dependency management system
26
+ # - version_control: VCS branch/commit info
27
+ # - environment: Normalized env vars (ci_system, app_env, etc.)
28
+ # - test_framework: Framework name, isolation mode, parser
29
+ # - execution_flags: Runtime flags in normalized form
30
+ # - protocol: TOPA version and configuration
31
+ # - project: Auto-detected project type
32
+ # - test_discovery: File pattern matching rules
33
+ #
34
+ # Compatible with: Ruby/RSpec/Minitest, Python/pytest/unittest,
35
+ # JavaScript/Jest/Mocha, Java/JUnit, Go, C#/NUnit, and more.
36
+ #
37
+ # Language Adaptation Examples:
38
+ # Python: runtime.language=python, package_manager.name=pip/poetry/conda
39
+ # Node.js: runtime.language=javascript, package_manager.name=npm/yarn/pnpm
40
+ # Java: runtime.language=java, package_manager.name=maven/gradle
41
+ # Go: runtime.language=go, package_manager.name=go_modules
42
+ # C#: runtime.language=csharp, package_manager.name=nuget/dotnet
14
43
  class AgentFormatter
15
44
  include FormatterInterface
16
45
 
@@ -506,9 +535,41 @@ class Tryouts
506
535
 
507
536
  def render_execution_context
508
537
  context_lines = []
509
- context_lines << "EXECUTION DETAILS:"
538
+ context_lines << "EXECUTION_CONTEXT:"
510
539
 
511
- # Framework and context mode
540
+ # Command that was executed
541
+ if @options[:original_command]
542
+ command_str = @options[:original_command].join(' ')
543
+ context_lines << " command: #{command_str}"
544
+ end
545
+
546
+ # Compact system info on one line when possible
547
+ context_lines << " pid: #{Process.pid} | pwd: #{Dir.pwd}"
548
+
549
+ # Runtime - compact format
550
+ platform = RUBY_PLATFORM.gsub(/darwin\d+/, 'darwin') # Simplify darwin25 -> darwin
551
+ context_lines << " runtime: ruby #{RUBY_VERSION} (#{platform})"
552
+
553
+ # Package manager - only if present, compact format
554
+ if defined?(Bundler)
555
+ context_lines << " package_manager: bundler #{Bundler::VERSION}"
556
+ end
557
+
558
+ # Version control - compact single line with timeout protection
559
+ git_info = safe_git_info
560
+ if git_info[:branch] && git_info[:commit] && !git_info[:branch].empty? && !git_info[:commit].empty?
561
+ context_lines << " vcs: git #{git_info[:branch]}@#{git_info[:commit]}"
562
+ end
563
+
564
+ # Environment - only non-defaults
565
+ env_vars = build_environment_context
566
+ if env_vars.any?
567
+ # Compact key=value format
568
+ env_str = env_vars.map { |k, v| "#{k}=#{v}" }.join(', ')
569
+ context_lines << " environment: #{env_str}"
570
+ end
571
+
572
+ # Test framework - compact critical info only
512
573
  framework = @options[:framework] || :direct
513
574
  shared_context = if @options.key?(:shared_context)
514
575
  @options[:shared_context]
@@ -522,26 +583,24 @@ class Tryouts
522
583
  end
523
584
  end
524
585
 
525
- context_lines << " Framework: #{framework}"
526
- context_lines << " Context mode: #{shared_context ? 'shared (variables persist across test cases)' : 'fresh (each test case isolated)'}"
527
-
528
- # Parser type
529
- parser = @options[:parser] || :enhanced
530
- context_lines << " Parser: #{parser}"
586
+ isolation = shared_context ? 'shared' : 'isolated'
587
+ context_lines << " test_framework: #{framework} (#{isolation})"
531
588
 
532
- # Other relevant flags
533
- flags = []
534
- flags << "verbose" if @options[:verbose]
535
- flags << "fails-only" if @options[:fails_only]
536
- flags << "debug" if @options[:debug]
537
- flags << "stack-traces" if @options[:stack_traces]
538
- flags << "parallel(#{@options[:parallel_threads] || 'auto'})" if @options[:parallel]
539
- flags << "line-spec" if @options[:line_spec]
589
+ # Execution flags - only if non-standard
590
+ flags = build_execution_flags
591
+ if flags.any?
592
+ context_lines << " flags: #{flags.join(', ')}"
593
+ end
540
594
 
541
- context_lines << " Flags: #{flags.any? ? flags.join(', ') : 'none'}" if flags.any?
595
+ # TOPA protocol - compact
596
+ context_lines << " protocol: TOPA v1.0 | focus: #{@focus_mode} | limit: #{@budget.limit}"
542
597
 
543
- # Agent-specific settings
544
- context_lines << " Agent mode: focus=#{@focus_mode}, limit=#{@budget.limit} tokens"
598
+ # File count being tested
599
+ if @collected_files && @collected_files.any?
600
+ context_lines << " files_under_test: #{@collected_files.size}"
601
+ elsif @total_stats[:files] && @total_stats[:files] > 0
602
+ context_lines << " files_under_test: #{@total_stats[:files]}"
603
+ end
545
604
 
546
605
  # Add syntax errors if any (these prevent test execution)
547
606
  if @syntax_errors.any?
@@ -571,6 +630,101 @@ class Tryouts
571
630
 
572
631
  context_lines.join("\n")
573
632
  end
633
+
634
+ # Build environment context with language-agnostic keys
635
+ def build_environment_context
636
+ env_vars = {}
637
+
638
+ # CI/CD detection - prioritize most specific
639
+ if ENV['GITHUB_ACTIONS']
640
+ env_vars['CI'] = 'github'
641
+ elsif ENV['GITLAB_CI']
642
+ env_vars['CI'] = 'gitlab'
643
+ elsif ENV['JENKINS_URL']
644
+ env_vars['CI'] = 'jenkins'
645
+ elsif ENV['CI']
646
+ env_vars['CI'] = 'true'
647
+ end
648
+
649
+ # Runtime environment - only if not default
650
+ if ENV['RAILS_ENV'] && ENV['RAILS_ENV'] != 'development'
651
+ env_vars['ENV'] = ENV['RAILS_ENV']
652
+ elsif ENV['RACK_ENV'] && ENV['RACK_ENV'] != 'development'
653
+ env_vars['ENV'] = ENV['RACK_ENV']
654
+ elsif ENV['NODE_ENV'] && ENV['NODE_ENV'] != 'development'
655
+ env_vars['ENV'] = ENV['NODE_ENV']
656
+ end
657
+
658
+ # Coverage - simplified
659
+ env_vars['COV'] = '1' if ENV['COVERAGE'] || ENV['SIMPLECOV']
660
+
661
+ # Test seed for reproducibility
662
+ env_vars['SEED'] = ENV['SEED'] if ENV['SEED']
663
+
664
+ env_vars
665
+ end
666
+
667
+ # Build execution flags in language-agnostic format
668
+ def build_execution_flags
669
+ flags = []
670
+ flags << "verbose" if @options[:verbose]
671
+ flags << "fails-only" if @options[:fails_only]
672
+ flags << "debug" if @options[:debug]
673
+ flags << "traces" if @options[:stack_traces] && !@options[:debug] # debug implies traces
674
+ flags << "parallel" if @options[:parallel]
675
+ flags << "line-spec" if @options[:line_spec]
676
+ flags << "strict" if @options[:strict]
677
+ flags << "quiet" if @options[:quiet]
678
+ flags
679
+ end
680
+
681
+ # Get test discovery patterns in language-agnostic format
682
+ def get_test_discovery_patterns
683
+ patterns = []
684
+
685
+ # Ruby/Tryouts patterns
686
+ patterns.concat([
687
+ "**/*_try.rb",
688
+ "**/*.try.rb",
689
+ "try/**/*.rb",
690
+ "tryouts/**/*.rb"
691
+ ])
692
+
693
+ # TOPA-compatible patterns for other languages:
694
+ # Python: ["**/*_test.py", "**/test_*.py", "tests/**/*.py"]
695
+ # JavaScript: ["**/*.test.js", "**/*.spec.js", "__tests__/**/*.js"]
696
+ # Java: ["**/*Test.java", "**/Test*.java", "src/test/**/*.java"]
697
+ # Go: ["**/*_test.go"]
698
+ # C#: ["**/*Test.cs", "**/*Tests.cs"]
699
+ # PHP: ["**/*Test.php", "tests/**/*.php"]
700
+ # Rust: ["**/*_test.rs", "tests/**/*.rs"]
701
+
702
+ patterns
703
+ end
704
+
705
+ private
706
+
707
+ # Safely get git information with timeout protection
708
+ def safe_git_info
709
+ # Check if we're in a git repository
710
+ return {} unless File.directory?('.git') || system('git rev-parse --git-dir >/dev/null 2>&1')
711
+
712
+ require 'timeout'
713
+
714
+ Timeout.timeout(2) do
715
+ branch = `git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
716
+ commit = `git rev-parse --short HEAD 2>/dev/null`.strip
717
+
718
+ # Validate output to prevent injection
719
+ branch = nil unless branch =~ /\A[\w\-\/\.]+\z/
720
+ commit = nil unless commit =~ /\A[a-f0-9]+\z/i
721
+
722
+ { branch: branch, commit: commit }
723
+ end
724
+ rescue Timeout::Error, StandardError
725
+ # Return empty hash on any error (timeout, permission, etc.)
726
+ {}
727
+ end
574
728
  end
575
729
  end
576
730
  end
@@ -40,6 +40,21 @@ class Tryouts
40
40
  # Try to evaluate in test context first, then fallback to global context for constants
41
41
  begin
42
42
  expected_class = eval_expectation_content(@expectation.content, expectation_result)
43
+
44
+ # If the evaluated result is not a class/module (e.g., shadowed constant),
45
+ # fall back to global context resolution
46
+ unless expected_class.is_a?(Class) || expected_class.is_a?(Module)
47
+ # Attempt to get the constant from global context
48
+ # This handles cases where a local variable/method shadows a class constant
49
+ constant_name = @expectation.content.strip
50
+ # Ensure the constant name is valid before attempting const_get
51
+ if constant_name =~ /\A[A-Z][\w:]*\z/
52
+ expected_class = Object.const_get(constant_name)
53
+ else
54
+ # If it's not a valid constant name, the evaluation failed
55
+ raise TypeError, "Expected a Class or Module, got #{expected_class.class}"
56
+ end
57
+ end
43
58
  rescue NameError => e
44
59
  # If we can't find the constant in test context, try global context
45
60
  # This is common for exception classes like ArgumentError, StandardError, etc.
@@ -1,5 +1,5 @@
1
1
  # lib/tryouts/version.rb
2
2
 
3
3
  class Tryouts
4
- VERSION = '3.5.0'
4
+ VERSION = '3.5.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tryouts
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.0
4
+ version: 3.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum