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.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +32 -0
  4. data/CLAUDE.md +52 -1
  5. data/Gemfile +3 -1
  6. data/Gemfile.lock +63 -16
  7. data/README.md +115 -5
  8. data/bin/roast +1 -1
  9. data/claude-swarm.yml +210 -0
  10. data/docs/AGENT_STEPS.md +264 -0
  11. data/examples/agent_workflow/README.md +75 -0
  12. data/examples/agent_workflow/apply_refactorings/prompt.md +22 -0
  13. data/examples/agent_workflow/identify_code_smells/prompt.md +15 -0
  14. data/examples/agent_workflow/summarize_improvements/prompt.md +18 -0
  15. data/examples/agent_workflow/workflow.yml +16 -0
  16. data/examples/available_tools_demo/README.md +42 -0
  17. data/examples/available_tools_demo/analyze_files/prompt.md +6 -0
  18. data/examples/available_tools_demo/explore_directory/prompt.md +6 -0
  19. data/examples/available_tools_demo/workflow.yml +32 -0
  20. data/examples/available_tools_demo/write_summary/prompt.md +6 -0
  21. data/examples/case_when/detect_language/prompt.md +2 -2
  22. data/examples/grading/README.md +71 -0
  23. data/examples/grading/run_coverage.rb +0 -2
  24. data/examples/iteration/analyze_complexity/prompt.md +2 -2
  25. data/examples/iteration/generate_recommendations/prompt.md +2 -2
  26. data/examples/iteration/implement_fix/prompt.md +2 -2
  27. data/examples/iteration/prioritize_issues/prompt.md +1 -1
  28. data/examples/iteration/prompts/analyze_file.md +2 -2
  29. data/examples/iteration/prompts/generate_summary.md +1 -1
  30. data/examples/iteration/prompts/update_report.md +3 -3
  31. data/examples/iteration/prompts/write_report.md +3 -3
  32. data/examples/iteration/read_file/prompt.md +2 -2
  33. data/examples/iteration/select_next_issue/prompt.md +2 -2
  34. data/examples/iteration/update_fix_count/prompt.md +4 -4
  35. data/examples/iteration/verify_fix/prompt.md +3 -3
  36. data/examples/mcp/README.md +3 -3
  37. data/examples/mcp/analyze_changes/prompt.md +1 -1
  38. data/examples/mcp/database_workflow.yml +1 -1
  39. data/examples/mcp/fetch_pr_context/prompt.md +1 -1
  40. data/examples/mcp/github_workflow.yml +1 -1
  41. data/examples/mcp/post_review/prompt.md +1 -1
  42. data/examples/pre_post_processing/analyze_test_file/prompt.md +1 -1
  43. data/examples/pre_post_processing/improve_test_coverage/prompt.md +1 -1
  44. data/examples/pre_post_processing/optimize_test_performance/prompt.md +1 -1
  45. data/examples/pre_post_processing/post_processing/aggregate_metrics/prompt.md +2 -2
  46. data/examples/pre_post_processing/post_processing/generate_summary_report/prompt.md +1 -1
  47. data/examples/pre_post_processing/pre_processing/setup_test_environment/prompt.md +1 -1
  48. data/examples/pre_post_processing/validate_changes/prompt.md +2 -2
  49. data/examples/user_input/README.md +90 -0
  50. data/examples/user_input/funny_name/create_backstory/prompt.md +10 -0
  51. data/examples/user_input/funny_name/workflow.yml +26 -0
  52. data/examples/user_input/generate_summary/prompt.md +11 -0
  53. data/examples/user_input/simple_input_demo/workflow.yml +35 -0
  54. data/examples/user_input/survey_workflow.yml +71 -0
  55. data/examples/user_input/welcome_message/prompt.md +3 -0
  56. data/examples/user_input/workflow.yml +73 -0
  57. data/examples/workflow_generator/create_workflow_files/prompt.md +1 -1
  58. data/lib/roast/errors.rb +6 -4
  59. data/lib/roast/helpers/function_caching_interceptor.rb +0 -2
  60. data/lib/roast/helpers/logger.rb +12 -35
  61. data/lib/roast/helpers/minitest_coverage_runner.rb +0 -1
  62. data/lib/roast/helpers/prompt_loader.rb +0 -2
  63. data/lib/roast/resources/api_resource.rb +0 -4
  64. data/lib/roast/resources/url_resource.rb +0 -3
  65. data/lib/roast/resources.rb +0 -8
  66. data/lib/roast/tools/ask_user.rb +0 -2
  67. data/lib/roast/tools/bash.rb +0 -3
  68. data/lib/roast/tools/cmd.rb +0 -3
  69. data/lib/roast/tools/coding_agent.rb +1 -8
  70. data/lib/roast/tools/grep.rb +0 -3
  71. data/lib/roast/tools/helpers/coding_agent_message_formatter.rb +1 -4
  72. data/lib/roast/tools/read_file.rb +0 -2
  73. data/lib/roast/tools/search_file.rb +0 -2
  74. data/lib/roast/tools/update_files.rb +0 -4
  75. data/lib/roast/tools/write_file.rb +0 -3
  76. data/lib/roast/tools.rb +0 -13
  77. data/lib/roast/value_objects/step_name.rb +14 -3
  78. data/lib/roast/value_objects/workflow_path.rb +0 -2
  79. data/lib/roast/value_objects.rb +4 -4
  80. data/lib/roast/version.rb +1 -1
  81. data/lib/roast/workflow/agent_step.rb +26 -0
  82. data/lib/roast/workflow/api_configuration.rb +0 -4
  83. data/lib/roast/workflow/base_iteration_step.rb +0 -4
  84. data/lib/roast/workflow/base_step.rb +54 -28
  85. data/lib/roast/workflow/base_workflow.rb +2 -21
  86. data/lib/roast/workflow/case_executor.rb +0 -1
  87. data/lib/roast/workflow/case_step.rb +0 -4
  88. data/lib/roast/workflow/command_executor.rb +0 -2
  89. data/lib/roast/workflow/conditional_executor.rb +0 -1
  90. data/lib/roast/workflow/conditional_step.rb +0 -4
  91. data/lib/roast/workflow/configuration.rb +3 -10
  92. data/lib/roast/workflow/configuration_loader.rb +0 -2
  93. data/lib/roast/workflow/configuration_parser.rb +1 -7
  94. data/lib/roast/workflow/dot_access_hash.rb +16 -1
  95. data/lib/roast/workflow/error_handler.rb +0 -3
  96. data/lib/roast/workflow/expression_evaluator.rb +0 -3
  97. data/lib/roast/workflow/file_state_repository.rb +0 -5
  98. data/lib/roast/workflow/input_executor.rb +41 -0
  99. data/lib/roast/workflow/input_step.rb +163 -0
  100. data/lib/roast/workflow/iteration_executor.rb +0 -2
  101. data/lib/roast/workflow/output_handler.rb +0 -2
  102. data/lib/roast/workflow/output_manager.rb +0 -2
  103. data/lib/roast/workflow/prompt_step.rb +1 -1
  104. data/lib/roast/workflow/replay_handler.rb +0 -3
  105. data/lib/roast/workflow/resource_resolver.rb +0 -3
  106. data/lib/roast/workflow/session_manager.rb +0 -3
  107. data/lib/roast/workflow/state_manager.rb +0 -2
  108. data/lib/roast/workflow/step_executor_coordinator.rb +34 -11
  109. data/lib/roast/workflow/step_executor_factory.rb +0 -5
  110. data/lib/roast/workflow/step_executor_registry.rb +1 -4
  111. data/lib/roast/workflow/step_executors/hash_step_executor.rb +0 -3
  112. data/lib/roast/workflow/step_executors/parallel_step_executor.rb +0 -3
  113. data/lib/roast/workflow/step_executors/string_step_executor.rb +0 -2
  114. data/lib/roast/workflow/step_factory.rb +56 -0
  115. data/lib/roast/workflow/step_loader.rb +30 -16
  116. data/lib/roast/workflow/step_orchestrator.rb +3 -2
  117. data/lib/roast/workflow/step_type_resolver.rb +28 -1
  118. data/lib/roast/workflow/validator.rb +0 -4
  119. data/lib/roast/workflow/workflow_executor.rb +0 -16
  120. data/lib/roast/workflow/workflow_initializer.rb +1 -8
  121. data/lib/roast/workflow/workflow_runner.rb +0 -7
  122. data/lib/roast/workflow.rb +0 -15
  123. data/lib/roast.rb +55 -10
  124. data/roast.gemspec +2 -1
  125. data/schema/workflow.json +46 -0
  126. metadata +44 -6
  127. data/lib/roast/helpers.rb +0 -12
@@ -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
- extend Forwardable
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
- def_delegator :workflow, :append_to_final_output
16
- def_delegator :workflow, :chat_completion
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 || self.class.name.underscore.split("/").last
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:).tap do |result|
49
- process_output(result, print_response:)
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
- return JSON.parse(result)
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
- # TODO: use the workflow binding or the step?
83
- append_to_final_output(ERB.new(File.read(output_path), trim_mode: "-").result(binding))
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,
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "roast/workflow/base_step"
4
- require "roast/workflow/expression_evaluator"
5
- require "roast/workflow/interpolator"
6
-
7
3
  module Roast
8
4
  module Workflow
9
5
  class CaseStep < BaseStep
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "English"
4
-
5
3
  module Roast
6
4
  module Workflow
7
5
  class CommandExecutor
@@ -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,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "roast/workflow/base_step"
4
- require "roast/workflow/expression_evaluator"
5
- require "roast/workflow/interpolator"
6
-
7
3
  module Roast
8
4
  module Workflow
9
5
  class ConditionalStep < BaseStep
@@ -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
@@ -96,11 +91,9 @@ module Roast
96
91
  attr_reader :api_configuration
97
92
 
98
93
  def process_resource
99
- if defined?(Roast::Resources)
100
- @resource = ResourceResolver.resolve(@target, context_path)
101
- # Update target with processed value for backward compatibility
102
- @target = @resource.value if has_target?
103
- end
94
+ @resource = ResourceResolver.resolve(@target, context_path)
95
+ # Update target with processed value for backward compatibility
96
+ @target = @resource.value if has_target?
104
97
  end
105
98
  end
106
99
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "yaml"
4
-
5
3
  module Roast
6
4
  module Workflow
7
5
  # Handles loading and parsing of workflow configuration files
@@ -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
- def_delegator :current_workflow, :output
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
- value.is_a?(Hash) ? DotAccessHash.new(value) : value
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
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "roast/helpers/logger"
4
- require "roast/workflow/command_executor"
5
-
6
3
  module Roast
7
4
  module Workflow
8
5
  # Handles error logging and instrumentation for workflow execution
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "roast/workflow/command_executor"
4
- require "roast/workflow/expression_utils"
5
-
6
3
  module Roast
7
4
  module Workflow
8
5
  # Shared module for evaluating expressions in workflow steps
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
- require "fileutils"
5
- require "roast/workflow/session_manager"
6
- require "roast/workflow/state_repository"
7
-
8
3
  module Roast
9
4
  module Workflow
10
5
  # File-based implementation of StateRepository
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roast
4
+ module Workflow
5
+ # Handles execution of input steps
6
+ class InputExecutor
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_input(input_config)
15
+ # Interpolate the prompt if workflow executor is available
16
+ if @workflow_executor && input_config["prompt"]
17
+ interpolated_config = input_config.dup
18
+ interpolated_config["prompt"] = @workflow_executor.interpolate(input_config["prompt"])
19
+ else
20
+ interpolated_config = input_config
21
+ end
22
+
23
+ # Create and execute an InputStep
24
+ input_step = InputStep.new(
25
+ @workflow,
26
+ config: interpolated_config,
27
+ name: input_config["name"] || "input_#{Time.now.to_i}",
28
+ context_path: @context_path,
29
+ )
30
+
31
+ result = input_step.call
32
+
33
+ # Store in 'previous' for conditional checks
34
+ @workflow.output["previous"] = result
35
+ @state_manager.save_state("previous", result)
36
+
37
+ result
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timeout"
4
+
5
+ module Roast
6
+ module Workflow
7
+ class InputStep < BaseStep
8
+ attr_reader :prompt_text, :type, :required, :default, :timeout, :options, :step_name
9
+
10
+ def initialize(workflow, config:, **kwargs)
11
+ super(workflow, **kwargs)
12
+ parse_config(config)
13
+ end
14
+
15
+ def call
16
+ # Get user input based on the configured type
17
+ result = case type
18
+ when "boolean"
19
+ prompt_boolean
20
+ when "choice"
21
+ prompt_choice
22
+ when "password"
23
+ prompt_password
24
+ else
25
+ prompt_text_input
26
+ end
27
+
28
+ # Store the result in workflow state if a name was provided
29
+ store_in_state(result) if step_name
30
+
31
+ result
32
+ rescue Timeout::Error
33
+ handle_timeout
34
+ end
35
+
36
+ private
37
+
38
+ def parse_config(config)
39
+ @prompt_text = config["prompt"] || raise_config_error("Missing 'prompt' in input configuration")
40
+ @step_name = config["name"]
41
+ @type = config["type"] || "text"
42
+ @required = config.fetch("required", false)
43
+ @default = config["default"]
44
+ @timeout = config["timeout"]
45
+ @options = config["options"]
46
+
47
+ validate_config
48
+ end
49
+
50
+ def validate_config
51
+ if type == "choice" && options.nil?
52
+ raise_config_error("Missing 'options' for choice type input")
53
+ end
54
+
55
+ if type == "boolean" && default && ![true, false, "true", "false", "yes", "no"].include?(default)
56
+ raise_config_error("Invalid default value for boolean type: #{default}")
57
+ end
58
+ end
59
+
60
+ def prompt_text_input
61
+ loop do
62
+ result = if timeout
63
+ with_timeout { ::CLI::UI.ask(prompt_text, default: default) }
64
+ else
65
+ ::CLI::UI.ask(prompt_text, default: default)
66
+ end
67
+
68
+ if required && result.to_s.strip.empty?
69
+ puts ::CLI::UI.fmt("{{red:This field is required. Please provide a value.}}")
70
+ next
71
+ end
72
+
73
+ return result
74
+ end
75
+ end
76
+
77
+ def prompt_boolean
78
+ if timeout
79
+ with_timeout { ::CLI::UI.confirm(prompt_text, default: boolean_default) }
80
+ else
81
+ ::CLI::UI.confirm(prompt_text, default: boolean_default)
82
+ end
83
+ end
84
+
85
+ def prompt_choice
86
+ if timeout
87
+ with_timeout { ::CLI::UI.ask(prompt_text, options: options, default: default) }
88
+ else
89
+ ::CLI::UI.ask(prompt_text, options: options, default: default)
90
+ end
91
+ end
92
+
93
+ def prompt_password
94
+ require "io/console"
95
+
96
+ loop do
97
+ result = if timeout
98
+ with_timeout { prompt_password_with_echo_off }
99
+ else
100
+ prompt_password_with_echo_off
101
+ end
102
+
103
+ if required && result.to_s.strip.empty?
104
+ puts ::CLI::UI.fmt("{{red:This field is required. Please provide a value.}}")
105
+ next
106
+ end
107
+
108
+ return result
109
+ end
110
+ end
111
+
112
+ def prompt_password_with_echo_off
113
+ ::CLI::UI.with_frame_color(:blue) do
114
+ print("🔒 #{prompt_text} ")
115
+
116
+ password = if $stdin.tty?
117
+ # Use noecho for TTY environments
118
+ $stdin.noecho { $stdin.gets }.chomp
119
+ else
120
+ # Fall back to regular input for non-TTY environments
121
+ warn("[WARNING] Password will be visible (not running in TTY)")
122
+ $stdin.gets.chomp
123
+ end
124
+
125
+ puts # Add newline after password input
126
+ password
127
+ end
128
+ end
129
+
130
+ def boolean_default
131
+ case default
132
+ when true, "true", "yes"
133
+ true
134
+ when false, "false", "no"
135
+ false
136
+ end
137
+ end
138
+
139
+ def with_timeout(&block)
140
+ Timeout.timeout(timeout, &block)
141
+ end
142
+
143
+ def handle_timeout
144
+ puts ::CLI::UI.fmt("{{yellow:Input timed out after #{timeout} seconds}}")
145
+
146
+ if default
147
+ puts ::CLI::UI.fmt("{{yellow:Using default value: #{default}}}")
148
+ default
149
+ elsif required
150
+ raise_config_error("Required input timed out with no default value")
151
+ end
152
+ end
153
+
154
+ def store_in_state(value)
155
+ workflow.output[step_name] = value
156
+ end
157
+
158
+ def raise_config_error(message)
159
+ raise WorkflowExecutor::ConfigurationError, message
160
+ end
161
+ end
162
+ end
163
+ end
@@ -24,7 +24,6 @@ module Roast
24
24
  raise WorkflowExecutor::ConfigurationError, "Missing 'until' condition in repeat configuration" unless until_condition
25
25
 
26
26
  # Create and execute a RepeatStep
27
- require "roast/workflow/repeat_step" unless defined?(RepeatStep)
28
27
  repeat_step = RepeatStep.new(
29
28
  @workflow,
30
29
  steps: steps,
@@ -64,7 +63,6 @@ module Roast
64
63
  raise WorkflowExecutor::ConfigurationError, "Missing 'steps' in each configuration" unless steps
65
64
 
66
65
  # Create and execute an EachStep
67
- require "roast/workflow/each_step" unless defined?(EachStep)
68
66
  each_step = EachStep.new(
69
67
  @workflow,
70
68
  collection_expr: collection_expr,
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "roast/workflow/file_state_repository"
4
-
5
3
  module Roast
6
4
  module Workflow
7
5
  # Handles output operations for workflows including saving final output and results
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "roast/workflow/dot_access_hash"
4
-
5
3
  module Roast
6
4
  module Workflow
7
5
  # Manages workflow output, including both the key-value output hash
@@ -9,7 +9,7 @@ module Roast
9
9
 
10
10
  def call
11
11
  prompt(name)
12
- result = chat_completion
12
+ result = chat_completion(print_response:, json:, params:)
13
13
 
14
14
  # Apply coercion if configured
15
15
  apply_coercion(result)
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "roast/workflow/step_finder"
4
- require "roast/workflow/file_state_repository"
5
-
6
3
  module Roast
7
4
  module Workflow
8
5
  # Handles replay functionality for workflows
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "open3"
4
- require "roast/resources"
5
-
6
3
  module Roast
7
4
  module Workflow
8
5
  # Handles resource resolution and target processing
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fileutils"
4
- require "digest"
5
-
6
3
  module Roast
7
4
  module Workflow
8
5
  # Manages session creation, timestamping, and directory management
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "roast/workflow/file_state_repository"
4
-
5
3
  module Roast
6
4
  module Workflow
7
5
  # Manages workflow state persistence and restoration