roast-ai 0.1.0 → 0.1.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/cla.yml +1 -1
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +28 -0
  5. data/CLAUDE.md +3 -1
  6. data/Gemfile +0 -1
  7. data/Gemfile.lock +3 -4
  8. data/README.md +419 -5
  9. data/Rakefile +1 -6
  10. data/docs/INSTRUMENTATION.md +202 -0
  11. data/examples/api_workflow/README.md +85 -0
  12. data/examples/api_workflow/fetch_api_data/prompt.md +10 -0
  13. data/examples/api_workflow/generate_report/prompt.md +10 -0
  14. data/examples/api_workflow/prompt.md +10 -0
  15. data/examples/api_workflow/transform_data/prompt.md +10 -0
  16. data/examples/api_workflow/workflow.yml +30 -0
  17. data/examples/grading/format_result.rb +25 -9
  18. data/examples/grading/js_test_runner +31 -0
  19. data/examples/grading/rb_test_runner +19 -0
  20. data/examples/grading/read_dependencies/prompt.md +14 -0
  21. data/examples/grading/run_coverage.rb +2 -2
  22. data/examples/grading/workflow.yml +3 -12
  23. data/examples/instrumentation.rb +76 -0
  24. data/examples/rspec_to_minitest/README.md +68 -0
  25. data/examples/rspec_to_minitest/analyze_spec/prompt.md +30 -0
  26. data/examples/rspec_to_minitest/create_minitest/prompt.md +33 -0
  27. data/examples/rspec_to_minitest/run_and_improve/prompt.md +35 -0
  28. data/examples/rspec_to_minitest/workflow.md +10 -0
  29. data/examples/rspec_to_minitest/workflow.yml +40 -0
  30. data/lib/roast/helpers/function_caching_interceptor.rb +72 -8
  31. data/lib/roast/helpers/prompt_loader.rb +2 -0
  32. data/lib/roast/resources/api_resource.rb +137 -0
  33. data/lib/roast/resources/base_resource.rb +47 -0
  34. data/lib/roast/resources/directory_resource.rb +40 -0
  35. data/lib/roast/resources/file_resource.rb +33 -0
  36. data/lib/roast/resources/none_resource.rb +29 -0
  37. data/lib/roast/resources/url_resource.rb +63 -0
  38. data/lib/roast/resources.rb +100 -0
  39. data/lib/roast/tools/coding_agent.rb +69 -0
  40. data/lib/roast/tools.rb +1 -0
  41. data/lib/roast/version.rb +1 -1
  42. data/lib/roast/workflow/base_step.rb +21 -17
  43. data/lib/roast/workflow/base_workflow.rb +69 -17
  44. data/lib/roast/workflow/configuration.rb +83 -8
  45. data/lib/roast/workflow/configuration_parser.rb +218 -3
  46. data/lib/roast/workflow/file_state_repository.rb +156 -0
  47. data/lib/roast/workflow/prompt_step.rb +16 -0
  48. data/lib/roast/workflow/session_manager.rb +82 -0
  49. data/lib/roast/workflow/state_repository.rb +21 -0
  50. data/lib/roast/workflow/workflow_executor.rb +99 -9
  51. data/lib/roast/workflow.rb +4 -0
  52. data/lib/roast.rb +2 -5
  53. data/roast.gemspec +1 -1
  54. data/schema/workflow.json +12 -0
  55. metadata +34 -6
  56. data/.rspec +0 -1
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support"
4
+ require "active_support/isolated_execution_state"
5
+ require "active_support/notifications"
6
+
3
7
  module Roast
4
8
  module Workflow
5
9
  # Handles the execution of workflow steps, including orchestration and threading
@@ -30,16 +34,60 @@ module Roast
30
34
  end
31
35
 
32
36
  def execute_step(name)
33
- $stderr.puts "Executing: #{name}"
34
- return strip_and_execute(name) if name.starts_with?("%") || name.starts_with?("$(")
37
+ start_time = Time.now
38
+ # For tests, make sure that we handle this gracefully
39
+ resource_type = workflow.respond_to?(:resource) ? workflow.resource&.type : nil
40
+
41
+ ActiveSupport::Notifications.instrument("roast.step.start", {
42
+ step_name: name,
43
+ resource_type: resource_type,
44
+ })
45
+
46
+ $stderr.puts "Executing: #{name} (Resource type: #{resource_type || "unknown"})"
47
+
48
+ result = if name.starts_with?("$(")
49
+ strip_and_execute(name).tap do |output|
50
+ # Add the command and output to the transcript for reference in following steps
51
+ workflow.transcript << { user: "I just executed the following command: ```\n#{name}\n```\n\nHere is the output:\n\n```\n#{output}\n```" }
52
+ workflow.transcript << { assistant: "Noted, thank you." }
53
+ end
54
+ elsif name.include?("*") && (!workflow.respond_to?(:resource) || !workflow.resource)
55
+ # Only use the glob method if we don't have a resource object yet
56
+ # This is for backward compatibility
57
+ glob(name)
58
+ else
59
+ step_object = find_and_load_step(name)
60
+ step_result = step_object.call
61
+ workflow.output[name] = step_result
62
+
63
+ # Save state after each step if the workflow supports it
64
+ save_state(name, step_result) if workflow.respond_to?(:session_name) && workflow.session_name
35
65
 
36
- return glob(name) if name.include?("*")
66
+ step_result
67
+ end
68
+
69
+ execution_time = Time.now - start_time
37
70
 
38
- step_object = find_and_load_step(name)
39
- result = step_object.call
71
+ ActiveSupport::Notifications.instrument("roast.step.complete", {
72
+ step_name: name,
73
+ resource_type: resource_type,
74
+ success: true,
75
+ execution_time: execution_time,
76
+ result_size: result.to_s.length,
77
+ })
40
78
 
41
- workflow.output[name] = result
42
79
  result
80
+ rescue => e
81
+ execution_time = Time.now - start_time
82
+
83
+ ActiveSupport::Notifications.instrument("roast.step.error", {
84
+ step_name: name,
85
+ resource_type: resource_type,
86
+ error: e.class.name,
87
+ message: e.message,
88
+ execution_time: execution_time,
89
+ })
90
+ raise
43
91
  end
44
92
 
45
93
  private
@@ -66,6 +114,11 @@ module Roast
66
114
  end
67
115
 
68
116
  def find_and_load_step(step_name)
117
+ # First check for a prompt step
118
+ if step_name.strip.include?(" ")
119
+ return Roast::Workflow::PromptStep.new(workflow, name: step_name, auto_loop: false)
120
+ end
121
+
69
122
  # First check for a ruby file with the step name
70
123
  rb_file_path = File.join(context_path, "#{step_name}.rb")
71
124
  if File.file?(rb_file_path)
@@ -100,8 +153,15 @@ module Roast
100
153
  def setup_step(step_class, step_name, context_path)
101
154
  step_class.new(workflow, name: step_name, context_path: context_path).tap do |step|
102
155
  step_config = config_hash[step_name]
156
+
157
+ # Always set the model, even if there's no step_config
158
+ # Use step-specific model if defined, otherwise use workflow default model, or fallback to DEFAULT_MODEL
159
+ step.model = step_config&.dig("model") || config_hash["model"] || DEFAULT_MODEL
160
+
161
+ # Pass resource to step if supported
162
+ step.resource = workflow.resource if step.respond_to?(:resource=)
163
+
103
164
  if step_config.present?
104
- step.model = step_config["model"] || DEFAULT_MODEL
105
165
  step.print_response = step_config["print_response"] if step_config["print_response"].present?
106
166
  step.loop = step_config["loop"] if step_config["loop"].present?
107
167
  step.json = step_config["json"] if step_config["json"].present?
@@ -111,8 +171,38 @@ module Roast
111
171
  end
112
172
 
113
173
  def strip_and_execute(step)
114
- command = step.gsub("%", "")
115
- %x(#{command})
174
+ if step.match?(/^\$\((.*)\)$/)
175
+ command = step.strip.match(/^\$\((.*)\)$/)[1]
176
+ %x(#{command})
177
+ else
178
+ raise "Missing closing parentheses: #{step}"
179
+ end
180
+ end
181
+
182
+ def save_state(step_name, step_result)
183
+ state_repository = FileStateRepository.new
184
+
185
+ # Gather necessary data for state
186
+ static_data = workflow.respond_to?(:transcript) ? workflow.transcript.map(&:itself) : []
187
+
188
+ # Get output and final_output if available
189
+ output = workflow.respond_to?(:output) ? workflow.output.clone : {}
190
+ final_output = workflow.respond_to?(:final_output) ? workflow.final_output.clone : []
191
+
192
+ state_data = {
193
+ step_name: step_name,
194
+ order: output.keys.index(step_name) || output.size,
195
+ transcript: static_data,
196
+ output: output,
197
+ final_output: final_output,
198
+ execution_order: output.keys,
199
+ }
200
+
201
+ # Save the state
202
+ state_repository.save_state(workflow, step_name, state_data)
203
+ rescue => e
204
+ # Don't fail the workflow if state saving fails
205
+ $stderr.puts "Warning: Failed to save workflow state: #{e.message}"
116
206
  end
117
207
  end
118
208
  end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "roast/workflow/base_step"
4
+ require "roast/workflow/prompt_step"
4
5
  require "roast/workflow/base_workflow"
5
6
  require "roast/workflow/configuration"
6
7
  require "roast/workflow/workflow_executor"
7
8
  require "roast/workflow/configuration_parser"
8
9
  require "roast/workflow/validator"
10
+ require "roast/workflow/state_repository"
11
+ require "roast/workflow/session_manager"
12
+ require "roast/workflow/file_state_repository"
9
13
 
10
14
  module Roast
11
15
  module Workflow
data/lib/roast.rb CHANGED
@@ -5,6 +5,7 @@ require "thor"
5
5
  require "roast/version"
6
6
  require "roast/tools"
7
7
  require "roast/helpers"
8
+ require "roast/resources"
8
9
  require "roast/workflow"
9
10
 
10
11
  module Roast
@@ -16,7 +17,7 @@ module Roast
16
17
  option :output, type: :string, aliases: "-o", desc: "Save results to a file"
17
18
  option :verbose, type: :boolean, aliases: "-v", desc: "Show output from all steps as they are executed"
18
19
  option :target, type: :string, aliases: "-t", desc: "Override target files. Can be file path, glob pattern, or $(shell command)"
19
- option :subject, type: :string, aliases: "-s", desc: "Subject file to analyze"
20
+ option :replay, type: :string, aliases: "-r", desc: "Resume workflow from a specific step. Format: step_name or session_timestamp:step_name"
20
21
  def execute(*paths)
21
22
  raise Thor::Error, "Workflow configuration file is required" if paths.empty?
22
23
 
@@ -24,10 +25,6 @@ module Roast
24
25
  expanded_workflow_path = File.expand_path(workflow_path)
25
26
  raise Thor::Error, "Expected a Roast workflow configuration file, got directory: #{expanded_workflow_path}" if File.directory?(expanded_workflow_path)
26
27
 
27
- if options[:subject] && !File.exist?(options[:subject])
28
- raise Thor::Error, "Subject file does not exist: #{options[:subject]}"
29
- end
30
-
31
28
  Roast::Workflow::ConfigurationParser.new(expanded_workflow_path, files, options.transform_keys(&:to_sym)).begin!
32
29
  end
33
30
 
data/roast.gemspec CHANGED
@@ -39,6 +39,6 @@ Gem::Specification.new do |spec|
39
39
  spec.add_dependency("activesupport", "~> 8.0")
40
40
  spec.add_dependency("faraday-retry")
41
41
  spec.add_dependency("json-schema")
42
- spec.add_dependency("raix", "0.8.3")
42
+ spec.add_dependency("raix", "~> 0.8.4")
43
43
  spec.add_dependency("thor", "~> 1.3")
44
44
  end
data/schema/workflow.json CHANGED
@@ -11,6 +11,18 @@
11
11
  "type": "string"
12
12
  }
13
13
  },
14
+ "target": {
15
+ "type": "string",
16
+ "description": "Optional target file, glob pattern, or shell command for the workflow to operate on"
17
+ },
18
+ "api_token": {
19
+ "type": "string",
20
+ "description": "Shell command to fetch an API token dynamically, e.g. $(cat ~/.my-token)"
21
+ },
22
+ "model": {
23
+ "type": "string",
24
+ "description": "Default AI model to use for all steps in the workflow"
25
+ },
14
26
  "inputs": {
15
27
  "type": "array",
16
28
  "items": {
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.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
@@ -55,16 +55,16 @@ dependencies:
55
55
  name: raix
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - '='
58
+ - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: 0.8.3
60
+ version: 0.8.4
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - '='
65
+ - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: 0.8.3
67
+ version: 0.8.4
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: thor
70
70
  requirement: !ruby/object:Gem::Requirement
@@ -92,7 +92,6 @@ files:
92
92
  - ".github/workflows/ci.yaml"
93
93
  - ".github/workflows/cla.yml"
94
94
  - ".gitignore"
95
- - ".rspec"
96
95
  - ".rubocop.yml"
97
96
  - ".ruby-version"
98
97
  - CHANGELOG.md
@@ -105,12 +104,22 @@ files:
105
104
  - README.md
106
105
  - Rakefile
107
106
  - bin/console
107
+ - docs/INSTRUMENTATION.md
108
+ - examples/api_workflow/README.md
109
+ - examples/api_workflow/fetch_api_data/prompt.md
110
+ - examples/api_workflow/generate_report/prompt.md
111
+ - examples/api_workflow/prompt.md
112
+ - examples/api_workflow/transform_data/prompt.md
113
+ - examples/api_workflow/workflow.yml
108
114
  - examples/grading/analyze_coverage/prompt.md
109
115
  - examples/grading/calculate_final_grade.rb
110
116
  - examples/grading/format_result.rb
111
117
  - examples/grading/generate_grades/prompt.md
112
118
  - examples/grading/generate_recommendations/output.txt
113
119
  - examples/grading/generate_recommendations/prompt.md
120
+ - examples/grading/js_test_runner
121
+ - examples/grading/rb_test_runner
122
+ - examples/grading/read_dependencies/prompt.md
114
123
  - examples/grading/run_coverage.rb
115
124
  - examples/grading/verify_mocks_and_stubs/prompt.md
116
125
  - examples/grading/verify_test_helpers/prompt.md
@@ -118,6 +127,13 @@ files:
118
127
  - examples/grading/workflow.rb.md
119
128
  - examples/grading/workflow.ts+tsx.md
120
129
  - examples/grading/workflow.yml
130
+ - examples/instrumentation.rb
131
+ - examples/rspec_to_minitest/README.md
132
+ - examples/rspec_to_minitest/analyze_spec/prompt.md
133
+ - examples/rspec_to_minitest/create_minitest/prompt.md
134
+ - examples/rspec_to_minitest/run_and_improve/prompt.md
135
+ - examples/rspec_to_minitest/workflow.md
136
+ - examples/rspec_to_minitest/workflow.yml
121
137
  - exe/roast
122
138
  - lib/roast.rb
123
139
  - lib/roast/helpers.rb
@@ -126,8 +142,16 @@ files:
126
142
  - lib/roast/helpers/minitest_coverage_runner.rb
127
143
  - lib/roast/helpers/path_resolver.rb
128
144
  - lib/roast/helpers/prompt_loader.rb
145
+ - lib/roast/resources.rb
146
+ - lib/roast/resources/api_resource.rb
147
+ - lib/roast/resources/base_resource.rb
148
+ - lib/roast/resources/directory_resource.rb
149
+ - lib/roast/resources/file_resource.rb
150
+ - lib/roast/resources/none_resource.rb
151
+ - lib/roast/resources/url_resource.rb
129
152
  - lib/roast/tools.rb
130
153
  - lib/roast/tools/cmd.rb
154
+ - lib/roast/tools/coding_agent.rb
131
155
  - lib/roast/tools/grep.rb
132
156
  - lib/roast/tools/read_file.rb
133
157
  - lib/roast/tools/search_file.rb
@@ -138,6 +162,10 @@ files:
138
162
  - lib/roast/workflow/base_workflow.rb
139
163
  - lib/roast/workflow/configuration.rb
140
164
  - lib/roast/workflow/configuration_parser.rb
165
+ - lib/roast/workflow/file_state_repository.rb
166
+ - lib/roast/workflow/prompt_step.rb
167
+ - lib/roast/workflow/session_manager.rb
168
+ - lib/roast/workflow/state_repository.rb
141
169
  - lib/roast/workflow/validator.rb
142
170
  - lib/roast/workflow/workflow_executor.rb
143
171
  - roast.gemspec
data/.rspec DELETED
@@ -1 +0,0 @@
1
- --require spec_helper