roast-ai 0.2.1 → 0.2.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +5 -1
  3. data/.gitignore +29 -1
  4. data/CHANGELOG.md +27 -0
  5. data/CLAUDE.md +5 -0
  6. data/Gemfile.lock +3 -4
  7. data/README.md +162 -3
  8. data/examples/grading/generate_recommendations/output.txt +6 -6
  9. data/examples/pre_post_processing/README.md +111 -0
  10. data/examples/pre_post_processing/analyze_test_file/prompt.md +23 -0
  11. data/examples/pre_post_processing/improve_test_coverage/prompt.md +17 -0
  12. data/examples/pre_post_processing/optimize_test_performance/prompt.md +25 -0
  13. data/examples/pre_post_processing/post_processing/aggregate_metrics/prompt.md +31 -0
  14. data/examples/pre_post_processing/post_processing/cleanup_environment/prompt.md +28 -0
  15. data/examples/pre_post_processing/post_processing/generate_summary_report/prompt.md +32 -0
  16. data/examples/pre_post_processing/post_processing/output.txt +24 -0
  17. data/examples/pre_post_processing/pre_processing/gather_baseline_metrics/prompt.md +26 -0
  18. data/examples/pre_post_processing/pre_processing/setup_test_environment/prompt.md +11 -0
  19. data/examples/pre_post_processing/validate_changes/prompt.md +24 -0
  20. data/examples/pre_post_processing/workflow.yml +21 -0
  21. data/examples/single_target_prepost/README.md +36 -0
  22. data/examples/single_target_prepost/post_processing/output.txt +27 -0
  23. data/examples/single_target_prepost/pre_processing/gather_dependencies/prompt.md +11 -0
  24. data/examples/single_target_prepost/workflow.yml +20 -0
  25. data/gemfiles/activesupport7.gemfile +4 -0
  26. data/gemfiles/activesupport8.gemfile +4 -0
  27. data/lib/roast/tools/grep.rb +13 -4
  28. data/lib/roast/tools/search_file.rb +2 -2
  29. data/lib/roast/tools.rb +16 -1
  30. data/lib/roast/value_objects/uri_base.rb +49 -0
  31. data/lib/roast/value_objects.rb +1 -0
  32. data/lib/roast/version.rb +1 -1
  33. data/lib/roast/workflow/api_configuration.rb +9 -1
  34. data/lib/roast/workflow/base_workflow.rb +4 -1
  35. data/lib/roast/workflow/command_executor.rb +5 -2
  36. data/lib/roast/workflow/configuration.rb +4 -2
  37. data/lib/roast/workflow/configuration_loader.rb +14 -0
  38. data/lib/roast/workflow/error_handler.rb +18 -0
  39. data/lib/roast/workflow/expression_evaluator.rb +8 -0
  40. data/lib/roast/workflow/step_executor_coordinator.rb +34 -8
  41. data/lib/roast/workflow/step_loader.rb +15 -2
  42. data/lib/roast/workflow/workflow_execution_context.rb +39 -0
  43. data/lib/roast/workflow/workflow_executor.rb +22 -2
  44. data/lib/roast/workflow/workflow_initializer.rb +11 -2
  45. data/lib/roast/workflow/workflow_runner.rb +127 -5
  46. data/lib/roast/workflow.rb +1 -0
  47. data/lib/roast.rb +7 -1
  48. data/roast.gemspec +1 -1
  49. data/schema/workflow.json +14 -0
  50. metadata +25 -5
@@ -81,11 +81,19 @@ module Roast
81
81
  end
82
82
  end
83
83
 
84
+ def client_options
85
+ {
86
+ access_token: @configuration.api_token,
87
+ uri_base: @configuration.uri_base&.to_s,
88
+ }.compact
89
+ end
90
+
84
91
  def configure_openrouter_client
85
92
  $stderr.puts "Configuring OpenRouter client with token from workflow"
86
93
  require "open_router"
87
94
 
88
- client = OpenRouter::Client.new(access_token: @configuration.api_token)
95
+ client = OpenRouter::Client.new(client_options)
96
+
89
97
  Raix.configure do |config|
90
98
  config.openrouter_client = client
91
99
  end
@@ -96,7 +104,8 @@ module Roast
96
104
  $stderr.puts "Configuring OpenAI client with token from workflow"
97
105
  require "openai"
98
106
 
99
- client = OpenAI::Client.new(access_token: @configuration.api_token)
107
+ client = OpenAI::Client.new(client_options)
108
+
100
109
  Raix.configure do |config|
101
110
  config.openai_client = client
102
111
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/notifications"
4
+ require "erb"
4
5
  require "roast/workflow/replay_handler"
5
6
  require "roast/workflow/workflow_executor"
6
7
  require "roast/workflow/output_handler"
7
8
  require "roast/workflow/base_workflow"
9
+ require "roast/workflow/dot_access_hash"
8
10
 
9
11
  module Roast
10
12
  module Workflow
@@ -14,6 +16,7 @@ module Roast
14
16
  @configuration = configuration
15
17
  @options = options
16
18
  @output_handler = OutputHandler.new
19
+ @execution_context = WorkflowExecutionContext.new
17
20
  end
18
21
 
19
22
  def run_for_files(files)
@@ -21,22 +24,65 @@ module Roast
21
24
  $stderr.puts "WARNING: Ignoring target parameter because files were provided: #{@configuration.target}"
22
25
  end
23
26
 
27
+ # Execute pre-processing steps once before any targets
28
+ if @configuration.pre_processing.any?
29
+ $stderr.puts "Running pre-processing steps..."
30
+ run_pre_processing
31
+ end
32
+
33
+ # Execute main workflow for each file
24
34
  files.each do |file|
25
35
  $stderr.puts "Running workflow for file: #{file}"
26
36
  run_single_workflow(file.strip)
27
37
  end
38
+
39
+ # Execute post-processing steps once after all targets
40
+ if @configuration.post_processing.any?
41
+ $stderr.puts "Running post-processing steps..."
42
+ run_post_processing
43
+ end
28
44
  end
29
45
 
30
46
  def run_for_targets
31
- @configuration.target.lines.each do |file|
32
- $stderr.puts "Running workflow for file: #{file.strip}"
33
- run_single_workflow(file.strip)
47
+ # Split targets by line and clean up
48
+ target_lines = @configuration.target.lines.map(&:strip).reject(&:empty?)
49
+
50
+ # Execute pre-processing steps once before any targets
51
+ if @configuration.pre_processing.any?
52
+ $stderr.puts "Running pre-processing steps..."
53
+ run_pre_processing
54
+ end
55
+
56
+ # Execute main workflow for each target
57
+ target_lines.each do |file|
58
+ $stderr.puts "Running workflow for file: #{file}"
59
+ run_single_workflow(file)
60
+ end
61
+
62
+ # Execute post-processing steps once after all targets
63
+ if @configuration.post_processing.any?
64
+ $stderr.puts "Running post-processing steps..."
65
+ run_post_processing
34
66
  end
35
67
  end
36
68
 
37
69
  def run_targetless
38
70
  $stderr.puts "Running targetless workflow"
71
+
72
+ # Execute pre-processing steps
73
+ if @configuration.pre_processing.any?
74
+ $stderr.puts "Running pre-processing steps..."
75
+ run_pre_processing
76
+ end
77
+
78
+ # Execute main workflow
39
79
  run_single_workflow(nil)
80
+
81
+ # Execute post-processing steps
82
+ if @configuration.post_processing.any?
83
+ $stderr.puts "Running post-processing steps..."
84
+ run_post_processing
85
+ end
40
86
  end
41
87
 
42
88
  # Public for backward compatibility with tests
@@ -63,11 +109,59 @@ module Roast
63
109
  private
64
110
 
65
111
  def run_single_workflow(file)
66
- workflow = create_workflow(file)
112
+ # Pass pre-processing data to target workflows
113
+ # Flatten the structure to remove the 'output' intermediary
114
+ pre_processing_data = @execution_context.pre_processing_output.raw_output.merge(
115
+ final_output: @execution_context.pre_processing_output.final_output,
116
+ )
117
+ workflow = create_workflow(file, pre_processing_data: pre_processing_data)
67
118
  execute_workflow(workflow)
119
+
120
+ # Store workflow output in execution context
121
+ @execution_context.add_target_output(file, workflow.output_manager)
122
+ end
123
+
124
+ def run_pre_processing
125
+ # Create a workflow for pre-processing (no specific file target)
126
+ workflow = create_workflow(nil)
127
+
128
+ # Execute pre-processing steps
129
+ executor = WorkflowExecutor.new(workflow, @configuration.config_hash, @configuration.context_path, phase: :pre_processing)
130
+ executor.execute_steps(@configuration.pre_processing)
131
+
132
+ # Store pre-processing output in execution context
133
+ @execution_context.pre_processing_output.output = workflow.output_manager.raw_output
134
+ @execution_context.pre_processing_output.final_output = workflow.output_manager.final_output
135
+ end
136
+
137
+ def run_post_processing
138
+ # Create a workflow for post-processing with access to all results
139
+ workflow = create_workflow(nil)
140
+
141
+ # Pass execution context data to post-processing workflow
142
+ # Make pre_processing available as a top-level DotNotationHash with flattened structure
143
+ pre_processing_data = @execution_context.pre_processing_output.raw_output.merge(
144
+ final_output: @execution_context.pre_processing_output.final_output,
145
+ )
146
+ workflow.instance_variable_set(:@pre_processing, DotAccessHash.new(pre_processing_data))
147
+ workflow.define_singleton_method(:pre_processing) { @pre_processing }
148
+
149
+ # Keep targets in output for now
150
+ workflow.output[:targets] = @execution_context.target_outputs.transform_values(&:to_h)
151
+
152
+ # Execute post-processing steps
153
+ executor = WorkflowExecutor.new(workflow, @configuration.config_hash, @configuration.context_path, phase: :post_processing)
154
+ executor.execute_steps(@configuration.post_processing)
155
+
156
+ # Apply output.txt template if it exists
157
+ apply_post_processing_template(workflow)
158
+
159
+ # Save post-processing outputs
160
+ @output_handler.save_final_output(workflow)
161
+ @output_handler.write_results(workflow)
68
162
  end
69
163
 
70
- def create_workflow(file)
164
+ def create_workflow(file, pre_processing_data: nil)
71
165
  BaseWorkflow.new(
72
166
  file,
73
167
  name: @configuration.basename,
@@ -75,6 +169,7 @@ module Roast
75
169
  resource: @configuration.resource,
76
170
  session_name: @configuration.name,
77
171
  configuration: @configuration,
172
+ pre_processing_data:,
78
173
  ).tap do |workflow|
79
174
  workflow.output_file = @options[:output] if @options[:output].present?
80
175
  workflow.verbose = @options[:verbose] if @options[:verbose].present?
@@ -82,6 +177,33 @@ module Roast
82
177
  workflow.pause_step_name = @options[:pause] if @options[:pause].present?
83
178
  end
84
179
  end
180
+
181
+ def apply_post_processing_template(workflow)
182
+ # Check for output.txt template in post_processing directory
183
+ template_path = File.join(@configuration.context_path, "post_processing", "output.txt")
184
+ return unless File.exist?(template_path)
185
+
186
+ # Prepare data for template
187
+ template_data = {
188
+ pre_processing: DotAccessHash.new(@execution_context.pre_processing_output.to_h),
189
+ targets: @execution_context.target_outputs.transform_values { |v| DotAccessHash.new(v.to_h) },
190
+ output: DotAccessHash.new(workflow.output_manager.raw_output),
191
+ final_output: workflow.final_output,
192
+ }
193
+
194
+ # Create binding for ERB template with access to template data
195
+ template_binding = binding
196
+ template_data.each do |key, value|
197
+ template_binding.local_variable_set(key, value)
198
+ end
199
+
200
+ # Apply template and append to final output
201
+ template_content = File.read(template_path)
202
+ rendered_output = ERB.new(template_content, trim_mode: "-").result(template_binding)
203
+ workflow.append_to_final_output(rendered_output)
204
+ rescue => e
205
+ $stderr.puts "Warning: Failed to apply post-processing output template: #{e.message}"
206
+ end
85
207
  end
86
208
  end
87
209
  end
@@ -7,6 +7,7 @@ require "roast/workflow/repeat_step"
7
7
  require "roast/workflow/each_step"
8
8
  require "roast/workflow/base_workflow"
9
9
  require "roast/workflow/configuration"
10
+ require "roast/workflow/workflow_execution_context"
10
11
  require "roast/workflow/workflow_executor"
11
12
  require "roast/workflow/configuration_parser"
12
13
  require "roast/workflow/validator"
data/lib/roast.rb CHANGED
@@ -28,7 +28,13 @@ module Roast
28
28
  raise Thor::Error, "Workflow configuration file is required" if paths.empty?
29
29
 
30
30
  workflow_path, *files = paths
31
- expanded_workflow_path = File.expand_path(workflow_path)
31
+
32
+ expanded_workflow_path = if workflow_path.include?("workflow.yml")
33
+ File.expand_path(workflow_path)
34
+ else
35
+ File.expand_path("roast/#{workflow_path}/workflow.yml")
36
+ end
37
+
32
38
  raise Thor::Error, "Expected a Roast workflow configuration file, got directory: #{expanded_workflow_path}" if File.directory?(expanded_workflow_path)
33
39
 
34
40
  Roast::Workflow::ConfigurationParser.new(expanded_workflow_path, files, options.transform_keys(&:to_sym)).begin!
data/roast.gemspec CHANGED
@@ -36,7 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
37
37
  spec.require_paths = ["lib"]
38
38
 
39
- spec.add_dependency("activesupport", "~> 8.0")
39
+ spec.add_dependency("activesupport", ">= 7.0")
40
40
  spec.add_dependency("cli-ui")
41
41
  spec.add_dependency("diff-lcs", "~> 1.5")
42
42
  spec.add_dependency("faraday-retry")
data/schema/workflow.json CHANGED
@@ -41,6 +41,13 @@
41
41
  ]
42
42
  }
43
43
  },
44
+ "pre_processing": {
45
+ "type": "array",
46
+ "description": "Steps executed once before any targets are processed",
47
+ "items": {
48
+ "$ref": "#/properties/steps/items"
49
+ }
50
+ },
44
51
  "steps": {
45
52
  "type": "array",
46
53
  "items": {
@@ -203,6 +210,13 @@
203
210
  ]
204
211
  }
205
212
  },
213
+ "post_processing": {
214
+ "type": "array",
215
+ "description": "Steps executed once after all targets have been processed",
216
+ "items": {
217
+ "$ref": "#/properties/steps/items"
218
+ }
219
+ },
206
220
  "proceed?": {
207
221
  "type": "object",
208
222
  "properties": {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roast-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
@@ -13,16 +13,16 @@ dependencies:
13
13
  name: activesupport
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - "~>"
16
+ - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '8.0'
18
+ version: '7.0'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
- - - "~>"
23
+ - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '8.0'
25
+ version: '7.0'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: cli-ui
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -207,12 +207,28 @@ files:
207
207
  - examples/openrouter_example/analyze_input/prompt.md
208
208
  - examples/openrouter_example/generate_response/prompt.md
209
209
  - examples/openrouter_example/workflow.yml
210
+ - examples/pre_post_processing/README.md
211
+ - examples/pre_post_processing/analyze_test_file/prompt.md
212
+ - examples/pre_post_processing/improve_test_coverage/prompt.md
213
+ - examples/pre_post_processing/optimize_test_performance/prompt.md
214
+ - examples/pre_post_processing/post_processing/aggregate_metrics/prompt.md
215
+ - examples/pre_post_processing/post_processing/cleanup_environment/prompt.md
216
+ - examples/pre_post_processing/post_processing/generate_summary_report/prompt.md
217
+ - examples/pre_post_processing/post_processing/output.txt
218
+ - examples/pre_post_processing/pre_processing/gather_baseline_metrics/prompt.md
219
+ - examples/pre_post_processing/pre_processing/setup_test_environment/prompt.md
220
+ - examples/pre_post_processing/validate_changes/prompt.md
221
+ - examples/pre_post_processing/workflow.yml
210
222
  - examples/rspec_to_minitest/README.md
211
223
  - examples/rspec_to_minitest/analyze_spec/prompt.md
212
224
  - examples/rspec_to_minitest/create_minitest/prompt.md
213
225
  - examples/rspec_to_minitest/run_and_improve/prompt.md
214
226
  - examples/rspec_to_minitest/workflow.md
215
227
  - examples/rspec_to_minitest/workflow.yml
228
+ - examples/single_target_prepost/README.md
229
+ - examples/single_target_prepost/post_processing/output.txt
230
+ - examples/single_target_prepost/pre_processing/gather_dependencies/prompt.md
231
+ - examples/single_target_prepost/workflow.yml
216
232
  - examples/smart_coercion_defaults/README.md
217
233
  - examples/smart_coercion_defaults/workflow.yml
218
234
  - examples/step_configuration/README.md
@@ -224,6 +240,8 @@ files:
224
240
  - examples/workflow_generator/info_from_roast.rb
225
241
  - examples/workflow_generator/workflow.yml
226
242
  - exe/roast
243
+ - gemfiles/activesupport7.gemfile
244
+ - gemfiles/activesupport8.gemfile
227
245
  - lib/roast.rb
228
246
  - lib/roast/errors.rb
229
247
  - lib/roast/factories/api_provider_factory.rb
@@ -253,6 +271,7 @@ files:
253
271
  - lib/roast/value_objects.rb
254
272
  - lib/roast/value_objects/api_token.rb
255
273
  - lib/roast/value_objects/step_name.rb
274
+ - lib/roast/value_objects/uri_base.rb
256
275
  - lib/roast/value_objects/workflow_path.rb
257
276
  - lib/roast/version.rb
258
277
  - lib/roast/workflow.rb
@@ -302,6 +321,7 @@ files:
302
321
  - lib/roast/workflow/step_type_resolver.rb
303
322
  - lib/roast/workflow/validator.rb
304
323
  - lib/roast/workflow/workflow_context.rb
324
+ - lib/roast/workflow/workflow_execution_context.rb
305
325
  - lib/roast/workflow/workflow_executor.rb
306
326
  - lib/roast/workflow/workflow_initializer.rb
307
327
  - lib/roast/workflow/workflow_runner.rb