tryouts 3.5.0 → 3.5.2

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: 958254585920c850e17a18e13699040fad97ec8610f3c2141b32899016dc4194
4
+ data.tar.gz: 3666350beb13d17cf24bf184350fd88746cebbf7b7efc109c3e5af7f8d5c6c3b
5
5
  SHA512:
6
- metadata.gz: d289a9b5ccd6694bd63fe4ca2a0c8aaf610d702e74380bf4975ae78ffaf377d0f82a9ec481dbc51105de9f3b07681c32eaa8a1c3566a1eebd0a861796eaff1e3
7
- data.tar.gz: a0db3ebf76b5a291e6ea9d81f1f6e268a95320d28040a10f2225171b221f04bb2ed525bae3f79785d50f4269eb7c9be1dec242012bbca0a6d4588ac9e7fdd362
6
+ metadata.gz: 1a1091a28256b97c210f8a0a970db3a78d715e1b195e95a5a844e8f32b7d65f7e6d5cb49209baaf2c061b02500ad6bf2d71968cbcb76c16206108eabfa8039f3
7
+ data.tar.gz: 9613b25213146c5da6dbd5153fa101e8139e205f06fb5bcddb8fa172a82fc76dffff56f79d69609c4764623ac9bf0682a0ba8ef4e76fd49517920a9b3e1141af
data/README.md CHANGED
@@ -132,18 +132,15 @@ try --agent --agent-limit 1000 # limit output to 1000 tokens
132
132
 
133
133
  #### Why Not Pipe Test Output Directly to AI?
134
134
 
135
- Raw test output creates several problems when working with AI assistants:
135
+ I mean, you could. If that already works well, you could probably still benefit from an agent that is able to focus on the critical information for the task. And the extra context window space.
136
136
 
137
- - **Token bloat**: Verbose formatting wastes 60-80% of your context window on styling
138
- - **Signal vs noise**: Important failures get buried in passing test details and framework boilerplate
139
- - **Inconsistent parsing**: AI struggles with varying output formats across different test runs
140
- - **Context overflow**: Large test suites exceed AI token limits, truncating critical information
137
+ Raw test output creates problems when working with AI assistants: high token usage with inconsistent parsing across different runs, where the same logical failure might be interpreted differently, making it difficult to reliably produce and analyze results consistently.
141
138
 
142
- #### TOPA: A Better Approach
139
+ #### TOPAZ: A Better Approach
143
140
 
144
- Tryouts' `--agent` mode inspired the development of **TOPA (Test Output Protocol for AI)** - a standardized format optimized for AI analysis. The [tpane](https://github.com/delano/tpane) tool implements this protocol, transforming any test framework's output into structured, token-efficient formats.
141
+ Tryouts' `--agent` mode inspired the development of **TOPAZ (Test Output Protocol for AI Zealots)** - a standardized format optimized for AI analysis. The [tpane](https://github.com/delano/tpane) tool implements this protocol, transforming any test framework's output into structured, token-efficient formats.
145
142
 
146
- Instead of overwhelming AI with raw output, TOPA provides clean semantic data focusing on what actually needs attention - failures, errors, and actionable context.
143
+ Instead of overwhelming AI with raw output, TOPAZ provides clean semantic data focusing on what actually needs attention - failures, errors, and actionable context.
147
144
 
148
145
  ### Exit Codes
149
146
 
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
+ # TOPAZ (Test Output Protocol for AI Zealots) Formatter
8
+ #
9
+ # Language-agnostic test output format designed for LLM context management.
10
+ # This formatter implements the TOPAZ v1.0 specification for structured,
11
+ # token-efficient test result communication.
12
+ #
13
+ # TOPAZ 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: TOPAZ 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
 
@@ -20,7 +49,7 @@ class Tryouts
20
49
  @focus_mode = options[:agent_focus] || :failures
21
50
  @collected_files = []
22
51
  @current_file_data = nil
23
- @total_stats = { files: 0, tests: 0, failures: 0, errors: 0, elapsed: 0 }
52
+ @total_stats = { files: 0, tests: 0, failures: 0, errors: 0, elapsed_time: 0 }
24
53
  @output_rendered = false
25
54
  @options = options # Store all options for execution context display
26
55
  @all_warnings = [] # Store warnings globally for execution details
@@ -91,7 +120,7 @@ class Tryouts
91
120
  # Always update global totals
92
121
  @total_stats[:failures] += failed_count
93
122
  @total_stats[:errors] += error_count
94
- @total_stats[:elapsed] += elapsed_time if elapsed_time
123
+ @total_stats[:elapsed_time] += elapsed_time if elapsed_time
95
124
 
96
125
  # Update per-file data - file_result is called AFTER file_end, so data is in @collected_files
97
126
  relative_file_path = relative_path(file_path)
@@ -146,7 +175,7 @@ class Tryouts
146
175
  error_count: @collected_files.sum { |f| f[:errors].size },
147
176
  successful_files: @collected_files.size - @collected_files.count { |f| f[:failures].any? || f[:errors].any? },
148
177
  total_files: @collected_files.size,
149
- elapsed_time: @total_stats[:elapsed]
178
+ elapsed_time: @total_stats[:elapsed_time]
150
179
  ) unless @output_rendered
151
180
  end
152
181
 
@@ -159,7 +188,7 @@ class Tryouts
159
188
  errors: error_count,
160
189
  successful_files: successful_files,
161
190
  total_files: total_files,
162
- elapsed: elapsed_time
191
+ elapsed_time: elapsed_time,
163
192
  )
164
193
 
165
194
  # Now render all collected data
@@ -249,6 +278,12 @@ class Tryouts
249
278
  def render_summary_only
250
279
  output = []
251
280
 
281
+ time_str = if @total_stats[:elapsed_time] < 2.0
282
+ " (#{(@total_stats[:elapsed_time] * 1000).round}ms)"
283
+ else
284
+ " (#{@total_stats[:elapsed_time].round(2)}s)"
285
+ end
286
+
252
287
  # Add execution context header for agent clarity
253
288
  output << render_execution_context
254
289
  output << ""
@@ -264,13 +299,13 @@ class Tryouts
264
299
  details = []
265
300
  details << "#{failed_count} failed" if failed_count > 0
266
301
  details << "#{error_count} errors" if error_count > 0
267
- status_parts << "FAIL: #{issues_count}/#{@total_stats[:tests]} tests (#{details.join(', ')}, #{passed_count} passed)"
302
+ status_parts << "FAIL: #{issues_count}/#{@total_stats[:tests]} tests (#{details.join(', ')}, #{passed_count} passed#{time_str})"
268
303
  else
269
304
  # Agent doesn't need output in the positive case (i.e. for passing
270
305
  # tests). It just fills out the context window.
271
306
  end
272
307
 
273
- status_parts << "(#{format_time(@total_stats[:elapsed])})" if @total_stats[:elapsed]
308
+ status_parts << "(#{format_time(@total_stats[:elapsed_time])})" if @total_stats[:elapsed_time]
274
309
 
275
310
  output << status_parts.join(" ")
276
311
 
@@ -302,6 +337,12 @@ class Tryouts
302
337
  # Only show errors (exceptions), skip assertion failures
303
338
  critical_files = @collected_files.select { |f| f[:errors].any? }
304
339
 
340
+ time_str = if @total_stats[:elapsed_time] < 2.0
341
+ " (#{(@total_stats[:elapsed_time] * 1000).round}ms)"
342
+ else
343
+ " (#{@total_stats[:elapsed_time].round(2)}s)"
344
+ end
345
+
305
346
  output = []
306
347
 
307
348
  # Add execution context header for agent clarity
@@ -314,7 +355,7 @@ class Tryouts
314
355
  return
315
356
  end
316
357
 
317
- output << "CRITICAL: #{critical_files.size} file#{'s' if critical_files.size != 1} with errors"
358
+ output << "CRITICAL: #{critical_files.size} file#{'s' if critical_files.size != 1} with errors#{time_str}"
318
359
  output << ""
319
360
 
320
361
  critical_files.each do |file_data|
@@ -344,6 +385,12 @@ class Tryouts
344
385
  def render_full_structured
345
386
  output = []
346
387
 
388
+ time_str = if @total_stats[:elapsed_time] < 2.0
389
+ " (#{(@total_stats[:elapsed_time] * 1000).round}ms)"
390
+ else
391
+ " (#{@total_stats[:elapsed_time].round(2)}s)"
392
+ end
393
+
347
394
  # Add execution context header for agent clarity
348
395
  output << render_execution_context
349
396
  output << ""
@@ -379,7 +426,7 @@ class Tryouts
379
426
  summary = "Summary: \n"
380
427
  summary += "#{passed_count} testcases passed, #{failed_count} failed"
381
428
  summary += ", #{error_count} errors" if error_count > 0
382
- summary += " in #{@total_stats[:files]} files"
429
+ summary += " in #{@total_stats[:files]} files#{time_str}"
383
430
 
384
431
  output << summary
385
432
 
@@ -506,9 +553,41 @@ class Tryouts
506
553
 
507
554
  def render_execution_context
508
555
  context_lines = []
509
- context_lines << "EXECUTION DETAILS:"
556
+ context_lines << "EXECUTION_CONTEXT:"
557
+
558
+ # Command that was executed
559
+ if @options[:original_command]
560
+ command_str = @options[:original_command].join(' ')
561
+ context_lines << " command: #{command_str}"
562
+ end
510
563
 
511
- # Framework and context mode
564
+ # Compact system info on one line when possible
565
+ context_lines << " pid: #{Process.pid} | pwd: #{Dir.pwd}"
566
+
567
+ # Runtime - compact format
568
+ platform = RUBY_PLATFORM.gsub(/darwin\d+/, 'darwin') # Simplify darwin25 -> darwin
569
+ context_lines << " runtime: ruby #{RUBY_VERSION} (#{platform}); tryouts #{Tryouts::VERSION}"
570
+
571
+ # Package manager - only if present, compact format
572
+ if defined?(Bundler)
573
+ context_lines << " package_manager: bundler #{Bundler::VERSION}"
574
+ end
575
+
576
+ # Version control - compact single line with timeout protection
577
+ git_info = safe_git_info
578
+ if git_info[:branch] && git_info[:commit] && !git_info[:branch].empty? && !git_info[:commit].empty?
579
+ context_lines << " vcs: git #{git_info[:branch]}@#{git_info[:commit]}"
580
+ end
581
+
582
+ # Environment - only non-defaults
583
+ env_vars = build_environment_context
584
+ if env_vars.any?
585
+ # Compact key=value format
586
+ env_str = env_vars.map { |k, v| "#{k}=#{v}" }.join(', ')
587
+ context_lines << " environment: #{env_str}"
588
+ end
589
+
590
+ # Test framework - compact critical info only
512
591
  framework = @options[:framework] || :direct
513
592
  shared_context = if @options.key?(:shared_context)
514
593
  @options[:shared_context]
@@ -522,26 +601,24 @@ class Tryouts
522
601
  end
523
602
  end
524
603
 
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}"
604
+ isolation = shared_context ? 'shared' : 'isolated'
605
+ context_lines << " test_framework: #{framework} (#{isolation})"
531
606
 
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]
607
+ # Execution flags - only if non-standard
608
+ flags = build_execution_flags
609
+ if flags.any?
610
+ context_lines << " flags: #{flags.join(', ')}"
611
+ end
540
612
 
541
- context_lines << " Flags: #{flags.any? ? flags.join(', ') : 'none'}" if flags.any?
613
+ # TOPAZ protocol - compact
614
+ context_lines << " protocol: TOPAZ v0.3 | focus: #{@focus_mode} | limit: #{@budget.limit}"
542
615
 
543
- # Agent-specific settings
544
- context_lines << " Agent mode: focus=#{@focus_mode}, limit=#{@budget.limit} tokens"
616
+ # File count being tested
617
+ if @collected_files && @collected_files.any?
618
+ context_lines << " files_under_test: #{@collected_files.size}"
619
+ elsif @total_stats[:files] && @total_stats[:files] > 0
620
+ context_lines << " files_under_test: #{@total_stats[:files]}"
621
+ end
545
622
 
546
623
  # Add syntax errors if any (these prevent test execution)
547
624
  if @syntax_errors.any?
@@ -571,6 +648,101 @@ class Tryouts
571
648
 
572
649
  context_lines.join("\n")
573
650
  end
651
+
652
+ # Build environment context with language-agnostic keys
653
+ def build_environment_context
654
+ env_vars = {}
655
+
656
+ # CI/CD detection - prioritize most specific
657
+ if ENV['GITHUB_ACTIONS']
658
+ env_vars['CI'] = 'github'
659
+ elsif ENV['GITLAB_CI']
660
+ env_vars['CI'] = 'gitlab'
661
+ elsif ENV['JENKINS_URL']
662
+ env_vars['CI'] = 'jenkins'
663
+ elsif ENV['CI']
664
+ env_vars['CI'] = 'true'
665
+ end
666
+
667
+ # Runtime environment - only if not default
668
+ if ENV['RAILS_ENV'] && ENV['RAILS_ENV'] != 'development'
669
+ env_vars['ENV'] = ENV['RAILS_ENV']
670
+ elsif ENV['RACK_ENV'] && ENV['RACK_ENV'] != 'development'
671
+ env_vars['ENV'] = ENV['RACK_ENV']
672
+ elsif ENV['NODE_ENV'] && ENV['NODE_ENV'] != 'development'
673
+ env_vars['ENV'] = ENV['NODE_ENV']
674
+ end
675
+
676
+ # Coverage - simplified
677
+ env_vars['COV'] = '1' if ENV['COVERAGE'] || ENV['SIMPLECOV']
678
+
679
+ # Test seed for reproducibility
680
+ env_vars['SEED'] = ENV['SEED'] if ENV['SEED']
681
+
682
+ env_vars
683
+ end
684
+
685
+ # Build execution flags in language-agnostic format
686
+ def build_execution_flags
687
+ flags = []
688
+ flags << "verbose" if @options[:verbose]
689
+ flags << "fails-only" if @options[:fails_only]
690
+ flags << "debug" if @options[:debug]
691
+ flags << "traces" if @options[:stack_traces] && !@options[:debug] # debug implies traces
692
+ flags << "parallel" if @options[:parallel]
693
+ flags << "line-spec" if @options[:line_spec]
694
+ flags << "strict" if @options[:strict]
695
+ flags << "quiet" if @options[:quiet]
696
+ flags
697
+ end
698
+
699
+ # Get test discovery patterns in language-agnostic format
700
+ def get_test_discovery_patterns
701
+ patterns = []
702
+
703
+ # Ruby/Tryouts patterns
704
+ patterns.concat([
705
+ "**/*_try.rb",
706
+ "**/*.try.rb",
707
+ "try/**/*.rb",
708
+ "tryouts/**/*.rb"
709
+ ])
710
+
711
+ # TOPA-compatible patterns for other languages:
712
+ # Python: ["**/*_test.py", "**/test_*.py", "tests/**/*.py"]
713
+ # JavaScript: ["**/*.test.js", "**/*.spec.js", "__tests__/**/*.js"]
714
+ # Java: ["**/*Test.java", "**/Test*.java", "src/test/**/*.java"]
715
+ # Go: ["**/*_test.go"]
716
+ # C#: ["**/*Test.cs", "**/*Tests.cs"]
717
+ # PHP: ["**/*Test.php", "tests/**/*.php"]
718
+ # Rust: ["**/*_test.rs", "tests/**/*.rs"]
719
+
720
+ patterns
721
+ end
722
+
723
+ private
724
+
725
+ # Safely get git information with timeout protection
726
+ def safe_git_info
727
+ # Check if we're in a git repository
728
+ return {} unless File.directory?('.git') || system('git rev-parse --git-dir >/dev/null 2>&1')
729
+
730
+ require 'timeout'
731
+
732
+ Timeout.timeout(2) do
733
+ branch = `git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
734
+ commit = `git rev-parse --short HEAD 2>/dev/null`.strip
735
+
736
+ # Validate output to prevent injection
737
+ branch = nil unless branch =~ /\A[\w\-\/\.]+\z/
738
+ commit = nil unless commit =~ /\A[a-f0-9]+\z/i
739
+
740
+ { branch: branch, commit: commit }
741
+ end
742
+ rescue Timeout::Error, StandardError
743
+ # Return empty hash on any error (timeout, permission, etc.)
744
+ {}
745
+ end
574
746
  end
575
747
  end
576
748
  end
@@ -10,20 +10,20 @@ class Tryouts
10
10
  Minitest: Fresh context (each test isolated)
11
11
 
12
12
  Examples:
13
- try test_try.rb # Tryouts test runner with shared context
14
- try --rspec test_try.rb # RSpec with fresh context
15
- try --direct --shared-context test_try.rb # Explicit shared context
16
- try --generate-rspec test_try.rb # Output RSpec code only
17
- try --inspect test_try.rb # Inspect file structure and validation
18
- try --agent test_try.rb # Agent-optimized structured output
19
- try --agent --agent-limit 10000 tests/ # Agent mode with 10K token limit
13
+ try test_try.rb # Tryouts test runner with shared context
14
+ try --rspec test_try.rb # RSpec with fresh context
15
+ try --direct --shared-context test_try.rb # Explicit shared context
16
+ try --generate-rspec test_try.rb # Output RSpec code only
17
+ try --inspect test_try.rb # Inspect file structure and validation
18
+ try --agent test_try.rb # Agent-optimized structured output
19
+ try --agent --agent-limit 10000 tests/ # Agent mode with 10K token limit
20
20
 
21
21
  Agent Output Modes:
22
- --agent # Structured, token-efficient output
23
- --agent-focus summary # Show counts and problem files only
24
- --agent-focus first-failure # Show first failure per file
25
- --agent-focus critical # Show errors/exceptions only
26
- --agent-limit 1000 # Limit output to 1000 tokens
22
+ --agent # Structured, token-efficient output
23
+ --agent-focus summary # Show counts and problem files only
24
+ --agent-focus first-failure # Show first failure per file
25
+ --agent-focus critical # Show errors/exceptions only
26
+ --agent-limit 1000 # Limit output to 1000 tokens
27
27
 
28
28
  File Naming & Organization:
29
29
  Files must end with '_try.rb' or '.try.rb' (e.g., auth_service_try.rb, user_model.try.rb)
@@ -37,7 +37,7 @@ class Tryouts
37
37
  #=> true # this is the expected result
38
38
 
39
39
  File Structure (3 sections):
40
- # Setup section (optional) - runs once before all tests
40
+ # Setup section (optional) - code before first testcase runs once before all tests
41
41
  @shared_var = "available to all test cases"
42
42
 
43
43
  ## TEST: Feature description
@@ -45,9 +45,9 @@ class Tryouts
45
45
  result = some_operation()
46
46
  #=> expected_value
47
47
 
48
- # Teardown section (optional) - runs once after all tests
48
+ # Teardown section (optional) - code after last testcase runs once after all tests
49
49
 
50
- Context Guidelines:
50
+ Execution Context:
51
51
  Shared Context (default): Instance variables persist across test cases
52
52
  - Use for: Integration testing, stateful scenarios, realistic workflows
53
53
  - Caution: Test order matters, state accumulates
@@ -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,6 +1,6 @@
1
1
  # lib/tryouts/file_processor.rb
2
2
 
3
- require_relative 'parsers/prism_parser'
3
+ require_relative 'parsers/legacy_parser'
4
4
  require_relative 'parsers/enhanced_parser'
5
5
  require_relative 'test_executor'
6
6
  require_relative 'cli/modes/inspect'
@@ -84,7 +84,7 @@ class Tryouts
84
84
  when :enhanced
85
85
  EnhancedParser.new(file, options)
86
86
  when :prism
87
- PrismParser.new(file, options)
87
+ LegacyParser.new(file, options)
88
88
  end
89
89
  end
90
90
 
@@ -22,5 +22,15 @@ class Tryouts
22
22
  suggestion: "Use explicit '## Description' to clarify test structure"
23
23
  )
24
24
  end
25
+
26
+ def self.malformed_expectation(line_number:, syntax:, context:)
27
+ new(
28
+ type: :malformed_expectation,
29
+ message: "Malformed expectation syntax '#=#{syntax}>' at line #{line_number}",
30
+ line_number: line_number,
31
+ context: context,
32
+ suggestion: "Use valid expectation syntax like #=>, #==>, #=:>, #=!>, etc."
33
+ )
34
+ end
25
35
  end
26
36
  end