roast-ai 0.3.1 → 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 +20 -0
- data/CLAUDE.md +52 -1
- data/Gemfile +3 -1
- data/Gemfile.lock +63 -16
- data/README.md +90 -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/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 -66
- 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/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 +43 -6
- data/lib/roast/helpers.rb +0 -12
data/lib/roast/resources.rb
CHANGED
@@ -1,13 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "roast/resources/base_resource"
|
4
|
-
require "roast/resources/file_resource"
|
5
|
-
require "roast/resources/directory_resource"
|
6
|
-
require "roast/resources/url_resource"
|
7
|
-
require "roast/resources/api_resource"
|
8
|
-
require "roast/resources/none_resource"
|
9
|
-
require "uri"
|
10
|
-
|
11
3
|
module Roast
|
12
4
|
# The Resources module contains classes for handling different types of resources
|
13
5
|
# that workflows can operate on. Each resource type implements a common interface.
|
data/lib/roast/tools/ask_user.rb
CHANGED
data/lib/roast/tools/bash.rb
CHANGED
data/lib/roast/tools/cmd.rb
CHANGED
@@ -1,12 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "roast/helpers/logger"
|
4
|
-
require "roast/tools/helpers/coding_agent_message_formatter"
|
5
|
-
require "json"
|
6
|
-
require "open3"
|
7
|
-
require "tempfile"
|
8
|
-
require "securerandom"
|
9
|
-
|
10
3
|
module Roast
|
11
4
|
module Tools
|
12
5
|
module CodingAgent
|
@@ -110,7 +103,7 @@ module Roast
|
|
110
103
|
def handle_intermediate_message(json)
|
111
104
|
case json["type"]
|
112
105
|
when "assistant", "user"
|
113
|
-
CodingAgentMessageFormatter.format_messages(json).each(&method(:log_message))
|
106
|
+
Roast::Tools::Helpers::CodingAgentMessageFormatter.format_messages(json).each(&method(:log_message))
|
114
107
|
when "result", "system"
|
115
108
|
# Ignore these message types
|
116
109
|
else
|
data/lib/roast/tools/grep.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "roast/helpers/logger"
|
4
|
-
|
5
3
|
module Roast
|
6
4
|
module Tools
|
7
5
|
module Grep
|
@@ -30,7 +28,6 @@ module Roast
|
|
30
28
|
Roast::Helpers::Logger.info("🔍 Grepping for string: #{string}\n")
|
31
29
|
|
32
30
|
# Use Open3 to safely pass the string as an argument, avoiding shell injection
|
33
|
-
require "open3"
|
34
31
|
cmd = ["rg", "-C", "4", "--trim", "--color=never", "--heading", "-F", "--", string, "."]
|
35
32
|
stdout, _stderr, _status = Open3.capture3(*cmd)
|
36
33
|
|
data/lib/roast/tools.rb
CHANGED
@@ -1,18 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "English"
|
4
|
-
require "fileutils"
|
5
|
-
|
6
|
-
require "roast/tools/ask_user"
|
7
|
-
require "roast/tools/bash"
|
8
|
-
require "roast/tools/cmd"
|
9
|
-
require "roast/tools/coding_agent"
|
10
|
-
require "roast/tools/grep"
|
11
|
-
require "roast/tools/read_file"
|
12
|
-
require "roast/tools/search_file"
|
13
|
-
require "roast/tools/update_files"
|
14
|
-
require "roast/tools/write_file"
|
15
|
-
|
16
3
|
module Roast
|
17
4
|
module Tools
|
18
5
|
extend self
|
@@ -24,12 +24,23 @@ module Roast
|
|
24
24
|
@value
|
25
25
|
end
|
26
26
|
|
27
|
+
# Enables implicit conversion to String
|
28
|
+
alias_method :to_str, :to_s
|
29
|
+
|
27
30
|
def ==(other)
|
28
|
-
|
31
|
+
case other
|
32
|
+
when StepName
|
33
|
+
value == other.value
|
34
|
+
when String
|
35
|
+
value == other
|
36
|
+
else
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
29
40
|
|
30
|
-
|
41
|
+
def eql?(other)
|
42
|
+
other.is_a?(StepName) && value == other.value
|
31
43
|
end
|
32
|
-
alias_method :eql?, :==
|
33
44
|
|
34
45
|
def hash
|
35
46
|
[self.class, @value].hash
|
data/lib/roast/value_objects.rb
CHANGED
data/lib/roast/version.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Roast
|
4
|
+
module Workflow
|
5
|
+
class AgentStep < BaseStep
|
6
|
+
def call
|
7
|
+
# For inline prompts (detected by plain text step names), use the name as the prompt
|
8
|
+
# For file-based steps, load from the prompt file
|
9
|
+
prompt_content = if name.plain_text?
|
10
|
+
name.to_s
|
11
|
+
else
|
12
|
+
read_sidecar_prompt
|
13
|
+
end
|
14
|
+
|
15
|
+
# Call CodingAgent directly with the prompt content
|
16
|
+
result = Roast::Tools::CodingAgent.call(prompt_content)
|
17
|
+
|
18
|
+
# Process output if print_response is enabled
|
19
|
+
process_output(result, print_response:)
|
20
|
+
|
21
|
+
# Apply coercion if configured
|
22
|
+
apply_coercion(result)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,9 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "roast/factories/api_provider_factory"
|
4
|
-
require "roast/workflow/resource_resolver"
|
5
|
-
require "roast/value_objects/uri_base"
|
6
|
-
|
7
3
|
module Roast
|
8
4
|
module Workflow
|
9
5
|
# Handles API-related configuration including tokens and providers
|
@@ -1,37 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "erb"
|
4
|
-
require "forwardable"
|
5
|
-
require "roast/workflow/context_path_resolver"
|
6
|
-
|
7
3
|
module Roast
|
8
4
|
module Workflow
|
9
5
|
class BaseStep
|
10
|
-
|
11
|
-
|
12
|
-
attr_accessor :model, :print_response, :json, :params, :resource, :coerce_to
|
6
|
+
attr_accessor :model, :print_response, :json, :params, :resource, :coerce_to, :available_tools
|
13
7
|
attr_reader :workflow, :name, :context_path
|
14
8
|
|
15
|
-
|
16
|
-
|
17
|
-
def_delegator :workflow, :transcript
|
9
|
+
delegate :append_to_final_output, :transcript, to: :workflow
|
10
|
+
delegate_missing_to :workflow
|
18
11
|
|
19
12
|
# TODO: is this really the model we want to default to, and is this the right place to set it?
|
20
13
|
def initialize(workflow, model: "anthropic:claude-opus-4", name: nil, context_path: nil)
|
21
14
|
@workflow = workflow
|
22
15
|
@model = model
|
23
|
-
@name = name
|
16
|
+
@name = normalize_name(name)
|
24
17
|
@context_path = context_path || ContextPathResolver.resolve(self.class)
|
25
18
|
@print_response = false
|
26
19
|
@json = false
|
27
20
|
@params = {}
|
28
21
|
@coerce_to = nil
|
22
|
+
@available_tools = nil
|
29
23
|
@resource = workflow.resource if workflow.respond_to?(:resource)
|
30
24
|
end
|
31
25
|
|
32
26
|
def call
|
33
27
|
prompt(read_sidecar_prompt)
|
34
|
-
result = chat_completion(print_response:, json:, params:)
|
28
|
+
result = chat_completion(print_response:, json:, params:, available_tools:)
|
35
29
|
|
36
30
|
# Apply coercion if configured
|
37
31
|
apply_coercion(result)
|
@@ -39,25 +33,17 @@ module Roast
|
|
39
33
|
|
40
34
|
protected
|
41
35
|
|
42
|
-
def chat_completion(print_response: nil, json: nil, params: nil)
|
36
|
+
def chat_completion(print_response: nil, json: nil, params: nil, available_tools: nil)
|
43
37
|
# Use instance variables as defaults if parameters are not provided
|
44
38
|
print_response = @print_response if print_response.nil?
|
45
39
|
json = @json if json.nil?
|
46
40
|
params = @params if params.nil?
|
41
|
+
available_tools = @available_tools if available_tools.nil?
|
47
42
|
|
48
|
-
workflow.chat_completion(openai: workflow.openai? && model, model: model, json:, params:)
|
49
|
-
|
50
|
-
|
51
|
-
begin
|
52
|
-
if json
|
53
|
-
return nil if result.strip.empty? # Explicitly handle empty string
|
43
|
+
result = workflow.chat_completion(openai: workflow.openai? && model, model: model, json:, params:, available_tools:)
|
44
|
+
process_output(result, print_response:)
|
54
45
|
|
55
|
-
|
56
|
-
end
|
57
|
-
rescue JSON::ParserError
|
58
|
-
# If JSON parsing fails, leave it as a string
|
59
|
-
end
|
60
|
-
end
|
46
|
+
result
|
61
47
|
end
|
62
48
|
|
63
49
|
def prompt(text)
|
@@ -79,8 +65,20 @@ module Roast
|
|
79
65
|
def process_output(response, print_response:)
|
80
66
|
output_path = File.join(context_path, "output.txt")
|
81
67
|
if File.exist?(output_path) && print_response
|
82
|
-
#
|
83
|
-
|
68
|
+
# Deep wrap the response for template access
|
69
|
+
template_response = deep_wrap_for_templates(response)
|
70
|
+
|
71
|
+
# Debug output
|
72
|
+
if template_response.is_a?(DotAccessHash) && template_response.recommendations&.is_a?(Array)
|
73
|
+
$stderr.puts "DEBUG: recommendations array has #{template_response.recommendations.size} items"
|
74
|
+
$stderr.puts "DEBUG: first item class: #{template_response.recommendations.first.class}" if template_response.recommendations.first
|
75
|
+
end
|
76
|
+
|
77
|
+
# Create a binding that includes the wrapped response
|
78
|
+
template_binding = binding
|
79
|
+
template_binding.local_variable_set(:response, template_response)
|
80
|
+
|
81
|
+
append_to_final_output(ERB.new(File.read(output_path), trim_mode: "-").result(template_binding))
|
84
82
|
elsif print_response
|
85
83
|
append_to_final_output(response)
|
86
84
|
end
|
@@ -88,6 +86,35 @@ module Roast
|
|
88
86
|
|
89
87
|
private
|
90
88
|
|
89
|
+
def normalize_name(name)
|
90
|
+
return name if name.is_a?(Roast::ValueObjects::StepName)
|
91
|
+
|
92
|
+
name_value = name || self.class.name.underscore.split("/").last
|
93
|
+
Roast::ValueObjects::StepName.new(name_value)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Deep wrap response for ERB templates
|
97
|
+
# This creates a new structure where:
|
98
|
+
# - Hashes are wrapped in DotAccessHash
|
99
|
+
# - Arrays are cloned with their Hash elements wrapped
|
100
|
+
def deep_wrap_for_templates(obj)
|
101
|
+
case obj
|
102
|
+
when Hash
|
103
|
+
# Convert the hash to a new hash with wrapped values
|
104
|
+
wrapped_hash = {}
|
105
|
+
obj.each do |key, value|
|
106
|
+
wrapped_hash[key] = deep_wrap_for_templates(value)
|
107
|
+
end
|
108
|
+
DotAccessHash.new(wrapped_hash)
|
109
|
+
when Array
|
110
|
+
# Create a new array with wrapped elements
|
111
|
+
# This allows the template to use dot notation on array elements
|
112
|
+
obj.map { |item| deep_wrap_for_templates(item) }
|
113
|
+
else
|
114
|
+
obj
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
91
118
|
def apply_coercion(result)
|
92
119
|
case @coerce_to
|
93
120
|
when :boolean
|
@@ -97,7 +124,6 @@ module Roast
|
|
97
124
|
!!result
|
98
125
|
when :llm_boolean
|
99
126
|
# Use LLM boolean coercer for natural language responses
|
100
|
-
require "roast/workflow/llm_boolean_coercer"
|
101
127
|
LlmBooleanCoercer.coerce(result)
|
102
128
|
when :iterable
|
103
129
|
# Ensure result is iterable
|
@@ -1,12 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "raix/chat_completion"
|
4
|
-
require "raix/function_dispatch"
|
5
|
-
|
6
|
-
require "roast/workflow/context_path_resolver"
|
7
|
-
require "roast/workflow/dot_access_hash"
|
8
|
-
require "roast/workflow/output_manager"
|
9
|
-
|
10
3
|
module Roast
|
11
4
|
module Workflow
|
12
5
|
class BaseWorkflow
|
@@ -29,6 +22,7 @@ module Roast
|
|
29
22
|
|
30
23
|
delegate :api_provider, :openai?, to: :workflow_configuration, allow_nil: true
|
31
24
|
delegate :output, :output=, :append_to_final_output, :final_output, to: :output_manager
|
25
|
+
delegate_missing_to :output
|
32
26
|
|
33
27
|
def initialize(file = nil, name: nil, context_path: nil, resource: nil, session_name: nil, workflow_configuration: nil, pre_processing_data: nil)
|
34
28
|
@file = file
|
@@ -81,7 +75,7 @@ module Roast
|
|
81
75
|
rescue Faraday::ResourceNotFound => e
|
82
76
|
execution_time = Time.now - start_time
|
83
77
|
message = e.response.dig(:body, "error", "message") || e.message
|
84
|
-
error = Roast::ResourceNotFoundError.new(message)
|
78
|
+
error = Roast::Errors::ResourceNotFoundError.new(message)
|
85
79
|
error.set_backtrace(e.backtrace)
|
86
80
|
log_and_raise_error(error, message, step_model || model, kwargs, execution_time)
|
87
81
|
rescue => e
|
@@ -104,19 +98,6 @@ module Roast
|
|
104
98
|
# Expose output manager for state management
|
105
99
|
attr_reader :output_manager
|
106
100
|
|
107
|
-
# Allow direct access to output values without 'output.' prefix
|
108
|
-
def method_missing(method_name, *args, &block)
|
109
|
-
if output.respond_to?(method_name)
|
110
|
-
output.send(method_name, *args, &block)
|
111
|
-
else
|
112
|
-
super
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def respond_to_missing?(method_name, include_private = false)
|
117
|
-
output.respond_to?(method_name) || super
|
118
|
-
end
|
119
|
-
|
120
101
|
private
|
121
102
|
|
122
103
|
def log_and_raise_error(error, message, model, params, execution_time)
|
@@ -24,7 +24,6 @@ module Roast
|
|
24
24
|
raise WorkflowExecutor::ConfigurationError, "Missing 'when' clauses in case configuration" unless when_clauses
|
25
25
|
|
26
26
|
# Create and execute a CaseStep
|
27
|
-
require "roast/workflow/case_step" unless defined?(Roast::Workflow::CaseStep)
|
28
27
|
case_step = CaseStep.new(
|
29
28
|
@workflow,
|
30
29
|
config: case_config,
|
@@ -24,7 +24,6 @@ module Roast
|
|
24
24
|
raise WorkflowExecutor::ConfigurationError, "Missing 'then' steps in conditional configuration" unless then_steps
|
25
25
|
|
26
26
|
# Create and execute a ConditionalStep
|
27
|
-
require "roast/workflow/conditional_step" unless defined?(Roast::Workflow::ConditionalStep)
|
28
27
|
conditional_step = ConditionalStep.new(
|
29
28
|
@workflow,
|
30
29
|
config: conditional_config,
|
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "roast/workflow/api_configuration"
|
4
|
-
require "roast/workflow/configuration_loader"
|
5
|
-
require "roast/workflow/resource_resolver"
|
6
|
-
require "roast/workflow/step_finder"
|
7
|
-
|
8
3
|
module Roast
|
9
4
|
module Workflow
|
10
5
|
# Encapsulates workflow configuration data and provides structured access
|
@@ -45,8 +40,6 @@ module Roast
|
|
45
40
|
# Process target and resource
|
46
41
|
@target = ConfigurationLoader.extract_target(@config_hash, options)
|
47
42
|
process_resource
|
48
|
-
|
49
|
-
mark_last_step_for_output
|
50
43
|
end
|
51
44
|
|
52
45
|
def context_path
|
@@ -98,65 +91,9 @@ module Roast
|
|
98
91
|
attr_reader :api_configuration
|
99
92
|
|
100
93
|
def process_resource
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
@target = @resource.value if has_target?
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def mark_last_step_for_output
|
109
|
-
return if @steps.empty?
|
110
|
-
|
111
|
-
last_step = find_last_executable_step(@steps.last)
|
112
|
-
return unless last_step
|
113
|
-
|
114
|
-
# Get the step name/key
|
115
|
-
step_key = extract_step_key(last_step)
|
116
|
-
return unless step_key
|
117
|
-
|
118
|
-
# Ensure config exists for this step
|
119
|
-
@config_hash[step_key] ||= {}
|
120
|
-
|
121
|
-
# Only set print_response if not already explicitly configured
|
122
|
-
@config_hash[step_key]["print_response"] = true unless @config_hash[step_key].key?("print_response")
|
123
|
-
end
|
124
|
-
|
125
|
-
def find_last_executable_step(step)
|
126
|
-
case step
|
127
|
-
when String
|
128
|
-
step
|
129
|
-
when Hash
|
130
|
-
# Check if it's a special step type (if, unless, each, repeat, case)
|
131
|
-
if step.key?("if") || step.key?("unless")
|
132
|
-
# For conditional steps, try to find the last step in the "then" branch
|
133
|
-
then_steps = step["then"] || step["steps"]
|
134
|
-
find_last_executable_step(then_steps.last) if then_steps&.any?
|
135
|
-
elsif step.key?("each") || step.key?("repeat")
|
136
|
-
# For iteration steps, we can't reliably determine the last step
|
137
|
-
nil
|
138
|
-
elsif step.key?("case")
|
139
|
-
# For case steps, we can't reliably determine the last step
|
140
|
-
nil
|
141
|
-
elsif step.size == 1
|
142
|
-
# Regular hash step with variable assignment
|
143
|
-
step
|
144
|
-
end
|
145
|
-
when Array
|
146
|
-
# For parallel steps, we can't determine a single "last" step
|
147
|
-
nil
|
148
|
-
else
|
149
|
-
step
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def extract_step_key(step)
|
154
|
-
case step
|
155
|
-
when String
|
156
|
-
step
|
157
|
-
when Hash
|
158
|
-
step.keys.first
|
159
|
-
end
|
94
|
+
@resource = ResourceResolver.resolve(@target, context_path)
|
95
|
+
# Update target with processed value for backward compatibility
|
96
|
+
@target = @resource.value if has_target?
|
160
97
|
end
|
161
98
|
end
|
162
99
|
end
|
@@ -1,17 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "roast/workflow/configuration"
|
4
|
-
require "roast/workflow/workflow_initializer"
|
5
|
-
require "roast/workflow/workflow_runner"
|
6
|
-
|
7
3
|
module Roast
|
8
4
|
module Workflow
|
9
5
|
class ConfigurationParser
|
10
|
-
extend Forwardable
|
11
|
-
|
12
6
|
attr_reader :configuration, :options, :files, :current_workflow
|
13
7
|
|
14
|
-
|
8
|
+
delegate :output, to: :current_workflow
|
15
9
|
|
16
10
|
def initialize(workflow_path, files = [], options = {})
|
17
11
|
@configuration = Configuration.new(workflow_path, options)
|
@@ -9,7 +9,7 @@ module Roast
|
|
9
9
|
|
10
10
|
def [](key)
|
11
11
|
value = @hash[key.to_sym] || @hash[key.to_s]
|
12
|
-
|
12
|
+
wrap_value(value)
|
13
13
|
end
|
14
14
|
|
15
15
|
def []=(key, value)
|
@@ -193,6 +193,21 @@ module Roast
|
|
193
193
|
end
|
194
194
|
|
195
195
|
alias_method :member?, :has_key?
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
def wrap_value(value)
|
200
|
+
case value
|
201
|
+
when Hash
|
202
|
+
DotAccessHash.new(value)
|
203
|
+
when Array
|
204
|
+
# Don't create a new array - return the original array
|
205
|
+
# Only wrap Hash elements within the array when needed
|
206
|
+
value
|
207
|
+
else
|
208
|
+
value
|
209
|
+
end
|
210
|
+
end
|
196
211
|
end
|
197
212
|
end
|
198
213
|
end
|