roast-ai 0.1.7 → 0.2.1
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/ci.yaml +1 -1
- data/CHANGELOG.md +49 -1
- data/CLAUDE.md +20 -0
- data/CLAUDE_NOTES.md +68 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +9 -6
- data/README.md +159 -26
- data/bin/roast +27 -0
- data/docs/ITERATION_SYNTAX.md +147 -0
- data/examples/case_when/README.md +58 -0
- data/examples/case_when/detect_language/prompt.md +16 -0
- data/examples/case_when/workflow.yml +58 -0
- data/examples/conditional/README.md +161 -0
- data/examples/conditional/check_condition/prompt.md +1 -0
- data/examples/conditional/simple_workflow.yml +15 -0
- data/examples/conditional/workflow.yml +23 -0
- data/examples/direct_coerce_syntax/README.md +32 -0
- data/examples/direct_coerce_syntax/workflow.yml +36 -0
- data/examples/dot_notation/README.md +37 -0
- data/examples/dot_notation/workflow.yml +44 -0
- data/examples/exit_on_error/README.md +50 -0
- data/examples/exit_on_error/analyze_lint_output/prompt.md +9 -0
- data/examples/exit_on_error/apply_fixes/prompt.md +2 -0
- data/examples/exit_on_error/workflow.yml +19 -0
- data/examples/grading/workflow.yml +10 -4
- data/examples/iteration/IMPLEMENTATION.md +88 -0
- data/examples/iteration/README.md +68 -0
- data/examples/iteration/analyze_complexity/prompt.md +22 -0
- data/examples/iteration/generate_recommendations/prompt.md +21 -0
- data/examples/iteration/generate_report/prompt.md +129 -0
- data/examples/iteration/implement_fix/prompt.md +25 -0
- data/examples/iteration/prioritize_issues/prompt.md +24 -0
- data/examples/iteration/prompts/analyze_file.md +28 -0
- data/examples/iteration/prompts/generate_summary.md +24 -0
- data/examples/iteration/prompts/update_report.md +29 -0
- data/examples/iteration/prompts/write_report.md +22 -0
- data/examples/iteration/read_file/prompt.md +9 -0
- data/examples/iteration/select_next_issue/prompt.md +25 -0
- data/examples/iteration/simple_workflow.md +39 -0
- data/examples/iteration/simple_workflow.yml +58 -0
- data/examples/iteration/update_fix_count/prompt.md +26 -0
- data/examples/iteration/verify_fix/prompt.md +29 -0
- data/examples/iteration/workflow.yml +42 -0
- data/examples/json_handling/README.md +32 -0
- data/examples/json_handling/workflow.yml +52 -0
- data/examples/openrouter_example/workflow.yml +2 -2
- data/examples/smart_coercion_defaults/README.md +65 -0
- data/examples/smart_coercion_defaults/workflow.yml +44 -0
- data/examples/step_configuration/README.md +87 -0
- data/examples/step_configuration/workflow.yml +60 -0
- data/examples/workflow_generator/README.md +27 -0
- data/examples/workflow_generator/analyze_user_request/prompt.md +34 -0
- data/examples/workflow_generator/create_workflow_files/prompt.md +32 -0
- data/examples/workflow_generator/get_user_input/prompt.md +14 -0
- data/examples/workflow_generator/info_from_roast.rb +22 -0
- data/examples/workflow_generator/workflow.yml +35 -0
- data/lib/roast/errors.rb +9 -0
- data/lib/roast/factories/api_provider_factory.rb +61 -0
- data/lib/roast/helpers/function_caching_interceptor.rb +1 -1
- data/lib/roast/helpers/minitest_coverage_runner.rb +1 -1
- data/lib/roast/helpers/prompt_loader.rb +50 -1
- data/lib/roast/resources/base_resource.rb +7 -0
- data/lib/roast/resources.rb +6 -6
- data/lib/roast/tools/ask_user.rb +40 -0
- data/lib/roast/tools/cmd.rb +1 -1
- data/lib/roast/tools/search_file.rb +1 -1
- data/lib/roast/tools.rb +11 -1
- data/lib/roast/value_objects/api_token.rb +49 -0
- data/lib/roast/value_objects/step_name.rb +39 -0
- data/lib/roast/value_objects/workflow_path.rb +77 -0
- data/lib/roast/value_objects.rb +5 -0
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/api_configuration.rb +61 -0
- data/lib/roast/workflow/base_iteration_step.rb +184 -0
- data/lib/roast/workflow/base_step.rb +44 -27
- data/lib/roast/workflow/base_workflow.rb +76 -73
- data/lib/roast/workflow/case_executor.rb +49 -0
- data/lib/roast/workflow/case_step.rb +82 -0
- data/lib/roast/workflow/command_executor.rb +88 -0
- data/lib/roast/workflow/conditional_executor.rb +50 -0
- data/lib/roast/workflow/conditional_step.rb +59 -0
- data/lib/roast/workflow/configuration.rb +35 -158
- data/lib/roast/workflow/configuration_loader.rb +78 -0
- data/lib/roast/workflow/configuration_parser.rb +13 -248
- data/lib/roast/workflow/context_path_resolver.rb +43 -0
- data/lib/roast/workflow/dot_access_hash.rb +198 -0
- data/lib/roast/workflow/each_step.rb +86 -0
- data/lib/roast/workflow/error_handler.rb +97 -0
- data/lib/roast/workflow/expression_evaluator.rb +78 -0
- data/lib/roast/workflow/expression_utils.rb +36 -0
- data/lib/roast/workflow/file_state_repository.rb +3 -2
- data/lib/roast/workflow/interpolator.rb +34 -0
- data/lib/roast/workflow/iteration_executor.rb +103 -0
- data/lib/roast/workflow/llm_boolean_coercer.rb +55 -0
- data/lib/roast/workflow/output_handler.rb +35 -0
- data/lib/roast/workflow/output_manager.rb +77 -0
- data/lib/roast/workflow/parallel_executor.rb +49 -0
- data/lib/roast/workflow/prompt_step.rb +4 -1
- data/lib/roast/workflow/repeat_step.rb +75 -0
- data/lib/roast/workflow/replay_handler.rb +123 -0
- data/lib/roast/workflow/resource_resolver.rb +77 -0
- data/lib/roast/workflow/session_manager.rb +6 -2
- data/lib/roast/workflow/state_manager.rb +97 -0
- data/lib/roast/workflow/step_executor_coordinator.rb +221 -0
- data/lib/roast/workflow/step_executor_factory.rb +47 -0
- data/lib/roast/workflow/step_executor_registry.rb +79 -0
- data/lib/roast/workflow/step_executors/base_step_executor.rb +23 -0
- data/lib/roast/workflow/step_executors/hash_step_executor.rb +43 -0
- data/lib/roast/workflow/step_executors/parallel_step_executor.rb +54 -0
- data/lib/roast/workflow/step_executors/string_step_executor.rb +29 -0
- data/lib/roast/workflow/step_finder.rb +97 -0
- data/lib/roast/workflow/step_loader.rb +155 -0
- data/lib/roast/workflow/step_orchestrator.rb +45 -0
- data/lib/roast/workflow/step_runner.rb +23 -0
- data/lib/roast/workflow/step_type_resolver.rb +133 -0
- data/lib/roast/workflow/workflow_context.rb +60 -0
- data/lib/roast/workflow/workflow_executor.rb +90 -209
- data/lib/roast/workflow/workflow_initializer.rb +112 -0
- data/lib/roast/workflow/workflow_runner.rb +87 -0
- data/lib/roast/workflow.rb +3 -0
- data/lib/roast.rb +96 -3
- data/roast.gemspec +2 -1
- data/schema/workflow.json +112 -0
- metadata +112 -4
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "English"
|
4
|
+
|
5
|
+
module Roast
|
6
|
+
module Workflow
|
7
|
+
class CommandExecutor
|
8
|
+
class CommandExecutionError < StandardError
|
9
|
+
attr_reader :command, :exit_status, :original_error
|
10
|
+
|
11
|
+
def initialize(message, command:, exit_status: nil, original_error: nil)
|
12
|
+
@command = command
|
13
|
+
@exit_status = exit_status
|
14
|
+
@original_error = original_error
|
15
|
+
super(message)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(logger: nil)
|
20
|
+
@logger = logger || NullLogger.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute(command_string, exit_on_error: true)
|
24
|
+
command = extract_command(command_string)
|
25
|
+
|
26
|
+
output = %x(#{command})
|
27
|
+
exit_status = $CHILD_STATUS.exitstatus
|
28
|
+
|
29
|
+
handle_execution_result(
|
30
|
+
command: command,
|
31
|
+
output: output,
|
32
|
+
exit_status: exit_status,
|
33
|
+
success: $CHILD_STATUS.success?,
|
34
|
+
exit_on_error: exit_on_error,
|
35
|
+
)
|
36
|
+
rescue ArgumentError, CommandExecutionError
|
37
|
+
raise
|
38
|
+
rescue => e
|
39
|
+
handle_execution_error(
|
40
|
+
command: command,
|
41
|
+
error: e,
|
42
|
+
exit_on_error: exit_on_error,
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def extract_command(command_string)
|
49
|
+
match = command_string.strip.match(/^\$\((.*)\)$/)
|
50
|
+
raise ArgumentError, "Invalid command format. Expected $(command), got: #{command_string}" unless match
|
51
|
+
|
52
|
+
match[1]
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_execution_result(command:, output:, exit_status:, success:, exit_on_error:)
|
56
|
+
return output if success
|
57
|
+
|
58
|
+
if exit_on_error
|
59
|
+
raise CommandExecutionError.new(
|
60
|
+
"Command exited with non-zero status (#{exit_status})",
|
61
|
+
command: command,
|
62
|
+
exit_status: exit_status,
|
63
|
+
)
|
64
|
+
else
|
65
|
+
@logger.warn("Command '#{command}' exited with non-zero status (#{exit_status}), continuing execution")
|
66
|
+
output + "\n[Exit status: #{exit_status}]"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def handle_execution_error(command:, error:, exit_on_error:)
|
71
|
+
if exit_on_error
|
72
|
+
raise CommandExecutionError.new(
|
73
|
+
"Failed to execute command '#{command}': #{error.message}",
|
74
|
+
command: command,
|
75
|
+
original_error: error,
|
76
|
+
)
|
77
|
+
else
|
78
|
+
@logger.warn("Command '#{command}' failed with error: #{error.message}, continuing execution")
|
79
|
+
"Error executing command: #{error.message}\n[Exit status: error]"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class NullLogger
|
84
|
+
def warn(_message); end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Roast
|
4
|
+
module Workflow
|
5
|
+
# Handles execution of conditional steps (if and unless)
|
6
|
+
class ConditionalExecutor
|
7
|
+
def initialize(workflow, context_path, state_manager, workflow_executor = nil)
|
8
|
+
@workflow = workflow
|
9
|
+
@context_path = context_path
|
10
|
+
@state_manager = state_manager
|
11
|
+
@workflow_executor = workflow_executor
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute_conditional(conditional_config)
|
15
|
+
$stderr.puts "Executing conditional step: #{conditional_config.inspect}"
|
16
|
+
|
17
|
+
# Determine if this is an 'if' or 'unless' condition
|
18
|
+
condition_expr = conditional_config["if"] || conditional_config["unless"]
|
19
|
+
is_unless = conditional_config.key?("unless")
|
20
|
+
then_steps = conditional_config["then"]
|
21
|
+
|
22
|
+
# Verify required parameters
|
23
|
+
raise WorkflowExecutor::ConfigurationError, "Missing condition in conditional configuration" unless condition_expr
|
24
|
+
raise WorkflowExecutor::ConfigurationError, "Missing 'then' steps in conditional configuration" unless then_steps
|
25
|
+
|
26
|
+
# Create and execute a ConditionalStep
|
27
|
+
require "roast/workflow/conditional_step" unless defined?(Roast::Workflow::ConditionalStep)
|
28
|
+
conditional_step = ConditionalStep.new(
|
29
|
+
@workflow,
|
30
|
+
config: conditional_config,
|
31
|
+
name: "conditional_#{condition_expr.gsub(/[^a-zA-Z0-9_]/, "_")[0..20]}",
|
32
|
+
context_path: @context_path,
|
33
|
+
workflow_executor: @workflow_executor,
|
34
|
+
)
|
35
|
+
|
36
|
+
result = conditional_step.call
|
37
|
+
|
38
|
+
# Store a marker in workflow output to indicate which branch was taken
|
39
|
+
condition_key = is_unless ? "unless" : "if"
|
40
|
+
step_name = "#{condition_key}_#{condition_expr.gsub(/[^a-zA-Z0-9_]/, "_")[0..30]}"
|
41
|
+
@workflow.output[step_name] = result
|
42
|
+
|
43
|
+
# Save state
|
44
|
+
@state_manager.save_state(step_name, @workflow.output[step_name])
|
45
|
+
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "roast/workflow/base_step"
|
4
|
+
require "roast/workflow/expression_evaluator"
|
5
|
+
require "roast/workflow/interpolator"
|
6
|
+
|
7
|
+
module Roast
|
8
|
+
module Workflow
|
9
|
+
class ConditionalStep < BaseStep
|
10
|
+
include ExpressionEvaluator
|
11
|
+
|
12
|
+
def initialize(workflow, config:, name:, context_path:, workflow_executor:, **kwargs)
|
13
|
+
super(workflow, name: name, context_path: context_path, **kwargs)
|
14
|
+
|
15
|
+
@config = config
|
16
|
+
@condition = config["if"] || config["unless"]
|
17
|
+
@is_unless = config.key?("unless")
|
18
|
+
@then_steps = config["then"] || []
|
19
|
+
@else_steps = config["else"] || []
|
20
|
+
@workflow_executor = workflow_executor
|
21
|
+
end
|
22
|
+
|
23
|
+
def call
|
24
|
+
# Evaluate the condition
|
25
|
+
condition_result = evaluate_condition(@condition)
|
26
|
+
|
27
|
+
# Invert the result if this is an 'unless' condition
|
28
|
+
condition_result = !condition_result if @is_unless
|
29
|
+
|
30
|
+
# Select which steps to execute based on the condition
|
31
|
+
steps_to_execute = condition_result ? @then_steps : @else_steps
|
32
|
+
|
33
|
+
# Execute the selected steps
|
34
|
+
unless steps_to_execute.empty?
|
35
|
+
@workflow_executor.execute_steps(steps_to_execute)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Return a result indicating which branch was taken
|
39
|
+
{ condition_result: condition_result, branch_executed: condition_result ? "then" : "else" }
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def evaluate_condition(condition)
|
45
|
+
return false unless condition.is_a?(String)
|
46
|
+
|
47
|
+
if ruby_expression?(condition)
|
48
|
+
# For conditionals, coerce result to boolean
|
49
|
+
!!evaluate_ruby_expression(condition)
|
50
|
+
elsif bash_command?(condition)
|
51
|
+
evaluate_bash_command(condition, for_condition: true)
|
52
|
+
else
|
53
|
+
# Treat as a step name or direct boolean
|
54
|
+
evaluate_step_or_value(condition, for_condition: true)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -1,55 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
require "
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
|
+
require "roast/workflow/api_configuration"
|
5
|
+
require "roast/workflow/configuration_loader"
|
6
|
+
require "roast/workflow/resource_resolver"
|
7
|
+
require "roast/workflow/step_finder"
|
5
8
|
|
6
9
|
module Roast
|
7
10
|
module Workflow
|
8
11
|
# Encapsulates workflow configuration data and provides structured access
|
9
12
|
# to the configuration settings
|
10
13
|
class Configuration
|
11
|
-
attr_reader :config_hash, :workflow_path, :name, :steps, :tools, :function_configs, :
|
14
|
+
attr_reader :config_hash, :workflow_path, :name, :steps, :tools, :function_configs, :model, :resource
|
12
15
|
attr_accessor :target
|
13
16
|
|
14
|
-
|
15
|
-
@workflow_path = workflow_path
|
16
|
-
@config_hash = YAML.load_file(workflow_path)
|
17
|
-
|
18
|
-
# Extract key configuration values
|
19
|
-
@name = @config_hash["name"] || File.basename(workflow_path, ".yml")
|
20
|
-
@steps = @config_hash["steps"] || []
|
17
|
+
delegate :api_provider, :openrouter?, :openai?, to: :api_configuration
|
21
18
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
parse_functions
|
27
|
-
|
28
|
-
# Read the target parameter
|
29
|
-
@target = options[:target] || @config_hash["target"]
|
19
|
+
# Delegate api_token to effective_token for backward compatibility
|
20
|
+
def api_token
|
21
|
+
@api_configuration.effective_token
|
22
|
+
end
|
30
23
|
|
31
|
-
|
32
|
-
@
|
24
|
+
def initialize(workflow_path, options = {})
|
25
|
+
@workflow_path = workflow_path
|
33
26
|
|
34
|
-
#
|
35
|
-
|
36
|
-
@resource = if has_target?
|
37
|
-
Roast::Resources.for(@target)
|
38
|
-
else
|
39
|
-
Roast::Resources::NoneResource.new(nil)
|
40
|
-
end
|
41
|
-
end
|
27
|
+
# Load configuration using ConfigurationLoader
|
28
|
+
@config_hash = ConfigurationLoader.load(workflow_path)
|
42
29
|
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
30
|
+
# Extract basic configuration values
|
31
|
+
@name = ConfigurationLoader.extract_name(@config_hash, workflow_path)
|
32
|
+
@steps = ConfigurationLoader.extract_steps(@config_hash)
|
33
|
+
@tools = ConfigurationLoader.extract_tools(@config_hash)
|
34
|
+
@function_configs = ConfigurationLoader.extract_functions(@config_hash)
|
35
|
+
@model = ConfigurationLoader.extract_model(@config_hash)
|
47
36
|
|
48
|
-
#
|
49
|
-
@
|
37
|
+
# Initialize components
|
38
|
+
@api_configuration = ApiConfiguration.new(@config_hash)
|
39
|
+
@step_finder = StepFinder.new(@steps)
|
50
40
|
|
51
|
-
#
|
52
|
-
@
|
41
|
+
# Process target and resource
|
42
|
+
@target = ConfigurationLoader.extract_target(@config_hash, options)
|
43
|
+
process_resource
|
53
44
|
end
|
54
45
|
|
55
46
|
def context_path
|
@@ -76,62 +67,10 @@ module Roast
|
|
76
67
|
# Handle different call patterns for backward compatibility
|
77
68
|
if steps_array.is_a?(String) && target_step.nil?
|
78
69
|
target_step = steps_array
|
79
|
-
steps_array =
|
80
|
-
elsif steps_array.is_a?(Array) && target_step.is_a?(String)
|
81
|
-
# This is the normal case - steps_array and target_step are provided
|
82
|
-
else
|
83
|
-
# Default to self.steps if just the target_step is provided
|
84
|
-
steps_array = steps
|
70
|
+
steps_array = nil
|
85
71
|
end
|
86
72
|
|
87
|
-
|
88
|
-
steps_array.each_with_index do |step, index|
|
89
|
-
case step
|
90
|
-
when Hash
|
91
|
-
# Could be {name: command} or {name: {substeps}}
|
92
|
-
step_key = step.keys.first
|
93
|
-
return index if step_key == target_step
|
94
|
-
when Array
|
95
|
-
# This is a parallel step container, search inside it
|
96
|
-
found = step.any? do |substep|
|
97
|
-
case substep
|
98
|
-
when Hash
|
99
|
-
substep.keys.first == target_step
|
100
|
-
when String
|
101
|
-
substep == target_step
|
102
|
-
else
|
103
|
-
false
|
104
|
-
end
|
105
|
-
end
|
106
|
-
return index if found
|
107
|
-
when String
|
108
|
-
return index if step == target_step
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
# Fall back to the original method using extract_step_name
|
113
|
-
steps_array.each_with_index do |step, index|
|
114
|
-
step_name = extract_step_name(step)
|
115
|
-
if step_name.is_a?(Array)
|
116
|
-
# For arrays (parallel steps), check if target is in the array
|
117
|
-
return index if step_name.flatten.include?(target_step)
|
118
|
-
elsif step_name == target_step
|
119
|
-
return index
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
nil
|
124
|
-
end
|
125
|
-
|
126
|
-
# Returns an array of all tool class names
|
127
|
-
def parse_tools
|
128
|
-
# Only support array format: ["Roast::Tools::Grep", "Roast::Tools::ReadFile"]
|
129
|
-
@tools = @config_hash["tools"] || []
|
130
|
-
end
|
131
|
-
|
132
|
-
# Parse function-specific configurations
|
133
|
-
def parse_functions
|
134
|
-
@function_configs = @config_hash["functions"] || {}
|
73
|
+
@step_finder.find_index(target_step, steps_array)
|
135
74
|
end
|
136
75
|
|
137
76
|
# Get configuration for a specific function
|
@@ -141,77 +80,15 @@ module Roast
|
|
141
80
|
@function_configs[function_name.to_s] || {}
|
142
81
|
end
|
143
82
|
|
144
|
-
def openrouter?
|
145
|
-
@api_provider == :openrouter
|
146
|
-
end
|
147
|
-
|
148
|
-
def openai?
|
149
|
-
@api_provider == :openai
|
150
|
-
end
|
151
|
-
|
152
83
|
private
|
153
84
|
|
154
|
-
|
155
|
-
return :openai unless @config_hash["api_provider"]
|
156
|
-
|
157
|
-
provider = @config_hash["api_provider"].to_s.downcase
|
158
|
-
|
159
|
-
case provider
|
160
|
-
when "openai"
|
161
|
-
:openai
|
162
|
-
when "openrouter"
|
163
|
-
:openrouter
|
164
|
-
else
|
165
|
-
Roast::Helpers::Logger.warn("Unknown API provider '#{provider}', defaulting to OpenAI")
|
166
|
-
:openai
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
def process_shell_command(command)
|
171
|
-
# If it's a bash command with the $(command) syntax
|
172
|
-
if command =~ /^\$\((.*)\)$/
|
173
|
-
return Open3.capture2e({}, ::Regexp.last_match(1)).first.strip
|
174
|
-
end
|
175
|
-
|
176
|
-
# Legacy % prefix for backward compatibility
|
177
|
-
if command.start_with?("% ")
|
178
|
-
return Open3.capture2e({}, *command.split(" ")[1..-1]).first.strip
|
179
|
-
end
|
180
|
-
|
181
|
-
# Not a shell command, return as is
|
182
|
-
command
|
183
|
-
end
|
184
|
-
|
185
|
-
def process_target(command)
|
186
|
-
# Process shell command first
|
187
|
-
processed = process_shell_command(command)
|
188
|
-
|
189
|
-
# If it's a glob pattern, return the full paths of the files it matches
|
190
|
-
if processed.include?("*")
|
191
|
-
matched_files = Dir.glob(processed)
|
192
|
-
# If no files match, return the pattern itself
|
193
|
-
return processed if matched_files.empty?
|
194
|
-
|
195
|
-
return matched_files.map { |file| File.expand_path(file) }.join("\n")
|
196
|
-
end
|
197
|
-
|
198
|
-
# For tests, if the command was already processed as a shell command and is simple,
|
199
|
-
# don't expand the path to avoid breaking existing tests
|
200
|
-
return processed if command != processed && !processed.include?("/")
|
85
|
+
attr_reader :api_configuration
|
201
86
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
case step
|
208
|
-
when String
|
209
|
-
step
|
210
|
-
when Hash
|
211
|
-
step.keys.first
|
212
|
-
when Array
|
213
|
-
# For arrays, we'll need special handling as they contain multiple steps
|
214
|
-
step.map { |s| extract_step_name(s) }
|
87
|
+
def process_resource
|
88
|
+
if defined?(Roast::Resources)
|
89
|
+
@resource = ResourceResolver.resolve(@target, context_path)
|
90
|
+
# Update target with processed value for backward compatibility
|
91
|
+
@target = @resource.value if has_target?
|
215
92
|
end
|
216
93
|
end
|
217
94
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
module Roast
|
6
|
+
module Workflow
|
7
|
+
# Handles loading and parsing of workflow configuration files
|
8
|
+
class ConfigurationLoader
|
9
|
+
class << self
|
10
|
+
# Load configuration from a YAML file
|
11
|
+
# @param workflow_path [String] Path to the workflow YAML file
|
12
|
+
# @return [Hash] The parsed configuration hash
|
13
|
+
def load(workflow_path)
|
14
|
+
validate_path!(workflow_path)
|
15
|
+
config_hash = YAML.load_file(workflow_path)
|
16
|
+
validate_config!(config_hash)
|
17
|
+
config_hash
|
18
|
+
end
|
19
|
+
|
20
|
+
# Extract the workflow name from config or path
|
21
|
+
# @param config_hash [Hash] The configuration hash
|
22
|
+
# @param workflow_path [String] Path to the workflow file
|
23
|
+
# @return [String] The workflow name
|
24
|
+
def extract_name(config_hash, workflow_path)
|
25
|
+
config_hash["name"] || File.basename(workflow_path, ".yml")
|
26
|
+
end
|
27
|
+
|
28
|
+
# Extract steps from the configuration
|
29
|
+
# @param config_hash [Hash] The configuration hash
|
30
|
+
# @return [Array] The steps array or empty array
|
31
|
+
def extract_steps(config_hash)
|
32
|
+
config_hash["steps"] || []
|
33
|
+
end
|
34
|
+
|
35
|
+
# Extract tools from the configuration
|
36
|
+
# @param config_hash [Hash] The configuration hash
|
37
|
+
# @return [Array] The tools array or empty array
|
38
|
+
def extract_tools(config_hash)
|
39
|
+
config_hash["tools"] || []
|
40
|
+
end
|
41
|
+
|
42
|
+
# Extract function configurations
|
43
|
+
# @param config_hash [Hash] The configuration hash
|
44
|
+
# @return [Hash] The functions configuration or empty hash
|
45
|
+
def extract_functions(config_hash)
|
46
|
+
config_hash["functions"] || {}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Extract model from the configuration
|
50
|
+
# @param config_hash [Hash] The configuration hash
|
51
|
+
# @return [String, nil] The model name if specified
|
52
|
+
def extract_model(config_hash)
|
53
|
+
config_hash["model"]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Extract target from config or options
|
57
|
+
# @param config_hash [Hash] The configuration hash
|
58
|
+
# @param options [Hash] Runtime options
|
59
|
+
# @return [String, nil] The target if specified
|
60
|
+
def extract_target(config_hash, options = {})
|
61
|
+
options[:target] || config_hash["target"]
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def validate_path!(workflow_path)
|
67
|
+
raise ArgumentError, "Workflow path cannot be nil" if workflow_path.nil?
|
68
|
+
raise ArgumentError, "Workflow file not found: #{workflow_path}" unless File.exist?(workflow_path)
|
69
|
+
raise ArgumentError, "Workflow path must be a YAML file" unless workflow_path.end_with?(".yml", ".yaml")
|
70
|
+
end
|
71
|
+
|
72
|
+
def validate_config!(config_hash)
|
73
|
+
raise ArgumentError, "Invalid workflow configuration" unless config_hash.is_a?(Hash)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|