roast-ai 0.4.4 → 0.4.6

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 (232) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +2 -0
  3. data/.github/workflows/ci.yaml +1 -0
  4. data/.gitignore +0 -26
  5. data/.rubocop.yml +13 -0
  6. data/.rubocop_todo.yml +21 -0
  7. data/CHANGELOG.md +37 -0
  8. data/Gemfile +4 -0
  9. data/Gemfile.lock +43 -1
  10. data/bin/rubocop +27 -0
  11. data/bin/spoom +27 -0
  12. data/bin/srb +27 -0
  13. data/bin/srb-rbi +27 -0
  14. data/bin/tapioca +27 -0
  15. data/docs/INSTRUMENTATION.md +3 -3
  16. data/dsl/simple.rb +10 -0
  17. data/examples/retry/workflow.yml +23 -0
  18. data/lib/roast/dsl/executor.rb +26 -0
  19. data/lib/roast/dsl.rb +7 -0
  20. data/lib/roast/errors.rb +1 -0
  21. data/lib/roast/factories/api_provider_factory.rb +1 -0
  22. data/lib/roast/helpers/function_caching_interceptor.rb +3 -0
  23. data/lib/roast/helpers/logger.rb +6 -9
  24. data/lib/roast/helpers/metadata_access.rb +1 -0
  25. data/lib/roast/helpers/minitest_coverage_runner.rb +1 -0
  26. data/lib/roast/helpers/path_resolver.rb +8 -18
  27. data/lib/roast/helpers/prompt_loader.rb +3 -2
  28. data/lib/roast/helpers/timeout_handler.rb +3 -5
  29. data/lib/roast/initializers.rb +1 -0
  30. data/lib/roast/resources/api_resource.rb +4 -3
  31. data/lib/roast/resources/base_resource.rb +1 -0
  32. data/lib/roast/resources/directory_resource.rb +1 -0
  33. data/lib/roast/resources/file_resource.rb +1 -0
  34. data/lib/roast/resources/none_resource.rb +1 -0
  35. data/lib/roast/resources/url_resource.rb +2 -1
  36. data/lib/roast/resources.rb +1 -2
  37. data/lib/roast/services/context_threshold_checker.rb +1 -0
  38. data/lib/roast/services/token_counting_service.rb +1 -0
  39. data/lib/roast/tools/apply_diff.rb +1 -0
  40. data/lib/roast/tools/ask_user.rb +1 -0
  41. data/lib/roast/tools/bash.rb +1 -0
  42. data/lib/roast/tools/cmd.rb +1 -0
  43. data/lib/roast/tools/coding_agent.rb +1 -0
  44. data/lib/roast/tools/context_summarizer.rb +1 -0
  45. data/lib/roast/tools/grep.rb +1 -0
  46. data/lib/roast/tools/helpers/coding_agent_message_formatter.rb +1 -0
  47. data/lib/roast/tools/read_file.rb +1 -0
  48. data/lib/roast/tools/search_file.rb +1 -0
  49. data/lib/roast/tools/swarm.rb +1 -0
  50. data/lib/roast/tools/update_files.rb +1 -0
  51. data/lib/roast/tools/write_file.rb +1 -0
  52. data/lib/roast/tools.rb +2 -0
  53. data/lib/roast/value_objects/api_token.rb +1 -0
  54. data/lib/roast/value_objects/step_name.rb +1 -0
  55. data/lib/roast/value_objects/uri_base.rb +1 -0
  56. data/lib/roast/value_objects/workflow_path.rb +1 -0
  57. data/lib/roast/value_objects.rb +1 -0
  58. data/lib/roast/version.rb +2 -1
  59. data/lib/roast/workflow/agent_step.rb +1 -0
  60. data/lib/roast/workflow/api_configuration.rb +1 -0
  61. data/lib/roast/workflow/base_iteration_step.rb +1 -0
  62. data/lib/roast/workflow/base_step.rb +1 -0
  63. data/lib/roast/workflow/base_workflow.rb +1 -0
  64. data/lib/roast/workflow/case_executor.rb +1 -0
  65. data/lib/roast/workflow/case_step.rb +1 -0
  66. data/lib/roast/workflow/command_executor.rb +1 -0
  67. data/lib/roast/workflow/conditional_executor.rb +1 -0
  68. data/lib/roast/workflow/conditional_step.rb +1 -0
  69. data/lib/roast/workflow/configuration.rb +9 -0
  70. data/lib/roast/workflow/configuration_loader.rb +4 -1
  71. data/lib/roast/workflow/context_manager.rb +1 -0
  72. data/lib/roast/workflow/context_path_resolver.rb +1 -0
  73. data/lib/roast/workflow/dot_access_hash.rb +1 -0
  74. data/lib/roast/workflow/each_step.rb +1 -0
  75. data/lib/roast/workflow/error_handler.rb +39 -3
  76. data/lib/roast/workflow/expression_evaluator.rb +1 -0
  77. data/lib/roast/workflow/expression_utils.rb +1 -0
  78. data/lib/roast/workflow/file_state_repository.rb +2 -1
  79. data/lib/roast/workflow/input_executor.rb +1 -0
  80. data/lib/roast/workflow/input_step.rb +1 -0
  81. data/lib/roast/workflow/interpolator.rb +2 -1
  82. data/lib/roast/workflow/iteration_executor.rb +1 -0
  83. data/lib/roast/workflow/llm_boolean_coercer.rb +1 -0
  84. data/lib/roast/workflow/metadata_manager.rb +1 -0
  85. data/lib/roast/workflow/output_handler.rb +1 -0
  86. data/lib/roast/workflow/output_manager.rb +1 -0
  87. data/lib/roast/workflow/parallel_executor.rb +1 -0
  88. data/lib/roast/workflow/prompt_step.rb +1 -0
  89. data/lib/roast/workflow/repeat_step.rb +1 -0
  90. data/lib/roast/workflow/replay_handler.rb +1 -0
  91. data/lib/roast/workflow/resource_resolver.rb +2 -6
  92. data/lib/roast/workflow/session_manager.rb +1 -0
  93. data/lib/roast/workflow/shell_script_step.rb +1 -0
  94. data/lib/roast/workflow/sqlite_state_repository.rb +1 -0
  95. data/lib/roast/workflow/state_manager.rb +1 -0
  96. data/lib/roast/workflow/state_repository.rb +1 -0
  97. data/lib/roast/workflow/state_repository_factory.rb +4 -2
  98. data/lib/roast/workflow/step_completion_reporter.rb +1 -0
  99. data/lib/roast/workflow/step_executor_coordinator.rb +17 -6
  100. data/lib/roast/workflow/step_executor_factory.rb +1 -0
  101. data/lib/roast/workflow/step_executor_registry.rb +2 -3
  102. data/lib/roast/workflow/step_executor_with_reporting.rb +1 -0
  103. data/lib/roast/workflow/step_executors/base_step_executor.rb +1 -0
  104. data/lib/roast/workflow/step_executors/hash_step_executor.rb +1 -0
  105. data/lib/roast/workflow/step_executors/parallel_step_executor.rb +1 -0
  106. data/lib/roast/workflow/step_executors/string_step_executor.rb +1 -0
  107. data/lib/roast/workflow/step_factory.rb +1 -0
  108. data/lib/roast/workflow/step_finder.rb +1 -0
  109. data/lib/roast/workflow/step_loader.rb +1 -0
  110. data/lib/roast/workflow/step_name_extractor.rb +1 -0
  111. data/lib/roast/workflow/step_runner.rb +1 -0
  112. data/lib/roast/workflow/step_type_resolver.rb +1 -0
  113. data/lib/roast/workflow/validation_command.rb +1 -0
  114. data/lib/roast/workflow/validator.rb +1 -0
  115. data/lib/roast/workflow/validators/base_validator.rb +1 -0
  116. data/lib/roast/workflow/validators/dependency_validator.rb +1 -0
  117. data/lib/roast/workflow/validators/linting_validator.rb +1 -0
  118. data/lib/roast/workflow/validators/schema_validator.rb +1 -0
  119. data/lib/roast/workflow/validators/step_collector.rb +1 -0
  120. data/lib/roast/workflow/validators/validation_orchestrator.rb +1 -0
  121. data/lib/roast/workflow/workflow_context.rb +1 -0
  122. data/lib/roast/workflow/workflow_execution_context.rb +1 -0
  123. data/lib/roast/workflow/workflow_executor.rb +2 -1
  124. data/lib/roast/workflow/workflow_initializer.rb +16 -1
  125. data/lib/roast/workflow/workflow_runner.rb +66 -55
  126. data/lib/roast/workflow.rb +1 -0
  127. data/lib/roast/workflow_diagram_generator.rb +3 -2
  128. data/lib/roast.rb +23 -8
  129. data/sorbet/config +8 -0
  130. data/sorbet/rbi/dsl/.gitattributes +1 -0
  131. data/sorbet/rbi/dsl/active_support/callbacks.rbi +21 -0
  132. data/sorbet/rbi/gems/.gitattributes +1 -0
  133. data/sorbet/rbi/gems/activesupport@8.0.2.rbi +19107 -0
  134. data/sorbet/rbi/gems/addressable@2.8.7.rbi +1994 -0
  135. data/sorbet/rbi/gems/ast@2.4.3.rbi +585 -0
  136. data/sorbet/rbi/gems/base64@0.3.0.rbi +545 -0
  137. data/sorbet/rbi/gems/benchmark@0.4.1.rbi +619 -0
  138. data/sorbet/rbi/gems/bigdecimal@3.2.2.rbi +62 -0
  139. data/sorbet/rbi/gems/cgi@0.5.0.rbi +2961 -0
  140. data/sorbet/rbi/gems/claude_swarm@0.1.19.rbi +568 -0
  141. data/sorbet/rbi/gems/cli-kit@5.0.1.rbi +1991 -0
  142. data/sorbet/rbi/gems/cli-ui@2.3.0.rbi +3181 -0
  143. data/sorbet/rbi/gems/coderay@1.1.3.rbi +9 -0
  144. data/sorbet/rbi/gems/concurrent-ruby@1.3.5.rbi +11657 -0
  145. data/sorbet/rbi/gems/connection_pool@2.5.3.rbi +9 -0
  146. data/sorbet/rbi/gems/crack@1.0.0.rbi +145 -0
  147. data/sorbet/rbi/gems/diff-lcs@1.6.2.rbi +972 -0
  148. data/sorbet/rbi/gems/dotenv@3.1.8.rbi +295 -0
  149. data/sorbet/rbi/gems/drb@2.2.3.rbi +1661 -0
  150. data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +672 -0
  151. data/sorbet/rbi/gems/dry-core@1.1.0.rbi +1894 -0
  152. data/sorbet/rbi/gems/dry-inflector@1.2.0.rbi +659 -0
  153. data/sorbet/rbi/gems/dry-initializer@3.2.0.rbi +781 -0
  154. data/sorbet/rbi/gems/dry-logic@1.6.0.rbi +1127 -0
  155. data/sorbet/rbi/gems/dry-schema@1.14.1.rbi +3727 -0
  156. data/sorbet/rbi/gems/dry-types@1.8.3.rbi +3969 -0
  157. data/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
  158. data/sorbet/rbi/gems/event_stream_parser@1.0.0.rbi +49 -0
  159. data/sorbet/rbi/gems/faraday-multipart@1.1.1.rbi +283 -0
  160. data/sorbet/rbi/gems/faraday-net_http@3.4.1.rbi +147 -0
  161. data/sorbet/rbi/gems/faraday-retry@2.3.2.rbi +9 -0
  162. data/sorbet/rbi/gems/faraday@2.13.1.rbi +2977 -0
  163. data/sorbet/rbi/gems/fast-mcp-annotations@1.5.3.rbi +1588 -0
  164. data/sorbet/rbi/gems/ffi@1.17.2.rbi +9 -0
  165. data/sorbet/rbi/gems/formatador@1.1.0.rbi +9 -0
  166. data/sorbet/rbi/gems/guard-compat@1.2.1.rbi +102 -0
  167. data/sorbet/rbi/gems/guard-minitest@2.4.6.rbi +402 -0
  168. data/sorbet/rbi/gems/guard@2.19.1.rbi +2283 -0
  169. data/sorbet/rbi/gems/hashdiff@1.2.0.rbi +355 -0
  170. data/sorbet/rbi/gems/i18n@1.14.7.rbi +2359 -0
  171. data/sorbet/rbi/gems/json-schema@5.1.1.rbi +1466 -0
  172. data/sorbet/rbi/gems/json@2.12.2.rbi +2051 -0
  173. data/sorbet/rbi/gems/language_server-protocol@3.17.0.5.rbi +9 -0
  174. data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +9 -0
  175. data/sorbet/rbi/gems/listen@3.9.0.rbi +1206 -0
  176. data/sorbet/rbi/gems/logger@1.7.0.rbi +963 -0
  177. data/sorbet/rbi/gems/lumberjack@1.2.10.rbi +1830 -0
  178. data/sorbet/rbi/gems/method_source@1.1.0.rbi +9 -0
  179. data/sorbet/rbi/gems/mime-types-data@3.2025.0617.rbi +136 -0
  180. data/sorbet/rbi/gems/mime-types@3.7.0.rbi +1342 -0
  181. data/sorbet/rbi/gems/minitest-rg@5.3.0.rbi +160 -0
  182. data/sorbet/rbi/gems/minitest@5.25.5.rbi +1640 -0
  183. data/sorbet/rbi/gems/mocha@2.7.1.rbi +12 -0
  184. data/sorbet/rbi/gems/multipart-post@2.4.1.rbi +244 -0
  185. data/sorbet/rbi/gems/nenv@0.3.0.rbi +147 -0
  186. data/sorbet/rbi/gems/net-http@0.6.0.rbi +4247 -0
  187. data/sorbet/rbi/gems/netrc@0.11.0.rbi +159 -0
  188. data/sorbet/rbi/gems/notiffany@0.1.3.rbi +1079 -0
  189. data/sorbet/rbi/gems/open_router@0.3.3.rbi +230 -0
  190. data/sorbet/rbi/gems/ostruct@0.6.2.rbi +354 -0
  191. data/sorbet/rbi/gems/parallel@1.27.0.rbi +291 -0
  192. data/sorbet/rbi/gems/parser@3.3.8.0.rbi +5535 -0
  193. data/sorbet/rbi/gems/prism@1.4.0.rbi +41732 -0
  194. data/sorbet/rbi/gems/pry@0.15.2.rbi +9 -0
  195. data/sorbet/rbi/gems/public_suffix@6.0.2.rbi +936 -0
  196. data/sorbet/rbi/gems/racc@1.8.1.rbi +158 -0
  197. data/sorbet/rbi/gems/rack@2.2.17.rbi +5659 -0
  198. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +9 -0
  199. data/sorbet/rbi/gems/raix@1.0.2.rbi +1104 -0
  200. data/sorbet/rbi/gems/rake@13.3.0.rbi +3036 -0
  201. data/sorbet/rbi/gems/rb-fsevent@0.11.2.rbi +9 -0
  202. data/sorbet/rbi/gems/rb-inotify@0.11.1.rbi +9 -0
  203. data/sorbet/rbi/gems/rbi@0.3.6.rbi +6893 -0
  204. data/sorbet/rbi/gems/rbs@3.9.4.rbi +6978 -0
  205. data/sorbet/rbi/gems/regexp_parser@2.10.0.rbi +9 -0
  206. data/sorbet/rbi/gems/rexml@3.4.1.rbi +5346 -0
  207. data/sorbet/rbi/gems/rubocop-ast@1.45.1.rbi +9 -0
  208. data/sorbet/rbi/gems/rubocop-shopify@2.17.1.rbi +9 -0
  209. data/sorbet/rbi/gems/rubocop@1.77.0.rbi +9 -0
  210. data/sorbet/rbi/gems/ruby-graphviz@1.2.5.rbi +1333 -0
  211. data/sorbet/rbi/gems/ruby-openai@8.1.0.rbi +758 -0
  212. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +9 -0
  213. data/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi +9 -0
  214. data/sorbet/rbi/gems/securerandom@0.4.1.rbi +75 -0
  215. data/sorbet/rbi/gems/shellany@0.0.1.rbi +102 -0
  216. data/sorbet/rbi/gems/spoom@1.6.3.rbi +6985 -0
  217. data/sorbet/rbi/gems/sqlite3@2.7.0.rbi +1900 -0
  218. data/sorbet/rbi/gems/tapioca@0.16.11.rbi +3628 -0
  219. data/sorbet/rbi/gems/thor@1.4.0.rbi +4399 -0
  220. data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +5918 -0
  221. data/sorbet/rbi/gems/unicode-display_width@3.1.4.rbi +9 -0
  222. data/sorbet/rbi/gems/unicode-emoji@4.0.4.rbi +9 -0
  223. data/sorbet/rbi/gems/uri@1.0.3.rbi +2349 -0
  224. data/sorbet/rbi/gems/vcr@6.3.1.rbi +3040 -0
  225. data/sorbet/rbi/gems/webmock@3.25.1.rbi +1792 -0
  226. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
  227. data/sorbet/rbi/gems/yard@0.9.37.rbi +18492 -0
  228. data/sorbet/rbi/gems/zeitwerk@2.7.3.rbi +1429 -0
  229. data/sorbet/tapioca/config.yml +13 -0
  230. data/sorbet/tapioca/require.rb +36 -0
  231. metadata +114 -2
  232. data/lib/roast/workflow/configuration_parser.rb +0 -54
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -59,6 +60,10 @@ module Roast
59
60
  Thread.current[:current_step_name] = step_name if step_name
60
61
  Thread.current[:workflow_metadata] = @context.workflow.metadata
61
62
 
63
+ unless options[:retries]
64
+ options[:retries] = @context.config_hash[step_name]&.fetch("retries", 0) || 0
65
+ end
66
+
62
67
  case step_type
63
68
  when StepTypeResolver::COMMAND_STEP
64
69
  # Command steps should also go through interpolation
@@ -145,9 +150,12 @@ module Roast
145
150
  def execute_command_step(step, options)
146
151
  exit_on_error = options.fetch(:exit_on_error, true)
147
152
  resource_type = @context.resource_type
153
+ step_key = options[:step_key]
154
+ display_name = step_key || step
155
+ retries = options.fetch(:retries, 0)
148
156
 
149
- error_handler.with_error_handling(step, resource_type: resource_type) do
150
- $stderr.puts "Executing: #{step} (Resource type: #{resource_type || "unknown"})"
157
+ error_handler.with_error_handling(display_name, resource_type: resource_type, retries:) do
158
+ $stderr.puts "Executing: #{display_name} (Resource type: #{resource_type || "unknown"})"
151
159
 
152
160
  begin
153
161
  output = command_executor.execute(step, exit_on_error: exit_on_error)
@@ -169,7 +177,7 @@ module Roast
169
177
  output
170
178
  rescue CommandExecutor::CommandExecutionError => e
171
179
  # Print user-friendly error message
172
- $stderr.puts "\n❌ Command failed: #{step}"
180
+ $stderr.puts "\n❌ Command failed: #{display_name}"
173
181
  $stderr.puts " Exit status: #{e.exit_status}" if e.exit_status
174
182
 
175
183
  # Show command output if available
@@ -257,9 +265,11 @@ module Roast
257
265
  interpolated_step = interpolator.interpolate(step)
258
266
 
259
267
  if StepTypeResolver.command_step?(interpolated_step)
260
- # Command step - execute directly, preserving any passed options
268
+ # Command step - execute directly, preserving any passed options including step_key
261
269
  exit_on_error = options.fetch(:exit_on_error, true)
262
- execute_command_step(interpolated_step, { exit_on_error: })
270
+ step_key = options[:step_key]
271
+ retries = options[:retries] || 0
272
+ execute_command_step(interpolated_step, { exit_on_error:, step_key:, retries: })
263
273
  else
264
274
  exit_on_error = options.fetch(:exit_on_error, context.exit_on_error?(step))
265
275
  execute_standard_step(interpolated_step, options.merge(exit_on_error:))
@@ -282,8 +292,9 @@ module Roast
282
292
 
283
293
  def execute_custom_step(name, step_key: nil, **options)
284
294
  resource_type = @context.workflow.respond_to?(:resource) ? @context.workflow.resource&.type : nil
295
+ retries = options[:retries] || 0
285
296
 
286
- error_handler.with_error_handling(name, resource_type: resource_type) do
297
+ error_handler.with_error_handling(name, resource_type: resource_type, retries:) do
287
298
  $stderr.puts "Executing: #{name} (Resource type: #{resource_type || "unknown"})"
288
299
 
289
300
  # Use step_key for loading if provided, otherwise use name
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -54,9 +55,7 @@ module Roast
54
55
  private
55
56
 
56
57
  def find_executor_class(step)
57
- # First check exact class matches
58
- executor_class = @executors[step.class]
59
- return executor_class if executor_class
58
+ return @executors[step.class] if @executors[step.class]
60
59
 
61
60
  # Then check custom matchers
62
61
  matcher_entry = @type_matchers.find { |entry| entry[:matcher].call(step) }
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -61,7 +62,7 @@ module Roast
61
62
  )
62
63
 
63
64
  # Dependencies with defaults
64
- @error_handler = error_handler || ErrorHandler.new
65
+ @error_handler = error_handler || ErrorHandler.new(workflow)
65
66
  @step_loader = step_loader || StepLoader.new(workflow, config_hash, context_path, phase: phase)
66
67
  @command_executor = command_executor || CommandExecutor.new(logger: @error_handler)
67
68
  @interpolator = interpolator || Interpolator.new(workflow, logger: @error_handler)
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -28,6 +29,9 @@ module Roast
28
29
  # Only check if the workflow has steps that would need API access
29
30
  return if @configuration.steps.empty?
30
31
 
32
+ # Strip whitespace from existing Raix clients
33
+ strip_tokens_from_existing_clients
34
+
31
35
  # Check if Raix has been configured with the appropriate client
32
36
  case @configuration.api_provider
33
37
  when :openai
@@ -214,7 +218,7 @@ module Roast
214
218
 
215
219
  def client_options
216
220
  {
217
- access_token: @configuration.api_token,
221
+ access_token: @configuration.api_token&.strip,
218
222
  uri_base: @configuration.uri_base&.to_s,
219
223
  }.compact
220
224
  end
@@ -264,6 +268,17 @@ module Roast
264
268
  end
265
269
  interpolated
266
270
  end
271
+
272
+ def strip_tokens_from_existing_clients
273
+ strip_token_in_client(Raix.configuration.openai_client)
274
+ strip_token_in_client(Raix.configuration.openrouter_client)
275
+ end
276
+
277
+ def strip_token_in_client(client)
278
+ return unless client.respond_to?(:access_token)
279
+
280
+ client.instance_variable_set(:@access_token, client.access_token&.strip)
281
+ end
267
282
  end
268
283
  end
269
284
  end
@@ -1,83 +1,67 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
4
5
  module Workflow
5
- # Handles running workflows for files/targets and orchestrating execution
6
6
  class WorkflowRunner
7
- def initialize(configuration, options = {})
8
- @configuration = configuration
7
+ attr_reader :configuration, :options, :files
8
+
9
+ def initialize(workflow_path, files = [], options = {})
10
+ @configuration = Configuration.new(workflow_path, options)
9
11
  @options = options
12
+ @files = files || []
10
13
  @output_handler = OutputHandler.new
11
14
  @execution_context = WorkflowExecutionContext.new
12
- end
13
-
14
- def run_for_files(files)
15
- if @configuration.has_target?
16
- $stderr.puts "WARNING: Ignoring target parameter because files were provided: #{@configuration.target}"
17
- end
18
15
 
19
- # Execute pre-processing steps once before any targets
20
- if @configuration.pre_processing.any?
21
- $stderr.puts "Running pre-processing steps..."
22
- run_pre_processing
23
- end
24
-
25
- # Execute main workflow for each file
26
- files.each do |file|
27
- $stderr.puts "Running workflow for file: #{file}"
28
- run_single_workflow(file.strip)
29
- end
30
-
31
- # Execute post-processing steps once after all targets
32
- if @configuration.post_processing.any?
33
- $stderr.puts "Running post-processing steps..."
34
- run_post_processing
35
- end
16
+ initializer = WorkflowInitializer.new(@configuration)
17
+ initializer.setup
36
18
  end
37
19
 
38
- def run_for_targets
39
- # Split targets by line and clean up
40
- target_lines = @configuration.target.lines.map(&:strip).reject(&:empty?)
20
+ def begin!
21
+ start_time = Time.now
22
+ $stderr.puts "Starting workflow..."
23
+ $stderr.puts "Workflow: #{configuration.workflow_path}"
24
+ $stderr.puts "Options: #{options}"
25
+
26
+ ActiveSupport::Notifications.instrument("roast.workflow.start", {
27
+ workflow_path: configuration.workflow_path,
28
+ options: options,
29
+ name: configuration.basename,
30
+ })
41
31
 
42
32
  # Execute pre-processing steps once before any targets
43
- if @configuration.pre_processing.any?
33
+ if @configuration.pre_processing?
44
34
  $stderr.puts "Running pre-processing steps..."
45
35
  run_pre_processing
46
36
  end
47
37
 
48
- # Execute main workflow for each target
49
- target_lines.each do |file|
50
- $stderr.puts "Running workflow for file: #{file}"
51
- run_single_workflow(file)
38
+ if files.any?
39
+ run_for_files(files)
40
+ elsif configuration.has_target?
41
+ run_for_targets
42
+ else
43
+ run_targetless
52
44
  end
53
45
 
54
46
  # Execute post-processing steps once after all targets
55
- if @configuration.post_processing.any?
47
+ if @configuration.post_processing?
56
48
  $stderr.puts "Running post-processing steps..."
57
49
  run_post_processing
58
50
  end
51
+ rescue Roast::Errors::ExitEarly
52
+ $stderr.puts "Exiting workflow early."
53
+ ensure
54
+ execution_time = Time.now - start_time
55
+
56
+ ActiveSupport::Notifications.instrument("roast.workflow.complete", {
57
+ workflow_path: configuration.workflow_path,
58
+ success: !$ERROR_INFO,
59
+ execution_time: execution_time,
60
+ })
59
61
  end
60
62
 
61
- def run_targetless
62
- $stderr.puts "Running targetless workflow"
63
-
64
- # Execute pre-processing steps
65
- if @configuration.pre_processing.any?
66
- $stderr.puts "Running pre-processing steps..."
67
- run_pre_processing
68
- end
69
-
70
- # Execute main workflow
71
- run_single_workflow(nil)
72
-
73
- # Execute post-processing steps
74
- if @configuration.post_processing.any?
75
- $stderr.puts "Running post-processing steps..."
76
- run_post_processing
77
- end
78
- end
63
+ private
79
64
 
80
- # Public for backward compatibility with tests
81
65
  def execute_workflow(workflow)
82
66
  steps = @configuration.steps
83
67
 
@@ -98,7 +82,34 @@ module Roast
98
82
  $stderr.puts "🔥🔥🔥 ROAST COMPLETE! 🔥🔥🔥"
99
83
  end
100
84
 
101
- private
85
+ def run_for_files(files)
86
+ if @configuration.has_target?
87
+ $stderr.puts "WARNING: Ignoring target parameter because files were provided: #{@configuration.target}"
88
+ end
89
+
90
+ # Execute main workflow for each file
91
+ files.each do |file|
92
+ $stderr.puts "Running workflow for file: #{file}"
93
+ run_single_workflow(file.strip)
94
+ end
95
+ end
96
+
97
+ def run_for_targets
98
+ # Split targets by line and clean up
99
+ target_lines = @configuration.target.lines.map(&:strip).reject(&:empty?)
100
+
101
+ # Execute main workflow for each target
102
+ target_lines.each do |file|
103
+ $stderr.puts "Running workflow for file: #{file}"
104
+ run_single_workflow(file)
105
+ end
106
+ end
107
+
108
+ def run_targetless
109
+ $stderr.puts "Running targetless workflow"
110
+ # Execute main workflow
111
+ run_single_workflow(nil)
112
+ end
102
113
 
103
114
  def run_single_workflow(file)
104
115
  # Pass pre-processing data to target workflows
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Roast
@@ -74,7 +75,7 @@ module Roast
74
75
  when Hash
75
76
  process_control_flow(step)
76
77
  else
77
- ::CLI::Kit.logger.warn("Unexpected step type in workflow diagram: #{step.class} - #{step.inspect}")
78
+ Roast::Helpers::Logger.warn("Unexpected step type in workflow diagram: #{step.class} - #{step.inspect}")
78
79
  nil
79
80
  end
80
81
  end
@@ -112,7 +113,7 @@ module Roast
112
113
  elsif control_flow.key?("case")
113
114
  process_case(control_flow)
114
115
  else
115
- ::CLI::Kit.logger.warn("Unexpected control flow structure in workflow diagram: #{control_flow.keys.join(", ")}")
116
+ Roast::Helpers::Logger.warn("Unexpected control flow structure in workflow diagram: #{control_flow.keys.join(", ")}")
116
117
  nil
117
118
  end
118
119
  end
data/lib/roast.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  # Standard library requires
@@ -13,6 +14,7 @@ require "pathname"
13
14
  require "securerandom"
14
15
  require "shellwords"
15
16
  require "tempfile"
17
+ require "timeout"
16
18
  require "uri"
17
19
  require "yaml"
18
20
 
@@ -40,6 +42,7 @@ require "zeitwerk"
40
42
 
41
43
  # Set up Zeitwerk autoloader
42
44
  loader = Zeitwerk::Loader.for_gem
45
+ loader.inflector.inflect("dsl" => "DSL")
43
46
  loader.setup
44
47
 
45
48
  module Roast
@@ -54,21 +57,33 @@ module Roast
54
57
  option :replay, type: :string, aliases: "-r", desc: "Resume workflow from a specific step. Format: step_name or session_timestamp:step_name"
55
58
  option :pause, type: :string, aliases: "-p", desc: "Pause workflow after a specific step. Format: step_name"
56
59
  option :file_storage, type: :boolean, aliases: "-f", desc: "Use filesystem storage for sessions instead of SQLite"
60
+ option :executor, type: :string, default: "default", desc: "Set workflow executor - experimental syntax"
57
61
 
58
62
  def execute(*paths)
59
63
  raise Thor::Error, "Workflow configuration file is required" if paths.empty?
60
64
 
61
65
  workflow_path, *files = paths
62
66
 
63
- expanded_workflow_path = if workflow_path.include?("workflow.yml")
64
- File.expand_path(workflow_path)
67
+ if options[:executor] == "dsl"
68
+ puts "⚠️ WARNING: This is an experimental syntax and may break at any time. Don't depend on it."
69
+ Roast::DSL::Executor.from_file(workflow_path)
65
70
  else
66
- File.expand_path("roast/#{workflow_path}/workflow.yml")
67
- end
71
+ expanded_workflow_path = if workflow_path.include?("workflow.yml")
72
+ File.expand_path(workflow_path)
73
+ else
74
+ File.expand_path("roast/#{workflow_path}/workflow.yml")
75
+ end
68
76
 
69
- raise Thor::Error, "Expected a Roast workflow configuration file, got directory: #{expanded_workflow_path}" if File.directory?(expanded_workflow_path)
77
+ raise Thor::Error, "Expected a Roast workflow configuration file, got directory: #{expanded_workflow_path}" if File.directory?(expanded_workflow_path)
70
78
 
71
- Roast::Workflow::ConfigurationParser.new(expanded_workflow_path, files, options.transform_keys(&:to_sym)).begin!
79
+ Roast::Workflow::WorkflowRunner.new(expanded_workflow_path, files, options.transform_keys(&:to_sym)).begin!
80
+ end
81
+ rescue => e
82
+ if options[:verbose]
83
+ raise e
84
+ else
85
+ $stderr.puts e.message
86
+ end
72
87
  end
73
88
 
74
89
  desc "resume WORKFLOW_FILE", "Resume a paused workflow with an event"
@@ -106,7 +121,7 @@ module Roast
106
121
  session_id: session_id,
107
122
  )
108
123
 
109
- Roast::Workflow::ConfigurationParser.new(expanded_workflow_path, [], resume_options).begin!
124
+ Roast::Workflow::WorkflowRunner.new(expanded_workflow_path, [], resume_options).begin!
110
125
  end
111
126
 
112
127
  desc "version", "Display the current version of Roast"
@@ -336,7 +351,7 @@ module Roast
336
351
 
337
352
  begin
338
353
  # Execute the workflow generator (it will handle user input)
339
- Roast::Workflow::ConfigurationParser.new(generator_path, [], {}).begin!
354
+ Roast::Workflow::WorkflowRunner.new(generator_path, [], {}).begin!
340
355
 
341
356
  puts
342
357
  puts("Workflow generation complete!")
data/sorbet/config ADDED
@@ -0,0 +1,8 @@
1
+ --dir
2
+ .
3
+ --ignore=tmp/
4
+ --ignore=vendor/
5
+ --ignore=test/
6
+ --ignore=bin/
7
+ --enable-experimental-requires-ancestor
8
+ --enable-experimental-rbs-comments
@@ -0,0 +1 @@
1
+ **/*.rbi linguist-generated=true
@@ -0,0 +1,21 @@
1
+ # typed: true
2
+
3
+ # DO NOT EDIT MANUALLY
4
+ # This is an autogenerated file for dynamic methods in `ActiveSupport::Callbacks`.
5
+ # Please instead update this file by running `bin/tapioca dsl ActiveSupport::Callbacks`.
6
+
7
+
8
+ module ActiveSupport::Callbacks
9
+ include GeneratedInstanceMethods
10
+
11
+ mixes_in_class_methods GeneratedClassMethods
12
+
13
+ module GeneratedClassMethods
14
+ def __callbacks; end
15
+ def __callbacks=(value); end
16
+ end
17
+
18
+ module GeneratedInstanceMethods
19
+ def __callbacks; end
20
+ end
21
+ end
@@ -0,0 +1 @@
1
+ **/*.rbi linguist-generated=true