spec_forge 0.4.0 → 0.6.0

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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +4 -0
  3. data/CHANGELOG.md +145 -1
  4. data/README.md +49 -638
  5. data/flake.lock +3 -3
  6. data/flake.nix +8 -2
  7. data/lib/spec_forge/attribute/chainable.rb +208 -20
  8. data/lib/spec_forge/attribute/factory.rb +141 -12
  9. data/lib/spec_forge/attribute/faker.rb +64 -15
  10. data/lib/spec_forge/attribute/global.rb +96 -0
  11. data/lib/spec_forge/attribute/literal.rb +15 -2
  12. data/lib/spec_forge/attribute/matcher.rb +188 -13
  13. data/lib/spec_forge/attribute/parameterized.rb +45 -20
  14. data/lib/spec_forge/attribute/regex.rb +55 -5
  15. data/lib/spec_forge/attribute/resolvable.rb +48 -5
  16. data/lib/spec_forge/attribute/resolvable_array.rb +62 -4
  17. data/lib/spec_forge/attribute/resolvable_hash.rb +62 -4
  18. data/lib/spec_forge/attribute/store.rb +65 -0
  19. data/lib/spec_forge/attribute/transform.rb +33 -5
  20. data/lib/spec_forge/attribute/variable.rb +37 -6
  21. data/lib/spec_forge/attribute.rb +168 -66
  22. data/lib/spec_forge/backtrace_formatter.rb +26 -3
  23. data/lib/spec_forge/callbacks.rb +79 -0
  24. data/lib/spec_forge/cli/actions.rb +27 -0
  25. data/lib/spec_forge/cli/command.rb +78 -24
  26. data/lib/spec_forge/cli/init.rb +11 -1
  27. data/lib/spec_forge/cli/new.rb +54 -3
  28. data/lib/spec_forge/cli/run.rb +20 -0
  29. data/lib/spec_forge/cli.rb +16 -5
  30. data/lib/spec_forge/configuration.rb +94 -25
  31. data/lib/spec_forge/context/callbacks.rb +91 -0
  32. data/lib/spec_forge/context/global.rb +72 -0
  33. data/lib/spec_forge/context/store.rb +148 -0
  34. data/lib/spec_forge/context/variables.rb +91 -0
  35. data/lib/spec_forge/context.rb +36 -0
  36. data/lib/spec_forge/core_ext/rspec.rb +24 -4
  37. data/lib/spec_forge/error.rb +267 -113
  38. data/lib/spec_forge/factory.rb +33 -14
  39. data/lib/spec_forge/filter.rb +87 -0
  40. data/lib/spec_forge/forge.rb +170 -0
  41. data/lib/spec_forge/http/backend.rb +99 -29
  42. data/lib/spec_forge/http/client.rb +23 -13
  43. data/lib/spec_forge/http/request.rb +74 -62
  44. data/lib/spec_forge/http/verb.rb +79 -0
  45. data/lib/spec_forge/http.rb +105 -0
  46. data/lib/spec_forge/loader.rb +254 -0
  47. data/lib/spec_forge/matchers.rb +130 -0
  48. data/lib/spec_forge/normalizer/configuration.rb +24 -11
  49. data/lib/spec_forge/normalizer/constraint.rb +22 -9
  50. data/lib/spec_forge/normalizer/expectation.rb +31 -12
  51. data/lib/spec_forge/normalizer/factory.rb +24 -11
  52. data/lib/spec_forge/normalizer/factory_reference.rb +32 -13
  53. data/lib/spec_forge/normalizer/global_context.rb +88 -0
  54. data/lib/spec_forge/normalizer/spec.rb +39 -16
  55. data/lib/spec_forge/normalizer.rb +255 -41
  56. data/lib/spec_forge/runner/callbacks.rb +246 -0
  57. data/lib/spec_forge/runner/debug_proxy.rb +213 -0
  58. data/lib/spec_forge/runner/listener.rb +54 -0
  59. data/lib/spec_forge/runner/metadata.rb +58 -0
  60. data/lib/spec_forge/runner/state.rb +99 -0
  61. data/lib/spec_forge/runner.rb +133 -119
  62. data/lib/spec_forge/spec/expectation/constraint.rb +95 -20
  63. data/lib/spec_forge/spec/expectation.rb +43 -51
  64. data/lib/spec_forge/spec.rb +83 -96
  65. data/lib/spec_forge/type.rb +36 -4
  66. data/lib/spec_forge/version.rb +4 -1
  67. data/lib/spec_forge.rb +161 -76
  68. metadata +20 -5
  69. data/spec_forge/factories/user.yml +0 -4
  70. data/spec_forge/forge_helper.rb +0 -37
  71. data/spec_forge/specs/users.yml +0 -65
@@ -1,46 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SpecForge
4
- class Configuration < Struct.new(:base_url, :headers, :query, :factories, :specs, :on_debug)
5
- ############################################################################
6
-
4
+ #
5
+ # Configuration container for SpecForge settings
6
+ # Defines default values and validation for all configuration options
7
+ #
8
+ class Configuration < Struct.new(:base_url, :headers, :query, :factories, :on_debug)
9
+ #
10
+ # Manages factory configuration settings
11
+ # Controls auto-discovery behavior and custom factory paths
12
+ #
13
+ # @example
14
+ # config.factories.auto_discover = false
15
+ # config.factories.paths += ["lib/factories"]
16
+ #
7
17
  class Factories < Struct.new(:auto_discover, :paths)
18
+ #
19
+ # Creates reader methods that return boolean values
20
+ # Allows for checking configuration with predicate methods
21
+ #
8
22
  attr_predicate :auto_discover, :paths
9
23
 
24
+ #
25
+ # Initializes a new Factories configuration
26
+ # Sets default values for auto-discovery and paths
27
+ #
28
+ # @param auto_discover [Boolean] Whether to auto-discover factories (default: true)
29
+ # @param paths [Array<String>] Additional paths to look for factories (default: [])
30
+ #
31
+ # @return [Factories] A new factories configuration instance
32
+ #
10
33
  def initialize(auto_discover: true, paths: []) = super
11
34
  end
12
35
 
13
- ############################################################################
14
-
15
- def self.overlay_options(source, overlay)
16
- source.deep_merge(overlay) do |key, source_value, overlay_value|
17
- # If overlay has a populated value, use it
18
- if overlay_value.present? || overlay_value == false
19
- overlay_value
20
- # If source is nil and overlay exists (but wasn't "present"), use overlay
21
- elsif source_value.nil? && !overlay_value.nil?
22
- overlay_value
23
- # Otherwise keep source value
24
- else
25
- source_value
26
- end
27
- end
28
- end
29
-
36
+ #
37
+ # Initializes a new Configuration with default values
38
+ # Sets up the configuration structure including factory settings and debug proxy
39
+ #
40
+ # @return [Configuration] A new configuration instance with defaults
41
+ #
30
42
  def initialize
31
43
  config = Normalizer.default_configuration
32
44
 
33
- # Allows me to modify the error backtrace reporting within rspec
34
- RSpec.configuration.instance_variable_set(:@backtrace_formatter, BacktraceFormatter)
35
-
36
45
  config[:base_url] = "http://localhost:3000"
37
46
  config[:factories] = Factories.new
38
- config[:specs] = RSpec.configuration
39
47
  config[:on_debug] = Runner::DebugProxy.default
40
48
 
41
49
  super(**config)
42
50
  end
43
51
 
52
+ #
53
+ # Validates the configuration and applies normalization
54
+ # Ensures all required fields have values and applies defaults when needed
55
+ #
56
+ # @return [self] Returns self for method chaining
57
+ #
58
+ # @api private
59
+ #
44
60
  def validate
45
61
  output = Normalizer.normalize_configuration!(to_h)
46
62
 
@@ -52,10 +68,63 @@ module SpecForge
52
68
  self
53
69
  end
54
70
 
71
+ #
72
+ # Recursively converts the configuration to a hash representation
73
+ #
74
+ # @return [Hash] Hash representation of the configuration
75
+ #
55
76
  def to_h
56
- hash = super.except(:specs)
77
+ hash = super
57
78
  hash[:factories] = hash[:factories].to_h
58
79
  hash
59
80
  end
81
+
82
+ #
83
+ # Returns the RSpec configuration object
84
+ # Provides access to RSpec's internal configuration for test customization
85
+ #
86
+ # @return [RSpec::Core::Configuration] RSpec's configuration object
87
+ #
88
+ # @example Setting formatter options
89
+ # SpecForge.configure do |config|
90
+ # config.specs.formatter = :documentation
91
+ # end
92
+ #
93
+ def specs
94
+ RSpec.configuration
95
+ end
96
+
97
+ alias_method :rspec, :specs
98
+
99
+ #
100
+ # Registers a callback for a specific test lifecycle event
101
+ # Allows custom code execution at specific points during test execution
102
+ #
103
+ # @param name [Symbol, String] The callback point to register for
104
+ # (:before_file, :after_expectation, etc.)
105
+ # @yield A block to execute when the callback is triggered
106
+ # @yieldparam context [Object] An object containing context-specific state data, depending
107
+ # on which hook the callback is triggered from.
108
+ #
109
+ # @return [Proc] The registered callback
110
+ #
111
+ # @example Registering a custom debug handler
112
+ # SpecForge.configure do |config|
113
+ # config.register_callback(:on_debug) { binding.pry }
114
+ # end
115
+ #
116
+ # @example Cleaning database after each test
117
+ # SpecForge.configure do |config|
118
+ # config.register_callback(:after_expectation) do
119
+ # DatabaseCleaner.clean
120
+ # end
121
+ # end
122
+ #
123
+ def register_callback(name, &)
124
+ Callbacks.register(name, &)
125
+ end
126
+
127
+ alias_method :define_callback, :register_callback
128
+ alias_method :callback, :register_callback
60
129
  end
61
130
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Context
5
+ #
6
+ # Manages user-defined callbacks grouped by lifecycle hook
7
+ #
8
+ # This class collects and organizes callbacks by their hook type
9
+ # (before_file, after_each, etc.) to support the test lifecycle.
10
+ # It ensures callbacks are properly categorized for execution.
11
+ #
12
+ # @example Creating callback groups
13
+ # callbacks = Context::Callbacks.new([
14
+ # {before_file: "setup_environment"},
15
+ # {after_each: "log_test_result"}
16
+ # ])
17
+ #
18
+ class Callbacks
19
+ #
20
+ # Creates a new callbacks collection
21
+ #
22
+ # @param callback_array [Array] Optional initial callbacks to register
23
+ #
24
+ # @return [Callbacks] A new callbacks collection
25
+ #
26
+ def initialize(callback_array = [])
27
+ set(callback_array)
28
+ end
29
+
30
+ #
31
+ # Updates the callbacks collection
32
+ #
33
+ # @param callback_array [Array] New callbacks to register
34
+ #
35
+ # @return [self]
36
+ #
37
+ def set(callback_array)
38
+ @inner = organize_callbacks_by_hook(callback_array)
39
+ self
40
+ end
41
+
42
+ #
43
+ # Returns the hash representation of callbacks
44
+ #
45
+ # @return [Hash] Callbacks organized by hook type
46
+ #
47
+ def to_h
48
+ @inner
49
+ end
50
+
51
+ #
52
+ # Executes all registered callbacks for a specific lifecycle hook
53
+ #
54
+ # @param hook_name [String, Symbol] The lifecycle hook (before_file, after_each, etc.)
55
+ # @param context [Hash] State data that will be converted to a structured object
56
+ # and passed to callbacks
57
+ #
58
+ def run(hook_name, context = {})
59
+ context = context.to_struct
60
+
61
+ @inner[hook_name].each do |callback_name|
62
+ SpecForge::Callbacks.run(callback_name, context)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ #
69
+ # Organizes callbacks from an array to hash structure by hook type
70
+ # Groups callbacks like before_file, after_each, etc. for easier lookup
71
+ #
72
+ # @param callback_array [Array] The array of callbacks
73
+ #
74
+ # @return [Hash] Callbacks indexed by hook type
75
+ #
76
+ # @private
77
+ #
78
+ def organize_callbacks_by_hook(callback_array)
79
+ groups = Hash.new { |h, k| h[k] = Set.new }
80
+
81
+ callback_array.each_with_object(groups) do |callbacks, groups|
82
+ callbacks.each do |hook, name|
83
+ next if name.blank?
84
+
85
+ groups[hook].add(name)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Context
5
+ #
6
+ # Manages global state and variables at the spec file level.
7
+ #
8
+ # The Global class provides access to variables that are defined at the global level
9
+ # in a spec file and are accessible across all specs and expectations in a file.
10
+ # Unlike regular variables, global variables do not support overlaying - they maintain
11
+ # consistent values throughout test execution.
12
+ #
13
+ # @example Basic usage
14
+ # global = Global.new(variables: {api_version: "v2", environment: "test"})
15
+ #
16
+ # global.variables[:api_version] #=> "v2"
17
+ # global.variables[:environment] #=> "test"
18
+ #
19
+ # # Update global variables
20
+ # global.set(variables: {environment: "staging"})
21
+ # global.variables[:environment] #=> "staging"
22
+ # global.variables[:api_version] #=> nil
23
+ #
24
+ class Global
25
+ # @return [Context::Variables] The container for global variables
26
+ attr_reader :variables
27
+
28
+ # @return [Context::Callbacks] The container for callbacks
29
+ attr_reader :callbacks
30
+
31
+ #
32
+ # Creates a new Global context instance
33
+ #
34
+ # @param variables [Hash<Symbol, Object>] A hash of variable names and values
35
+ # @param callbacks [Array<Hash<Symbol, String>>] An array of callback hooks
36
+ #
37
+ # @return [Global] The new Global instance
38
+ #
39
+ def initialize(variables: {}, callbacks: [])
40
+ @variables = Variables.new(base: variables)
41
+ @callbacks = Callbacks.new(callbacks)
42
+ end
43
+
44
+ #
45
+ # Sets the global variables
46
+ #
47
+ # @param variables [Hash<Symbol, Object>] A hash of variable names and values
48
+ # @param callbacks [Array<Hash<Symbol, String>>] An array of callback hooks
49
+ #
50
+ # @return [self]
51
+ #
52
+ def set(variables: {}, callbacks: [])
53
+ @variables.set(base: variables)
54
+ @callbacks.set(callbacks)
55
+
56
+ self
57
+ end
58
+
59
+ #
60
+ # Returns a hash representation of the global context
61
+ #
62
+ # @return [Hash]
63
+ #
64
+ def to_h
65
+ {
66
+ variables: variables.to_h,
67
+ callbacks: callbacks.to_h
68
+ }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Context
5
+ #
6
+ # Manages storage of API responses for use in subsequent tests
7
+ #
8
+ # This class provides a mechanism to store HTTP requests and responses
9
+ # during test execution, allowing values to be referenced in later tests
10
+ # through the `store.id.body.attribute` syntax.
11
+ #
12
+ # @example Storing and retrieving a response in specs
13
+ # # In one expectation:
14
+ # store_as: user_creation
15
+ #
16
+ # # In a later test:
17
+ # query:
18
+ # id: store.user_creation.body.id
19
+ #
20
+ class Store
21
+ #
22
+ # Represents a single stored entry with request, variables, and response data
23
+ #
24
+ # Entries are immutable once created and contain a deep-frozen
25
+ # snapshot of the test state at the time of storage.
26
+ #
27
+ # @example Accessing stored entry data
28
+ # entry = store["user_creation"]
29
+ # entry.status # => 201
30
+ # entry.body.id # => 42
31
+ #
32
+ class Entry < Data.define(:scope, :request, :variables, :response)
33
+ #
34
+ # Creates a new immutable store entry
35
+ #
36
+ # @param request [Hash] The HTTP request that was executed
37
+ # @param variables [Hash] Variables from the test context
38
+ # @param response [Hash] The HTTP response received
39
+ # @param scope [Symbol] Scope of this entry, either :file or :spec
40
+ #
41
+ # @return [Entry] A new immutable entry instance
42
+ #
43
+ def initialize(request:, variables:, response:, scope: :file)
44
+ request = request.deep_freeze
45
+ variables = variables.deep_freeze
46
+ response = response.deep_freeze
47
+
48
+ super
49
+ end
50
+
51
+ #
52
+ # Shorthand accessor for the HTTP status code
53
+ #
54
+ # @return [Integer] The response status code
55
+ #
56
+ def status = response[:status]
57
+
58
+ #
59
+ # Shorthand accessor for the response body
60
+ #
61
+ # @return [Hash, Array, String] The parsed response body
62
+ #
63
+ def body = response[:body]
64
+
65
+ #
66
+ # Shorthand accessor for the response headers
67
+ #
68
+ # @return [Hash] The response headers
69
+ #
70
+ def headers = response[:headers]
71
+
72
+ #
73
+ # Returns all available methods that can be called
74
+ #
75
+ # @return [Array] The method names
76
+ #
77
+ def available_methods
78
+ members + [:status, :body, :headers]
79
+ end
80
+ end
81
+
82
+ #
83
+ # Creates a new empty store
84
+ #
85
+ # @return [Store] A new store instance
86
+ #
87
+ def initialize
88
+ @inner = {}
89
+ end
90
+
91
+ #
92
+ # Retrieves a stored entry by ID
93
+ #
94
+ # @param id [String, Symbol] The identifier for the stored entry
95
+ #
96
+ # @return [Entry, nil] The stored entry or nil if not found
97
+ #
98
+ def [](id)
99
+ @inner[id]
100
+ end
101
+
102
+ #
103
+ # Returns the number of entries in the store
104
+ #
105
+ # @return [Integer] The count of stored entries
106
+ #
107
+ def size
108
+ @inner.size
109
+ end
110
+
111
+ #
112
+ # Stores an entry with the specified ID
113
+ #
114
+ # @param id [String, Symbol] The identifier to store the entry under
115
+ #
116
+ # @return [self]
117
+ #
118
+ def set(id, **)
119
+ @inner[id] = Entry.new(**)
120
+
121
+ self
122
+ end
123
+
124
+ #
125
+ # Removes all entries from the store
126
+ #
127
+ def clear
128
+ @inner.clear
129
+ end
130
+
131
+ #
132
+ # Removes all spec entries from the store
133
+ #
134
+ def clear_specs
135
+ @inner.delete_if { |_k, v| v.scope == :spec }
136
+ end
137
+
138
+ #
139
+ # Returns a hash representation of store
140
+ #
141
+ # @return [Hash]
142
+ #
143
+ def to_h
144
+ @inner.transform_values(&:to_h).deep_stringify_keys
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Context
5
+ #
6
+ # Manages variable resolution across different expectations in SpecForge tests.
7
+ #
8
+ # The Variables class handles two layers of variable definitions:
9
+ # - Base variables: The core set of variables defined at the spec level
10
+ # - Overlay variables: Additional variables defined at the expectation level
11
+ # that can override base variables with the same name.
12
+ #
13
+ # @example Basic usage
14
+ # variables = Variables.new(
15
+ # base: {user_id: 123, name: "Test User"},
16
+ # overlay: {
17
+ # "expectation_1": {name: "Override User"}
18
+ # }
19
+ # )
20
+ #
21
+ # variables[:user_id] #=> 123
22
+ # variables[:name] #=> "Test User"
23
+ #
24
+ # variables.use_overlay("expectation_1")
25
+ # variables[:name] #=> "Override User"
26
+ # variables[:user_id] #=> 123 (unchanged)
27
+ #
28
+ class Variables < Hash
29
+ attr_reader :base, :overlay
30
+
31
+ #
32
+ # Creates a new Variables container with base and overlay definitions
33
+ #
34
+ # @param base [Hash] The base set of variables (typically defined at spec level)
35
+ # @param overlay [Hash<String, Hash>] A hash of overlay variable sets keyed by ID
36
+ #
37
+ # @return [Variables]
38
+ #
39
+ def initialize(base: {}, overlay: {})
40
+ set(base:, overlay:)
41
+ end
42
+
43
+ #
44
+ # Sets the base and overlay variable hashes
45
+ #
46
+ # @param base [Hash] The new base variable hash
47
+ # @param overlay [Hash<String, Hash>] The new overlay variable hashes
48
+ #
49
+ # @return [self]
50
+ #
51
+ def set(base:, overlay: {})
52
+ @base = Attribute.from(base)
53
+ @overlay = overlay
54
+
55
+ resolve_into_self(@base)
56
+ self
57
+ end
58
+
59
+ #
60
+ # Applies a specific overlay to the base variables
61
+ # If the overlay doesn't exist or is empty, no changes are made.
62
+ #
63
+ # @param id [String] The ID of the overlay to apply
64
+ #
65
+ # @return [nil]
66
+ #
67
+ def use_overlay(id)
68
+ active = @base
69
+
70
+ if (overlay = @overlay[id]) && overlay.present?
71
+ active = active.deep_merge(overlay)
72
+ end
73
+
74
+ resolve_into_self(active)
75
+ self
76
+ end
77
+
78
+ private
79
+
80
+ def resolve_into_self(hash)
81
+ # Start fresh
82
+ clear
83
+
84
+ # Load the resolved values into self
85
+ hash.each do |key, value|
86
+ self[key] = Attribute.from(value).resolved
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ #
5
+ # Core data structure that maintains context during test execution
6
+ #
7
+ # Context stores and provides access to global variables, test variables, and
8
+ # shared state across specs.
9
+ # It acts as a central repository for test data during execution.
10
+ #
11
+ # @example Accessing the current context
12
+ # SpecForge.context.variables[:user_id] #=> 123
13
+ #
14
+ class Context < Data.define(:global, :store, :variables)
15
+ #
16
+ # Creates a new context with default values
17
+ #
18
+ # @param global [Hash] Global variables shared across all specs
19
+ # @param variables [Hash] Test variables specific to the current context
20
+ #
21
+ # @return [Context] A new context instance
22
+ #
23
+ def initialize(global: {}, variables: {})
24
+ super(
25
+ global: Global.new(**global),
26
+ store: Store.new,
27
+ variables: Variables.new(**variables)
28
+ )
29
+ end
30
+ end
31
+ end
32
+
33
+ require_relative "context/callbacks"
34
+ require_relative "context/global"
35
+ require_relative "context/store"
36
+ require_relative "context/variables"
@@ -1,18 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ return if defined?(SPEC_FORGE_INTERNAL_TESTING)
4
+
5
+ #
6
+ # RSpec's core testing framework module
7
+ # Provides the fundamental structure and functionality for RSpec tests
8
+ #
3
9
  module RSpec
10
+ #
11
+ # Core implementation details and extensions for RSpec
12
+ # Contains the fundamental building blocks of the RSpec testing framework
13
+ #
4
14
  module Core
15
+ #
16
+ # Handles notifications and reporting for RSpec test runs
17
+ # Manages how test results and metadata are processed and communicated
18
+ #
5
19
  module Notifications
6
20
  #
7
- # I did attempt to do this without monkey patching
8
- # Getting around the `rspec` word was making it difficult
21
+ # A monkey patch of an internal RSpec class to allow SpecForge to replace parts of
22
+ # RSpec's reporting output in order to provide useful feedback to the user.
23
+ # This replaces "rspec" in commands with "spec_forge", removes any line numbers, and
24
+ # ensures that failures properly report the YAML file that it occurred in.
9
25
  #
10
26
  class SummaryNotification
27
+ #
28
+ # Create an alias to RSpec original colorized_rerun_commands so it can be called at a
29
+ # later point.
30
+ #
31
+ alias_method :og_colorized_rerun_commands, :colorized_rerun_commands
32
+
11
33
  # Customizes RSpec's failure output to:
12
34
  # 1. Use 'spec_forge' instead of 'rspec' for rerun commands
13
35
  # 2. Remove line numbers since SpecForge uses dynamic spec generation
14
- alias_method :og_colorized_rerun_commands, :colorized_rerun_commands
15
-
16
36
  def colorized_rerun_commands(colorizer)
17
37
  # Updating these at this point fixes the re-run for some failures - it depends
18
38
  failed_examples.each do |example|