roast-ai 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +32 -0
- data/CLAUDE.md +52 -1
- data/Gemfile +3 -1
- data/Gemfile.lock +63 -16
- data/README.md +115 -5
- data/bin/roast +1 -1
- data/claude-swarm.yml +210 -0
- data/docs/AGENT_STEPS.md +264 -0
- data/examples/agent_workflow/README.md +75 -0
- data/examples/agent_workflow/apply_refactorings/prompt.md +22 -0
- data/examples/agent_workflow/identify_code_smells/prompt.md +15 -0
- data/examples/agent_workflow/summarize_improvements/prompt.md +18 -0
- data/examples/agent_workflow/workflow.yml +16 -0
- data/examples/available_tools_demo/README.md +42 -0
- data/examples/available_tools_demo/analyze_files/prompt.md +6 -0
- data/examples/available_tools_demo/explore_directory/prompt.md +6 -0
- data/examples/available_tools_demo/workflow.yml +32 -0
- data/examples/available_tools_demo/write_summary/prompt.md +6 -0
- data/examples/case_when/detect_language/prompt.md +2 -2
- data/examples/grading/README.md +71 -0
- data/examples/grading/run_coverage.rb +0 -2
- data/examples/iteration/analyze_complexity/prompt.md +2 -2
- data/examples/iteration/generate_recommendations/prompt.md +2 -2
- data/examples/iteration/implement_fix/prompt.md +2 -2
- data/examples/iteration/prioritize_issues/prompt.md +1 -1
- data/examples/iteration/prompts/analyze_file.md +2 -2
- data/examples/iteration/prompts/generate_summary.md +1 -1
- data/examples/iteration/prompts/update_report.md +3 -3
- data/examples/iteration/prompts/write_report.md +3 -3
- data/examples/iteration/read_file/prompt.md +2 -2
- data/examples/iteration/select_next_issue/prompt.md +2 -2
- data/examples/iteration/update_fix_count/prompt.md +4 -4
- data/examples/iteration/verify_fix/prompt.md +3 -3
- data/examples/mcp/README.md +3 -3
- data/examples/mcp/analyze_changes/prompt.md +1 -1
- data/examples/mcp/database_workflow.yml +1 -1
- data/examples/mcp/fetch_pr_context/prompt.md +1 -1
- data/examples/mcp/github_workflow.yml +1 -1
- data/examples/mcp/post_review/prompt.md +1 -1
- data/examples/pre_post_processing/analyze_test_file/prompt.md +1 -1
- data/examples/pre_post_processing/improve_test_coverage/prompt.md +1 -1
- data/examples/pre_post_processing/optimize_test_performance/prompt.md +1 -1
- data/examples/pre_post_processing/post_processing/aggregate_metrics/prompt.md +2 -2
- data/examples/pre_post_processing/post_processing/generate_summary_report/prompt.md +1 -1
- data/examples/pre_post_processing/pre_processing/setup_test_environment/prompt.md +1 -1
- data/examples/pre_post_processing/validate_changes/prompt.md +2 -2
- data/examples/user_input/README.md +90 -0
- data/examples/user_input/funny_name/create_backstory/prompt.md +10 -0
- data/examples/user_input/funny_name/workflow.yml +26 -0
- data/examples/user_input/generate_summary/prompt.md +11 -0
- data/examples/user_input/simple_input_demo/workflow.yml +35 -0
- data/examples/user_input/survey_workflow.yml +71 -0
- data/examples/user_input/welcome_message/prompt.md +3 -0
- data/examples/user_input/workflow.yml +73 -0
- data/examples/workflow_generator/create_workflow_files/prompt.md +1 -1
- data/lib/roast/errors.rb +6 -4
- data/lib/roast/helpers/function_caching_interceptor.rb +0 -2
- data/lib/roast/helpers/logger.rb +12 -35
- data/lib/roast/helpers/minitest_coverage_runner.rb +0 -1
- data/lib/roast/helpers/prompt_loader.rb +0 -2
- data/lib/roast/resources/api_resource.rb +0 -4
- data/lib/roast/resources/url_resource.rb +0 -3
- data/lib/roast/resources.rb +0 -8
- data/lib/roast/tools/ask_user.rb +0 -2
- data/lib/roast/tools/bash.rb +0 -3
- data/lib/roast/tools/cmd.rb +0 -3
- data/lib/roast/tools/coding_agent.rb +1 -8
- data/lib/roast/tools/grep.rb +0 -3
- data/lib/roast/tools/helpers/coding_agent_message_formatter.rb +1 -4
- data/lib/roast/tools/read_file.rb +0 -2
- data/lib/roast/tools/search_file.rb +0 -2
- data/lib/roast/tools/update_files.rb +0 -4
- data/lib/roast/tools/write_file.rb +0 -3
- data/lib/roast/tools.rb +0 -13
- data/lib/roast/value_objects/step_name.rb +14 -3
- data/lib/roast/value_objects/workflow_path.rb +0 -2
- data/lib/roast/value_objects.rb +4 -4
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/agent_step.rb +26 -0
- data/lib/roast/workflow/api_configuration.rb +0 -4
- data/lib/roast/workflow/base_iteration_step.rb +0 -4
- data/lib/roast/workflow/base_step.rb +54 -28
- data/lib/roast/workflow/base_workflow.rb +2 -21
- data/lib/roast/workflow/case_executor.rb +0 -1
- data/lib/roast/workflow/case_step.rb +0 -4
- data/lib/roast/workflow/command_executor.rb +0 -2
- data/lib/roast/workflow/conditional_executor.rb +0 -1
- data/lib/roast/workflow/conditional_step.rb +0 -4
- data/lib/roast/workflow/configuration.rb +3 -10
- data/lib/roast/workflow/configuration_loader.rb +0 -2
- data/lib/roast/workflow/configuration_parser.rb +1 -7
- data/lib/roast/workflow/dot_access_hash.rb +16 -1
- data/lib/roast/workflow/error_handler.rb +0 -3
- data/lib/roast/workflow/expression_evaluator.rb +0 -3
- data/lib/roast/workflow/file_state_repository.rb +0 -5
- data/lib/roast/workflow/input_executor.rb +41 -0
- data/lib/roast/workflow/input_step.rb +163 -0
- data/lib/roast/workflow/iteration_executor.rb +0 -2
- data/lib/roast/workflow/output_handler.rb +0 -2
- data/lib/roast/workflow/output_manager.rb +0 -2
- data/lib/roast/workflow/prompt_step.rb +1 -1
- data/lib/roast/workflow/replay_handler.rb +0 -3
- data/lib/roast/workflow/resource_resolver.rb +0 -3
- data/lib/roast/workflow/session_manager.rb +0 -3
- data/lib/roast/workflow/state_manager.rb +0 -2
- data/lib/roast/workflow/step_executor_coordinator.rb +34 -11
- data/lib/roast/workflow/step_executor_factory.rb +0 -5
- data/lib/roast/workflow/step_executor_registry.rb +1 -4
- data/lib/roast/workflow/step_executors/hash_step_executor.rb +0 -3
- data/lib/roast/workflow/step_executors/parallel_step_executor.rb +0 -3
- data/lib/roast/workflow/step_executors/string_step_executor.rb +0 -2
- data/lib/roast/workflow/step_factory.rb +56 -0
- data/lib/roast/workflow/step_loader.rb +30 -16
- data/lib/roast/workflow/step_orchestrator.rb +3 -2
- data/lib/roast/workflow/step_type_resolver.rb +28 -1
- data/lib/roast/workflow/validator.rb +0 -4
- data/lib/roast/workflow/workflow_executor.rb +0 -16
- data/lib/roast/workflow/workflow_initializer.rb +1 -8
- data/lib/roast/workflow/workflow_runner.rb +0 -7
- data/lib/roast/workflow.rb +0 -15
- data/lib/roast.rb +55 -10
- data/roast.gemspec +2 -1
- data/schema/workflow.json +46 -0
- metadata +44 -6
- data/lib/roast/helpers.rb +0 -12
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "roast/workflow/case_executor"
|
4
|
-
require "roast/workflow/conditional_executor"
|
5
|
-
require "roast/workflow/step_executor_factory"
|
6
|
-
require "roast/workflow/step_type_resolver"
|
7
|
-
|
8
3
|
module Roast
|
9
4
|
module Workflow
|
10
5
|
# Coordinates the execution of different types of steps
|
@@ -29,20 +24,21 @@ module Roast
|
|
29
24
|
|
30
25
|
# Execute a list of steps
|
31
26
|
def execute_steps(workflow_steps)
|
32
|
-
workflow_steps.
|
27
|
+
workflow_steps.each_with_index do |step, index|
|
28
|
+
is_last_step = (index == workflow_steps.length - 1)
|
33
29
|
case step
|
34
30
|
when Hash
|
35
|
-
execute(step)
|
31
|
+
execute(step, is_last_step: is_last_step)
|
36
32
|
when Array
|
37
|
-
execute(step)
|
33
|
+
execute(step, is_last_step: is_last_step)
|
38
34
|
when String
|
39
|
-
execute(step)
|
35
|
+
execute(step, is_last_step: is_last_step)
|
40
36
|
# Handle pause after string steps
|
41
37
|
if @context.workflow.pause_step_name == step
|
42
38
|
Kernel.binding.irb # rubocop:disable Lint/Debugger
|
43
39
|
end
|
44
40
|
else
|
45
|
-
step_orchestrator.execute_step(step)
|
41
|
+
step_orchestrator.execute_step(step, is_last_step: is_last_step)
|
46
42
|
end
|
47
43
|
end
|
48
44
|
end
|
@@ -63,6 +59,8 @@ module Roast
|
|
63
59
|
when StepTypeResolver::COMMAND_STEP
|
64
60
|
# Command steps should also go through interpolation
|
65
61
|
execute_string_step(step, options)
|
62
|
+
when StepTypeResolver::AGENT_STEP
|
63
|
+
execute_agent_step(step, options)
|
66
64
|
when StepTypeResolver::GLOB_STEP
|
67
65
|
execute_glob_step(step)
|
68
66
|
when StepTypeResolver::ITERATION_STEP
|
@@ -71,6 +69,8 @@ module Roast
|
|
71
69
|
execute_conditional_step(step)
|
72
70
|
when StepTypeResolver::CASE_STEP
|
73
71
|
execute_case_step(step)
|
72
|
+
when StepTypeResolver::INPUT_STEP
|
73
|
+
execute_input_step(step)
|
74
74
|
when StepTypeResolver::HASH_STEP
|
75
75
|
execute_hash_step(step)
|
76
76
|
when StepTypeResolver::PARALLEL_STEP
|
@@ -117,6 +117,15 @@ module Roast
|
|
117
117
|
)
|
118
118
|
end
|
119
119
|
|
120
|
+
def input_executor
|
121
|
+
@input_executor ||= dependencies[:input_executor] || InputExecutor.new(
|
122
|
+
context.workflow,
|
123
|
+
context.context_path,
|
124
|
+
dependencies[:state_manager] || dependencies[:workflow_executor].state_manager,
|
125
|
+
workflow_executor,
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
120
129
|
def step_orchestrator
|
121
130
|
dependencies[:step_orchestrator]
|
122
131
|
end
|
@@ -171,6 +180,15 @@ module Roast
|
|
171
180
|
end
|
172
181
|
end
|
173
182
|
|
183
|
+
def execute_agent_step(step, options = {})
|
184
|
+
# Extract the step name without the ^ prefix
|
185
|
+
step_name = StepTypeResolver.extract_name(step)
|
186
|
+
|
187
|
+
# Load and execute the agent step
|
188
|
+
exit_on_error = options.fetch(:exit_on_error, context.exit_on_error?(step))
|
189
|
+
step_orchestrator.execute_step(step_name, exit_on_error:, step_key: options[:step_key], agent_type: :coding_agent)
|
190
|
+
end
|
191
|
+
|
174
192
|
def execute_glob_step(step)
|
175
193
|
Dir.glob(step).join("\n")
|
176
194
|
end
|
@@ -196,6 +214,10 @@ module Roast
|
|
196
214
|
case_executor.execute_case(step)
|
197
215
|
end
|
198
216
|
|
217
|
+
def execute_input_step(step)
|
218
|
+
input_executor.execute_input(step["input"])
|
219
|
+
end
|
220
|
+
|
199
221
|
def execute_hash_step(step)
|
200
222
|
name, command = step.to_a.flatten
|
201
223
|
interpolated_name = interpolator.interpolate(name)
|
@@ -235,7 +257,8 @@ module Roast
|
|
235
257
|
def execute_standard_step(step, options)
|
236
258
|
exit_on_error = options.fetch(:exit_on_error, true)
|
237
259
|
step_key = options[:step_key]
|
238
|
-
|
260
|
+
is_last_step = options[:is_last_step]
|
261
|
+
step_orchestrator.execute_step(step, exit_on_error:, step_key:, is_last_step:)
|
239
262
|
end
|
240
263
|
|
241
264
|
def validate_each_step!(step)
|
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "roast/workflow/step_executor_registry"
|
4
|
-
require "roast/workflow/step_executors/hash_step_executor"
|
5
|
-
require "roast/workflow/step_executors/parallel_step_executor"
|
6
|
-
require "roast/workflow/step_executors/string_step_executor"
|
7
|
-
|
8
3
|
module Roast
|
9
4
|
module Workflow
|
10
5
|
# Factory for creating step executors - now delegates to registry
|
@@ -43,10 +43,7 @@ module Roast
|
|
43
43
|
def clear!
|
44
44
|
@executors.clear
|
45
45
|
@type_matchers.clear
|
46
|
-
|
47
|
-
if defined?(StepExecutorFactory)
|
48
|
-
StepExecutorFactory.instance_variable_set(:@defaults_registered, false)
|
49
|
-
end
|
46
|
+
StepExecutorFactory.instance_variable_set(:@defaults_registered, false)
|
50
47
|
end
|
51
48
|
|
52
49
|
# Get all registered executors (useful for debugging)
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Roast
|
4
|
+
module Workflow
|
5
|
+
# Factory for creating step instances based on step characteristics
|
6
|
+
class StepFactory
|
7
|
+
class << self
|
8
|
+
# Create a step instance based on the step type and characteristics
|
9
|
+
#
|
10
|
+
# @param workflow [BaseWorkflow] The workflow instance
|
11
|
+
# @param step_name [String, StepName] The name of the step
|
12
|
+
# @param options [Hash] Additional options for step creation
|
13
|
+
# @return [BaseStep] The appropriate step instance
|
14
|
+
def create(workflow, step_name, options = {})
|
15
|
+
name = normalize_step_name(step_name)
|
16
|
+
|
17
|
+
# Determine the step class based on characteristics
|
18
|
+
step_class = determine_step_class(name, options)
|
19
|
+
|
20
|
+
# Create the step instance with appropriate parameters
|
21
|
+
build_step_instance(step_class, workflow, name, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def normalize_step_name(step_name)
|
27
|
+
step_name.is_a?(Roast::ValueObjects::StepName) ? step_name : Roast::ValueObjects::StepName.new(step_name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def determine_step_class(name, options)
|
31
|
+
# Check if this is an agent step (indicated by special processing needs)
|
32
|
+
if options[:agent_type] == :coding_agent
|
33
|
+
Roast::Workflow::AgentStep
|
34
|
+
elsif name.plain_text?
|
35
|
+
# Plain text steps are always prompt steps
|
36
|
+
options[:agent_type] == :coding_agent ? Roast::Workflow::AgentStep : Roast::Workflow::PromptStep
|
37
|
+
else
|
38
|
+
# Default to BaseStep for directory-based steps
|
39
|
+
Roast::Workflow::BaseStep
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_step_instance(step_class, workflow, name, options)
|
44
|
+
step_params = {
|
45
|
+
name:,
|
46
|
+
}
|
47
|
+
|
48
|
+
# Add context path if provided
|
49
|
+
step_params[:context_path] = options[:context_path] if options[:context_path]
|
50
|
+
|
51
|
+
step_class.new(workflow, **step_params)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "roast/value_objects/step_name"
|
4
|
-
require "roast/workflow/workflow_context"
|
5
|
-
require "roast/workflow/base_step"
|
6
|
-
require "roast/workflow/prompt_step"
|
7
|
-
|
8
3
|
module Roast
|
9
4
|
module Workflow
|
10
5
|
# Handles loading and instantiation of workflow steps
|
@@ -47,8 +42,9 @@ module Roast
|
|
47
42
|
#
|
48
43
|
# @param step_name [String, StepName] The name of the step to load
|
49
44
|
# @param step_key [String] The configuration key for the step (optional)
|
45
|
+
# @param options [Hash] Additional options for step loading
|
50
46
|
# @return [BaseStep] The loaded step instance
|
51
|
-
def load(step_name, step_key: nil)
|
47
|
+
def load(step_name, step_key: nil, is_last_step: nil, **options)
|
52
48
|
name = step_name.is_a?(Roast::ValueObjects::StepName) ? step_name : Roast::ValueObjects::StepName.new(step_name)
|
53
49
|
|
54
50
|
# Get step config for per-step path
|
@@ -57,17 +53,17 @@ module Roast
|
|
57
53
|
|
58
54
|
# First check for a prompt step (contains spaces)
|
59
55
|
if name.plain_text?
|
60
|
-
step =
|
56
|
+
step = StepFactory.create(workflow, name, options)
|
61
57
|
# Use step_key for configuration if provided, otherwise use name
|
62
58
|
config_key = step_key || name.to_s
|
63
|
-
configure_step(step, config_key)
|
59
|
+
configure_step(step, config_key, is_last_step:)
|
64
60
|
return step
|
65
61
|
end
|
66
62
|
|
67
63
|
# Look for Ruby file in various locations
|
68
64
|
step_file_path = find_step_file(name.to_s, per_step_path)
|
69
65
|
if step_file_path
|
70
|
-
return load_ruby_step(step_file_path, name.to_s)
|
66
|
+
return load_ruby_step(step_file_path, name.to_s, is_last_step:)
|
71
67
|
end
|
72
68
|
|
73
69
|
# Look for step directory
|
@@ -76,7 +72,10 @@ module Roast
|
|
76
72
|
raise StepNotFoundError.new("Step directory or file not found: #{name}", step_name: name.to_s)
|
77
73
|
end
|
78
74
|
|
79
|
-
|
75
|
+
# Use factory to create the appropriate step instance
|
76
|
+
step = StepFactory.create(workflow, name, options.merge(context_path: step_directory))
|
77
|
+
configure_step(step, name.to_s, is_last_step:)
|
78
|
+
step
|
80
79
|
end
|
81
80
|
|
82
81
|
private
|
@@ -141,7 +140,7 @@ module Roast
|
|
141
140
|
end
|
142
141
|
|
143
142
|
# Load a Ruby step from a file
|
144
|
-
def load_ruby_step(file_path, step_name)
|
143
|
+
def load_ruby_step(file_path, step_name, is_last_step: nil)
|
145
144
|
$stderr.puts "Requiring step file: #{file_path}"
|
146
145
|
|
147
146
|
begin
|
@@ -154,18 +153,24 @@ module Roast
|
|
154
153
|
|
155
154
|
step_class = step_name.classify.constantize
|
156
155
|
context = File.dirname(file_path)
|
157
|
-
|
156
|
+
# For Ruby steps, we instantiate the specific class directly
|
157
|
+
# Convert step_name to StepName value object
|
158
|
+
step_name_obj = Roast::ValueObjects::StepName.new(step_name)
|
159
|
+
step = step_class.new(workflow, name: step_name_obj, context_path: context)
|
160
|
+
configure_step(step, step_name, is_last_step:)
|
161
|
+
step
|
158
162
|
end
|
159
163
|
|
160
164
|
# Create and configure a step instance
|
161
|
-
def create_step_instance(step_class, step_name, context_path)
|
162
|
-
|
163
|
-
|
165
|
+
def create_step_instance(step_class, step_name, context_path, options = {})
|
166
|
+
is_last_step = options[:is_last_step]
|
167
|
+
step = StepFactory.create(workflow, step_name, options.merge(context_path: context_path))
|
168
|
+
configure_step(step, step_name, is_last_step:)
|
164
169
|
step
|
165
170
|
end
|
166
171
|
|
167
172
|
# Configure a step instance with settings from config_hash
|
168
|
-
def configure_step(step, step_name)
|
173
|
+
def configure_step(step, step_name, is_last_step: nil)
|
169
174
|
step_config = config_hash[step_name]
|
170
175
|
|
171
176
|
# Always set the model
|
@@ -176,6 +181,11 @@ module Roast
|
|
176
181
|
|
177
182
|
# Apply additional configuration if present
|
178
183
|
apply_step_configuration(step, step_config) if step_config.present?
|
184
|
+
|
185
|
+
# Set print_response to true for the last step if not already configured
|
186
|
+
if is_last_step && !step_config&.key?("print_response")
|
187
|
+
step.print_response = true
|
188
|
+
end
|
179
189
|
end
|
180
190
|
|
181
191
|
# Determine which model to use for the step
|
@@ -189,6 +199,10 @@ module Roast
|
|
189
199
|
step.json = step_config["json"] if step_config.key?("json")
|
190
200
|
step.params = step_config["params"] if step_config.key?("params")
|
191
201
|
step.coerce_to = step_config["coerce_to"].to_sym if step_config.key?("coerce_to")
|
202
|
+
|
203
|
+
if step_config.key?("available_tools")
|
204
|
+
step.available_tools = step_config["available_tools"]
|
205
|
+
end
|
192
206
|
end
|
193
207
|
end
|
194
208
|
end
|
@@ -22,7 +22,7 @@ module Roast
|
|
22
22
|
@workflow_executor = workflow_executor
|
23
23
|
end
|
24
24
|
|
25
|
-
def execute_step(name, exit_on_error: true, step_key: nil)
|
25
|
+
def execute_step(name, exit_on_error: true, step_key: nil, **options)
|
26
26
|
resource_type = @workflow.respond_to?(:resource) ? @workflow.resource&.type : nil
|
27
27
|
|
28
28
|
@error_handler.with_error_handling(name, resource_type: resource_type) do
|
@@ -30,7 +30,8 @@ module Roast
|
|
30
30
|
|
31
31
|
# Use step_key for loading if provided, otherwise use name
|
32
32
|
load_key = step_key || name
|
33
|
-
|
33
|
+
is_last_step = options[:is_last_step]
|
34
|
+
step_object = @step_loader.load(name, step_key: load_key, is_last_step:, **options)
|
34
35
|
step_result = step_object.call
|
35
36
|
|
36
37
|
# Store result in workflow output
|
@@ -10,10 +10,12 @@ module Roast
|
|
10
10
|
ITERATION_STEP = :iteration
|
11
11
|
CONDITIONAL_STEP = :conditional
|
12
12
|
CASE_STEP = :case
|
13
|
+
INPUT_STEP = :input
|
13
14
|
HASH_STEP = :hash
|
14
15
|
PARALLEL_STEP = :parallel
|
15
16
|
STRING_STEP = :string
|
16
17
|
STANDARD_STEP = :standard
|
18
|
+
AGENT_STEP = :agent
|
17
19
|
|
18
20
|
# Special step names for iterations
|
19
21
|
ITERATION_STEPS = ["repeat", "each"].freeze
|
@@ -24,6 +26,9 @@ module Roast
|
|
24
26
|
# Special step name for case statements
|
25
27
|
CASE_STEPS = ["case"].freeze
|
26
28
|
|
29
|
+
# Special step name for input steps
|
30
|
+
INPUT_STEPS = ["input"].freeze
|
31
|
+
|
27
32
|
class << self
|
28
33
|
# Resolve the type of a step
|
29
34
|
# @param step [String, Hash, Array] The step to analyze
|
@@ -49,6 +54,13 @@ module Roast
|
|
49
54
|
step.is_a?(String) && step.start_with?("$(")
|
50
55
|
end
|
51
56
|
|
57
|
+
# Check if a step is an agent step
|
58
|
+
# @param step [String] The step to check
|
59
|
+
# @return [Boolean] true if it's an agent step
|
60
|
+
def agent_step?(step)
|
61
|
+
step.is_a?(String) && step.start_with?("^")
|
62
|
+
end
|
63
|
+
|
52
64
|
# Check if a step is a glob pattern
|
53
65
|
# @param step [String] The step to check
|
54
66
|
# @param context [WorkflowContext, nil] The workflow context
|
@@ -90,13 +102,24 @@ module Roast
|
|
90
102
|
CASE_STEPS.include?(step_name)
|
91
103
|
end
|
92
104
|
|
105
|
+
# Check if a step is an input step
|
106
|
+
# @param step [Hash] The step to check
|
107
|
+
# @return [Boolean] true if it's an input step
|
108
|
+
def input_step?(step)
|
109
|
+
return false unless step.is_a?(Hash)
|
110
|
+
|
111
|
+
step_name = step.keys.first
|
112
|
+
INPUT_STEPS.include?(step_name)
|
113
|
+
end
|
114
|
+
|
93
115
|
# Extract the step name from various step formats
|
94
116
|
# @param step [String, Hash, Array] The step
|
95
117
|
# @return [String, nil] The step name or nil
|
96
118
|
def extract_name(step)
|
97
119
|
case step
|
98
120
|
when String
|
99
|
-
|
121
|
+
# Strip ^ prefix for agent steps
|
122
|
+
agent_step?(step) ? step[1..] : step
|
100
123
|
when Hash
|
101
124
|
step.keys.first
|
102
125
|
when Array
|
@@ -109,6 +132,8 @@ module Roast
|
|
109
132
|
def resolve_string_step(step, context)
|
110
133
|
if command_step?(step)
|
111
134
|
COMMAND_STEP
|
135
|
+
elsif agent_step?(step)
|
136
|
+
AGENT_STEP
|
112
137
|
elsif glob_step?(step, context)
|
113
138
|
GLOB_STEP
|
114
139
|
else
|
@@ -123,6 +148,8 @@ module Roast
|
|
123
148
|
CONDITIONAL_STEP
|
124
149
|
elsif case_step?(step)
|
125
150
|
CASE_STEP
|
151
|
+
elsif input_step?(step)
|
152
|
+
INPUT_STEP
|
126
153
|
else
|
127
154
|
HASH_STEP
|
128
155
|
end
|
@@ -1,21 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "English"
|
4
|
-
|
5
|
-
require "roast/workflow/command_executor"
|
6
|
-
require "roast/workflow/conditional_executor"
|
7
|
-
require "roast/workflow/error_handler"
|
8
|
-
require "roast/workflow/interpolator"
|
9
|
-
require "roast/workflow/iteration_executor"
|
10
|
-
require "roast/workflow/parallel_executor"
|
11
|
-
require "roast/workflow/state_manager"
|
12
|
-
require "roast/workflow/step_executor_factory"
|
13
|
-
require "roast/workflow/step_executor_coordinator"
|
14
|
-
require "roast/workflow/step_loader"
|
15
|
-
require "roast/workflow/step_orchestrator"
|
16
|
-
require "roast/workflow/step_type_resolver"
|
17
|
-
require "roast/workflow/workflow_context"
|
18
|
-
|
19
3
|
module Roast
|
20
4
|
module Workflow
|
21
5
|
# Handles the execution of workflow steps, including orchestration and threading
|
@@ -1,12 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "raix"
|
4
|
-
require "roast/initializers"
|
5
|
-
require "roast/helpers/function_caching_interceptor"
|
6
|
-
require "roast/helpers/logger"
|
7
|
-
require "roast/workflow/base_workflow"
|
8
|
-
require "roast/workflow/interpolator"
|
9
|
-
|
10
3
|
module Roast
|
11
4
|
module Workflow
|
12
5
|
# Handles initialization of workflow dependencies: initializers, tools, and API clients
|
@@ -103,7 +96,7 @@ module Roast
|
|
103
96
|
# Validate the client configuration by making a test API call
|
104
97
|
validate_api_client(client) if client
|
105
98
|
rescue OpenRouter::ConfigurationError, Faraday::UnauthorizedError => e
|
106
|
-
error = Roast::AuthenticationError.new("API authentication failed: No API token provided or token is invalid")
|
99
|
+
error = Roast::Errors::AuthenticationError.new("API authentication failed: No API token provided or token is invalid")
|
107
100
|
error.set_backtrace(e.backtrace)
|
108
101
|
|
109
102
|
ActiveSupport::Notifications.instrument("roast.workflow.start.error", {
|
@@ -1,12 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "erb"
|
4
|
-
require "roast/workflow/replay_handler"
|
5
|
-
require "roast/workflow/workflow_executor"
|
6
|
-
require "roast/workflow/output_handler"
|
7
|
-
require "roast/workflow/base_workflow"
|
8
|
-
require "roast/workflow/dot_access_hash"
|
9
|
-
|
10
3
|
module Roast
|
11
4
|
module Workflow
|
12
5
|
# Handles running workflows for files/targets and orchestrating execution
|
data/lib/roast/workflow.rb
CHANGED
@@ -1,20 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "roast/workflow/base_step"
|
4
|
-
require "roast/workflow/prompt_step"
|
5
|
-
require "roast/workflow/base_iteration_step"
|
6
|
-
require "roast/workflow/repeat_step"
|
7
|
-
require "roast/workflow/each_step"
|
8
|
-
require "roast/workflow/base_workflow"
|
9
|
-
require "roast/workflow/configuration"
|
10
|
-
require "roast/workflow/workflow_execution_context"
|
11
|
-
require "roast/workflow/workflow_executor"
|
12
|
-
require "roast/workflow/configuration_parser"
|
13
|
-
require "roast/workflow/validator"
|
14
|
-
require "roast/workflow/state_repository"
|
15
|
-
require "roast/workflow/session_manager"
|
16
|
-
require "roast/workflow/file_state_repository"
|
17
|
-
|
18
3
|
module Roast
|
19
4
|
module Workflow
|
20
5
|
end
|
data/lib/roast.rb
CHANGED
@@ -1,24 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Standard library requires
|
4
|
+
require "digest"
|
5
|
+
require "English"
|
6
|
+
require "erb"
|
7
|
+
require "fileutils"
|
8
|
+
require "json"
|
9
|
+
require "logger"
|
10
|
+
require "net/http"
|
11
|
+
require "open3"
|
12
|
+
require "pathname"
|
13
|
+
require "securerandom"
|
14
|
+
require "tempfile"
|
15
|
+
require "uri"
|
16
|
+
require "yaml"
|
17
|
+
|
18
|
+
# Third-party gem requires
|
3
19
|
require "active_support"
|
4
20
|
require "active_support/cache"
|
5
|
-
require "active_support/notifications"
|
6
21
|
require "active_support/core_ext/hash/indifferent_access"
|
22
|
+
require "active_support/core_ext/module/delegation"
|
7
23
|
require "active_support/core_ext/string"
|
8
24
|
require "active_support/core_ext/string/inflections"
|
9
|
-
require "active_support/core_ext/module/delegation"
|
10
25
|
require "active_support/isolated_execution_state"
|
11
|
-
require "
|
26
|
+
require "active_support/notifications"
|
12
27
|
require "cli/ui"
|
28
|
+
require "diff/lcs"
|
29
|
+
require "json-schema"
|
13
30
|
require "raix"
|
31
|
+
require "raix/chat_completion"
|
32
|
+
require "raix/function_dispatch"
|
14
33
|
require "thor"
|
15
|
-
|
16
|
-
|
17
|
-
require "
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
34
|
+
|
35
|
+
# Autoloading setup
|
36
|
+
require "zeitwerk"
|
37
|
+
|
38
|
+
# Set up Zeitwerk autoloader
|
39
|
+
loader = Zeitwerk::Loader.for_gem
|
40
|
+
loader.setup
|
22
41
|
|
23
42
|
module Roast
|
24
43
|
ROOT = File.expand_path("../..", __FILE__)
|
@@ -63,6 +82,32 @@ module Roast
|
|
63
82
|
end
|
64
83
|
end
|
65
84
|
|
85
|
+
desc "list", "List workflows visible to Roast and their source"
|
86
|
+
def list
|
87
|
+
roast_dir = File.join(Dir.pwd, "roast")
|
88
|
+
|
89
|
+
unless File.directory?(roast_dir)
|
90
|
+
raise Thor::Error, "No roast/ directory found in current path"
|
91
|
+
end
|
92
|
+
|
93
|
+
workflow_files = Dir.glob(File.join(roast_dir, "**/workflow.yml")).sort
|
94
|
+
|
95
|
+
if workflow_files.empty?
|
96
|
+
raise Thor::Error, "No workflow.yml files found in roast/ directory"
|
97
|
+
end
|
98
|
+
|
99
|
+
puts "Available workflows:"
|
100
|
+
puts
|
101
|
+
|
102
|
+
workflow_files.each do |file|
|
103
|
+
workflow_name = File.dirname(file.sub("#{roast_dir}/", ""))
|
104
|
+
puts " #{workflow_name} (from project)"
|
105
|
+
end
|
106
|
+
|
107
|
+
puts
|
108
|
+
puts "Run a workflow with: roast execute <workflow_name>"
|
109
|
+
end
|
110
|
+
|
66
111
|
private
|
67
112
|
|
68
113
|
def show_example_picker
|
data/roast.gemspec
CHANGED
@@ -37,11 +37,12 @@ Gem::Specification.new do |spec|
|
|
37
37
|
spec.require_paths = ["lib"]
|
38
38
|
|
39
39
|
spec.add_dependency("activesupport", ">= 7.0")
|
40
|
-
spec.add_dependency("cli-ui")
|
40
|
+
spec.add_dependency("cli-ui", "2.3.0")
|
41
41
|
spec.add_dependency("diff-lcs", "~> 1.5")
|
42
42
|
spec.add_dependency("faraday-retry")
|
43
43
|
spec.add_dependency("json-schema")
|
44
44
|
spec.add_dependency("open_router", "~> 0.3")
|
45
45
|
spec.add_dependency("raix", "~> 1.0")
|
46
46
|
spec.add_dependency("thor", "~> 1.3")
|
47
|
+
spec.add_dependency("zeitwerk", "~> 2.6")
|
47
48
|
end
|
data/schema/workflow.json
CHANGED
@@ -206,6 +206,52 @@
|
|
206
206
|
}
|
207
207
|
},
|
208
208
|
"required": ["case", "when"]
|
209
|
+
},
|
210
|
+
{
|
211
|
+
"type": "object",
|
212
|
+
"properties": {
|
213
|
+
"input": {
|
214
|
+
"type": "object",
|
215
|
+
"properties": {
|
216
|
+
"prompt": {
|
217
|
+
"type": "string",
|
218
|
+
"description": "The prompt text to display to the user"
|
219
|
+
},
|
220
|
+
"name": {
|
221
|
+
"type": "string",
|
222
|
+
"description": "Optional name to store the input value in workflow state"
|
223
|
+
},
|
224
|
+
"type": {
|
225
|
+
"type": "string",
|
226
|
+
"enum": ["text", "boolean", "choice", "password"],
|
227
|
+
"default": "text",
|
228
|
+
"description": "The type of input to collect"
|
229
|
+
},
|
230
|
+
"required": {
|
231
|
+
"type": "boolean",
|
232
|
+
"default": false,
|
233
|
+
"description": "Whether the input is required"
|
234
|
+
},
|
235
|
+
"default": {
|
236
|
+
"description": "Default value for the input"
|
237
|
+
},
|
238
|
+
"timeout": {
|
239
|
+
"type": "number",
|
240
|
+
"description": "Optional timeout in seconds"
|
241
|
+
},
|
242
|
+
"options": {
|
243
|
+
"type": "array",
|
244
|
+
"items": {
|
245
|
+
"type": "string"
|
246
|
+
},
|
247
|
+
"description": "Options for choice type inputs"
|
248
|
+
}
|
249
|
+
},
|
250
|
+
"required": ["prompt"],
|
251
|
+
"additionalProperties": false
|
252
|
+
}
|
253
|
+
},
|
254
|
+
"required": ["input"]
|
209
255
|
}
|
210
256
|
]
|
211
257
|
}
|