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.
- checksums.yaml +4 -4
- data/.github/workflows/cla.yml +1 -1
- data/.gitignore +1 -0
- data/CHANGELOG.md +28 -0
- data/CLAUDE.md +3 -1
- data/Gemfile +0 -1
- data/Gemfile.lock +3 -4
- data/README.md +419 -5
- data/Rakefile +1 -6
- data/docs/INSTRUMENTATION.md +202 -0
- data/examples/api_workflow/README.md +85 -0
- data/examples/api_workflow/fetch_api_data/prompt.md +10 -0
- data/examples/api_workflow/generate_report/prompt.md +10 -0
- data/examples/api_workflow/prompt.md +10 -0
- data/examples/api_workflow/transform_data/prompt.md +10 -0
- data/examples/api_workflow/workflow.yml +30 -0
- data/examples/grading/format_result.rb +25 -9
- data/examples/grading/js_test_runner +31 -0
- data/examples/grading/rb_test_runner +19 -0
- data/examples/grading/read_dependencies/prompt.md +14 -0
- data/examples/grading/run_coverage.rb +2 -2
- data/examples/grading/workflow.yml +3 -12
- data/examples/instrumentation.rb +76 -0
- data/examples/rspec_to_minitest/README.md +68 -0
- data/examples/rspec_to_minitest/analyze_spec/prompt.md +30 -0
- data/examples/rspec_to_minitest/create_minitest/prompt.md +33 -0
- data/examples/rspec_to_minitest/run_and_improve/prompt.md +35 -0
- data/examples/rspec_to_minitest/workflow.md +10 -0
- data/examples/rspec_to_minitest/workflow.yml +40 -0
- data/lib/roast/helpers/function_caching_interceptor.rb +72 -8
- data/lib/roast/helpers/prompt_loader.rb +2 -0
- data/lib/roast/resources/api_resource.rb +137 -0
- data/lib/roast/resources/base_resource.rb +47 -0
- data/lib/roast/resources/directory_resource.rb +40 -0
- data/lib/roast/resources/file_resource.rb +33 -0
- data/lib/roast/resources/none_resource.rb +29 -0
- data/lib/roast/resources/url_resource.rb +63 -0
- data/lib/roast/resources.rb +100 -0
- data/lib/roast/tools/coding_agent.rb +69 -0
- data/lib/roast/tools.rb +1 -0
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/base_step.rb +21 -17
- data/lib/roast/workflow/base_workflow.rb +69 -17
- data/lib/roast/workflow/configuration.rb +83 -8
- data/lib/roast/workflow/configuration_parser.rb +218 -3
- data/lib/roast/workflow/file_state_repository.rb +156 -0
- data/lib/roast/workflow/prompt_step.rb +16 -0
- data/lib/roast/workflow/session_manager.rb +82 -0
- data/lib/roast/workflow/state_repository.rb +21 -0
- data/lib/roast/workflow/workflow_executor.rb +99 -9
- data/lib/roast/workflow.rb +4 -0
- data/lib/roast.rb +2 -5
- data/roast.gemspec +1 -1
- data/schema/workflow.json +12 -0
- metadata +34 -6
- 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
|
-
|
34
|
-
|
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
|
-
|
66
|
+
step_result
|
67
|
+
end
|
68
|
+
|
69
|
+
execution_time = Time.now - start_time
|
37
70
|
|
38
|
-
|
39
|
-
|
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
|
-
|
115
|
-
|
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
|
data/lib/roast/workflow.rb
CHANGED
@@ -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 :
|
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.
|
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.
|
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.
|
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.
|
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
|