roast-ai 0.4.7 → 0.4.9

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 (311) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +1 -0
  4. data/Gemfile.lock +3 -3
  5. data/README.md +9 -5
  6. data/Rakefile +2 -0
  7. data/dsl/less_simple.rb +112 -0
  8. data/dsl/prototype.rb +17 -0
  9. data/dsl/simple.rb +5 -7
  10. data/dsl/step_communication.rb +18 -0
  11. data/examples/README.md +9 -0
  12. data/examples/available_tools_demo/workflow.yml +1 -1
  13. data/examples/basic_prompt_workflow/workflow.md +1 -0
  14. data/examples/basic_prompt_workflow/workflow.yml +14 -0
  15. data/examples/grading/README.md +1 -26
  16. data/examples/grading/analyze_coverage/prompt.md +1 -1
  17. data/examples/grading/calculate_final_grade.rb +10 -13
  18. data/examples/grading/format_result.rb +5 -8
  19. data/examples/grading/generate_grades/prompt.md +1 -1
  20. data/examples/grading/generate_recommendations/prompt.md +1 -1
  21. data/examples/grading/read_dependencies/prompt.md +0 -1
  22. data/examples/grading/verify_test_helpers/prompt.md +1 -1
  23. data/examples/grading/workflow.md +1 -4
  24. data/examples/grading/workflow.yml +3 -16
  25. data/lib/roast/dsl/cog/config.rb +31 -0
  26. data/lib/roast/dsl/cog/stack.rb +21 -0
  27. data/lib/roast/dsl/cog/store.rb +26 -0
  28. data/lib/roast/dsl/cog.rb +70 -0
  29. data/lib/roast/dsl/cog_execution_context.rb +29 -0
  30. data/lib/roast/dsl/cogs/cmd.rb +55 -0
  31. data/lib/roast/dsl/cogs/graph.rb +53 -0
  32. data/lib/roast/dsl/cogs.rb +65 -0
  33. data/lib/roast/dsl/config_context.rb +54 -0
  34. data/lib/roast/dsl/executor.rb +62 -7
  35. data/lib/roast/dsl/workflow_execution_context.rb +47 -0
  36. data/lib/roast/error.rb +7 -0
  37. data/lib/roast/errors.rb +3 -3
  38. data/lib/roast/graph/edge.rb +25 -0
  39. data/lib/roast/graph/node.rb +40 -0
  40. data/lib/roast/graph/quantum_edge.rb +27 -0
  41. data/lib/roast/graph/threaded_exec.rb +93 -0
  42. data/lib/roast/graph.rb +233 -0
  43. data/lib/roast/resources/api_resource.rb +2 -2
  44. data/lib/roast/resources/url_resource.rb +2 -2
  45. data/lib/roast/tools/apply_diff.rb +1 -1
  46. data/lib/roast/tools/ask_user.rb +1 -1
  47. data/lib/roast/tools/bash.rb +1 -1
  48. data/lib/roast/tools/cmd.rb +2 -2
  49. data/lib/roast/tools/coding_agent.rb +2 -2
  50. data/lib/roast/tools/grep.rb +1 -1
  51. data/lib/roast/tools/read_file.rb +1 -1
  52. data/lib/roast/tools/search_file.rb +1 -1
  53. data/lib/roast/tools/swarm.rb +1 -1
  54. data/lib/roast/tools/update_files.rb +2 -2
  55. data/lib/roast/tools/write_file.rb +1 -1
  56. data/lib/roast/tools.rb +1 -1
  57. data/lib/roast/value_objects/api_token.rb +1 -1
  58. data/lib/roast/value_objects/uri_base.rb +1 -1
  59. data/lib/roast/value_objects/workflow_path.rb +1 -1
  60. data/lib/roast/version.rb +1 -1
  61. data/lib/roast/workflow/base_step.rb +2 -3
  62. data/lib/roast/workflow/base_workflow.rb +38 -2
  63. data/lib/roast/workflow/command_executor.rb +1 -1
  64. data/lib/roast/workflow/configuration_loader.rb +1 -1
  65. data/lib/roast/workflow/error_handler.rb +1 -1
  66. data/lib/roast/workflow/step_executor_registry.rb +1 -1
  67. data/lib/roast/workflow/step_loader.rb +3 -8
  68. data/lib/roast/workflow/workflow_executor.rb +1 -1
  69. data/lib/roast.rb +7 -2
  70. data/sorbet/config +2 -0
  71. data/sorbet/rbi/annotations/.gitattributes +1 -0
  72. data/sorbet/rbi/annotations/activesupport.rbi +495 -0
  73. data/sorbet/rbi/annotations/faraday.rbi +17 -0
  74. data/sorbet/rbi/annotations/minitest.rbi +119 -0
  75. data/sorbet/rbi/annotations/mocha.rbi +34 -0
  76. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  77. data/sorbet/rbi/annotations/webmock.rbi +9 -0
  78. data/sorbet/rbi/gems/rbs-inline@0.12.0.rbi +2170 -0
  79. data/sorbet/rbi/gems/{rexml@3.4.1.rbi → rexml@3.4.2.rbi} +284 -239
  80. data/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +11 -0
  81. data/sorbet/rbi/shims/lib/roast/dsl/workflow_execution_context.rbi +11 -0
  82. data/sorbet/rbi/todo.rbi +7 -0
  83. metadata +37 -231
  84. data/CHANGELOG.md +0 -369
  85. data/examples/agent_continue/add_documentation/prompt.md +0 -5
  86. data/examples/agent_continue/add_error_handling/prompt.md +0 -5
  87. data/examples/agent_continue/analyze_codebase/prompt.md +0 -7
  88. data/examples/agent_continue/combined_workflow.yml +0 -24
  89. data/examples/agent_continue/continue_adding_features/prompt.md +0 -4
  90. data/examples/agent_continue/create_integration_tests/prompt.md +0 -3
  91. data/examples/agent_continue/document_with_context/prompt.md +0 -5
  92. data/examples/agent_continue/explore_api/prompt.md +0 -6
  93. data/examples/agent_continue/implement_client/prompt.md +0 -6
  94. data/examples/agent_continue/inline_workflow.yml +0 -20
  95. data/examples/agent_continue/refactor_code/prompt.md +0 -2
  96. data/examples/agent_continue/verify_changes/prompt.md +0 -6
  97. data/examples/agent_continue/workflow.yml +0 -27
  98. data/examples/agent_workflow/README.md +0 -75
  99. data/examples/agent_workflow/apply_refactorings/prompt.md +0 -22
  100. data/examples/agent_workflow/identify_code_smells/prompt.md +0 -15
  101. data/examples/agent_workflow/summarize_improvements/prompt.md +0 -18
  102. data/examples/agent_workflow/workflow.png +0 -0
  103. data/examples/agent_workflow/workflow.yml +0 -16
  104. data/examples/api_workflow/README.md +0 -85
  105. data/examples/api_workflow/fetch_api_data/prompt.md +0 -10
  106. data/examples/api_workflow/generate_report/prompt.md +0 -10
  107. data/examples/api_workflow/prompt.md +0 -10
  108. data/examples/api_workflow/transform_data/prompt.md +0 -10
  109. data/examples/api_workflow/workflow.png +0 -0
  110. data/examples/api_workflow/workflow.yml +0 -30
  111. data/examples/apply_diff_demo/README.md +0 -58
  112. data/examples/apply_diff_demo/apply_simple_change/prompt.md +0 -13
  113. data/examples/apply_diff_demo/create_sample_file/prompt.md +0 -11
  114. data/examples/apply_diff_demo/workflow.yml +0 -24
  115. data/examples/available_tools_demo/workflow.png +0 -0
  116. data/examples/bash_prototyping/README.md +0 -53
  117. data/examples/bash_prototyping/analyze_network/prompt.md +0 -13
  118. data/examples/bash_prototyping/analyze_system/prompt.md +0 -11
  119. data/examples/bash_prototyping/api_testing.png +0 -0
  120. data/examples/bash_prototyping/api_testing.yml +0 -14
  121. data/examples/bash_prototyping/check_processes/prompt.md +0 -11
  122. data/examples/bash_prototyping/generate_report/prompt.md +0 -16
  123. data/examples/bash_prototyping/process_json_response/prompt.md +0 -24
  124. data/examples/bash_prototyping/system_analysis.png +0 -0
  125. data/examples/bash_prototyping/system_analysis.yml +0 -14
  126. data/examples/bash_prototyping/test_public_api/prompt.md +0 -22
  127. data/examples/case_when/README.md +0 -58
  128. data/examples/case_when/detect_language/prompt.md +0 -16
  129. data/examples/case_when/workflow.png +0 -0
  130. data/examples/case_when/workflow.yml +0 -58
  131. data/examples/cmd/README.md +0 -99
  132. data/examples/cmd/analyze_project/prompt.md +0 -57
  133. data/examples/cmd/basic_demo/prompt.md +0 -48
  134. data/examples/cmd/basic_workflow.png +0 -0
  135. data/examples/cmd/basic_workflow.yml +0 -16
  136. data/examples/cmd/check_repository/prompt.md +0 -57
  137. data/examples/cmd/create_and_verify/prompt.md +0 -56
  138. data/examples/cmd/dev_workflow.png +0 -0
  139. data/examples/cmd/dev_workflow.yml +0 -26
  140. data/examples/cmd/explore_project/prompt.md +0 -67
  141. data/examples/cmd/explorer_workflow.png +0 -0
  142. data/examples/cmd/explorer_workflow.yml +0 -21
  143. data/examples/cmd/smart_tool_selection/prompt.md +0 -99
  144. data/examples/coding_agent_with_model.yml +0 -20
  145. data/examples/coding_agent_with_retries.yml +0 -30
  146. data/examples/conditional/README.md +0 -161
  147. data/examples/conditional/check_condition/prompt.md +0 -1
  148. data/examples/conditional/simple_workflow.png +0 -0
  149. data/examples/conditional/simple_workflow.yml +0 -15
  150. data/examples/conditional/workflow.png +0 -0
  151. data/examples/conditional/workflow.yml +0 -23
  152. data/examples/context_management_demo/README.md +0 -43
  153. data/examples/context_management_demo/workflow.yml +0 -42
  154. data/examples/direct_coerce_syntax/README.md +0 -32
  155. data/examples/direct_coerce_syntax/workflow.png +0 -0
  156. data/examples/direct_coerce_syntax/workflow.yml +0 -36
  157. data/examples/dot_notation/README.md +0 -37
  158. data/examples/dot_notation/workflow.png +0 -0
  159. data/examples/dot_notation/workflow.yml +0 -44
  160. data/examples/exit_on_error/README.md +0 -50
  161. data/examples/exit_on_error/analyze_lint_output/prompt.md +0 -9
  162. data/examples/exit_on_error/apply_fixes/prompt.md +0 -2
  163. data/examples/exit_on_error/workflow.png +0 -0
  164. data/examples/exit_on_error/workflow.yml +0 -19
  165. data/examples/grading/js_test_runner +0 -31
  166. data/examples/grading/rb_test_runner +0 -19
  167. data/examples/grading/run_coverage.rb +0 -54
  168. data/examples/grading/workflow.png +0 -0
  169. data/examples/grading/workflow.rb.md +0 -6
  170. data/examples/grading/workflow.ts+tsx.md +0 -6
  171. data/examples/instrumentation.rb +0 -76
  172. data/examples/interpolation/README.md +0 -50
  173. data/examples/interpolation/analyze_file/prompt.md +0 -1
  174. data/examples/interpolation/analyze_patterns/prompt.md +0 -27
  175. data/examples/interpolation/generate_report_for_js/prompt.md +0 -3
  176. data/examples/interpolation/generate_report_for_rb/prompt.md +0 -3
  177. data/examples/interpolation/sample.js +0 -48
  178. data/examples/interpolation/sample.rb +0 -42
  179. data/examples/interpolation/workflow.md +0 -1
  180. data/examples/interpolation/workflow.png +0 -0
  181. data/examples/interpolation/workflow.yml +0 -21
  182. data/examples/iteration/IMPLEMENTATION.md +0 -88
  183. data/examples/iteration/README.md +0 -68
  184. data/examples/iteration/analyze_complexity/prompt.md +0 -22
  185. data/examples/iteration/generate_recommendations/prompt.md +0 -21
  186. data/examples/iteration/generate_report/prompt.md +0 -129
  187. data/examples/iteration/implement_fix/prompt.md +0 -25
  188. data/examples/iteration/prioritize_issues/prompt.md +0 -24
  189. data/examples/iteration/prompts/analyze_file.md +0 -28
  190. data/examples/iteration/prompts/generate_summary.md +0 -24
  191. data/examples/iteration/prompts/update_report.md +0 -29
  192. data/examples/iteration/prompts/write_report.md +0 -22
  193. data/examples/iteration/read_file/prompt.md +0 -9
  194. data/examples/iteration/select_next_issue/prompt.md +0 -25
  195. data/examples/iteration/simple_workflow.md +0 -39
  196. data/examples/iteration/simple_workflow.yml +0 -58
  197. data/examples/iteration/update_fix_count/prompt.md +0 -26
  198. data/examples/iteration/verify_fix/prompt.md +0 -29
  199. data/examples/iteration/workflow.png +0 -0
  200. data/examples/iteration/workflow.yml +0 -42
  201. data/examples/json_handling/README.md +0 -32
  202. data/examples/json_handling/workflow.png +0 -0
  203. data/examples/json_handling/workflow.yml +0 -52
  204. data/examples/mcp/README.md +0 -223
  205. data/examples/mcp/analyze_changes/prompt.md +0 -8
  206. data/examples/mcp/analyze_issues/prompt.md +0 -4
  207. data/examples/mcp/analyze_schema/prompt.md +0 -4
  208. data/examples/mcp/check_data_quality/prompt.md +0 -5
  209. data/examples/mcp/check_documentation/prompt.md +0 -4
  210. data/examples/mcp/create_recommendations/prompt.md +0 -5
  211. data/examples/mcp/database_workflow.png +0 -0
  212. data/examples/mcp/database_workflow.yml +0 -29
  213. data/examples/mcp/env_demo/workflow.png +0 -0
  214. data/examples/mcp/env_demo/workflow.yml +0 -34
  215. data/examples/mcp/fetch_pr_context/prompt.md +0 -4
  216. data/examples/mcp/filesystem_demo/create_test_file/prompt.md +0 -2
  217. data/examples/mcp/filesystem_demo/list_files/prompt.md +0 -6
  218. data/examples/mcp/filesystem_demo/read_with_mcp/prompt.md +0 -7
  219. data/examples/mcp/filesystem_demo/workflow.png +0 -0
  220. data/examples/mcp/filesystem_demo/workflow.yml +0 -38
  221. data/examples/mcp/generate_insights/prompt.md +0 -4
  222. data/examples/mcp/generate_report/prompt.md +0 -6
  223. data/examples/mcp/generate_review/prompt.md +0 -16
  224. data/examples/mcp/github_workflow.png +0 -0
  225. data/examples/mcp/github_workflow.yml +0 -32
  226. data/examples/mcp/multi_mcp_workflow.png +0 -0
  227. data/examples/mcp/multi_mcp_workflow.yml +0 -58
  228. data/examples/mcp/post_review/prompt.md +0 -3
  229. data/examples/mcp/save_report/prompt.md +0 -6
  230. data/examples/mcp/search_issues/prompt.md +0 -2
  231. data/examples/mcp/summarize/prompt.md +0 -1
  232. data/examples/mcp/test_filesystem/prompt.md +0 -6
  233. data/examples/mcp/test_github/prompt.md +0 -8
  234. data/examples/mcp/test_read/prompt.md +0 -1
  235. data/examples/mcp/workflow.png +0 -0
  236. data/examples/mcp/workflow.yml +0 -35
  237. data/examples/no_model_fallback/README.md +0 -17
  238. data/examples/no_model_fallback/analyze_file/prompt.md +0 -1
  239. data/examples/no_model_fallback/analyze_patterns/prompt.md +0 -27
  240. data/examples/no_model_fallback/generate_report_for_md/prompt.md +0 -10
  241. data/examples/no_model_fallback/generate_report_for_rb/prompt.md +0 -3
  242. data/examples/no_model_fallback/sample.rb +0 -42
  243. data/examples/no_model_fallback/workflow.yml +0 -19
  244. data/examples/openrouter_example/README.md +0 -48
  245. data/examples/openrouter_example/analyze_input/prompt.md +0 -16
  246. data/examples/openrouter_example/generate_response/prompt.md +0 -9
  247. data/examples/openrouter_example/workflow.png +0 -0
  248. data/examples/openrouter_example/workflow.yml +0 -12
  249. data/examples/pre_post_processing/README.md +0 -111
  250. data/examples/pre_post_processing/analyze_test_file/prompt.md +0 -23
  251. data/examples/pre_post_processing/improve_test_coverage/prompt.md +0 -17
  252. data/examples/pre_post_processing/optimize_test_performance/prompt.md +0 -25
  253. data/examples/pre_post_processing/post_processing/aggregate_metrics/prompt.md +0 -31
  254. data/examples/pre_post_processing/post_processing/cleanup_environment/prompt.md +0 -28
  255. data/examples/pre_post_processing/post_processing/generate_summary_report/prompt.md +0 -32
  256. data/examples/pre_post_processing/post_processing/output.txt +0 -24
  257. data/examples/pre_post_processing/pre_processing/gather_baseline_metrics/prompt.md +0 -26
  258. data/examples/pre_post_processing/pre_processing/setup_test_environment/prompt.md +0 -11
  259. data/examples/pre_post_processing/validate_changes/prompt.md +0 -24
  260. data/examples/pre_post_processing/workflow.png +0 -0
  261. data/examples/pre_post_processing/workflow.yml +0 -21
  262. data/examples/retry/workflow.yml +0 -23
  263. data/examples/rspec_to_minitest/README.md +0 -68
  264. data/examples/rspec_to_minitest/analyze_spec/prompt.md +0 -30
  265. data/examples/rspec_to_minitest/create_minitest/prompt.md +0 -33
  266. data/examples/rspec_to_minitest/run_and_improve/prompt.md +0 -35
  267. data/examples/rspec_to_minitest/workflow.md +0 -10
  268. data/examples/rspec_to_minitest/workflow.png +0 -0
  269. data/examples/rspec_to_minitest/workflow.yml +0 -40
  270. data/examples/shared_config/README.md +0 -52
  271. data/examples/shared_config/example_with_shared_config/workflow.png +0 -0
  272. data/examples/shared_config/example_with_shared_config/workflow.yml +0 -6
  273. data/examples/shared_config/shared.png +0 -0
  274. data/examples/shared_config/shared.yml +0 -7
  275. data/examples/single_target_prepost/README.md +0 -36
  276. data/examples/single_target_prepost/post_processing/output.txt +0 -27
  277. data/examples/single_target_prepost/pre_processing/gather_dependencies/prompt.md +0 -11
  278. data/examples/single_target_prepost/workflow.png +0 -0
  279. data/examples/single_target_prepost/workflow.yml +0 -20
  280. data/examples/smart_coercion_defaults/README.md +0 -65
  281. data/examples/smart_coercion_defaults/workflow.png +0 -0
  282. data/examples/smart_coercion_defaults/workflow.yml +0 -44
  283. data/examples/step_configuration/README.md +0 -84
  284. data/examples/step_configuration/workflow.png +0 -0
  285. data/examples/step_configuration/workflow.yml +0 -57
  286. data/examples/swarm_example.yml +0 -25
  287. data/examples/tool_config_example/README.md +0 -109
  288. data/examples/tool_config_example/example_step/prompt.md +0 -42
  289. data/examples/tool_config_example/workflow.png +0 -0
  290. data/examples/tool_config_example/workflow.yml +0 -17
  291. data/examples/user_input/README.md +0 -90
  292. data/examples/user_input/funny_name/create_backstory/prompt.md +0 -10
  293. data/examples/user_input/funny_name/workflow.png +0 -0
  294. data/examples/user_input/funny_name/workflow.yml +0 -25
  295. data/examples/user_input/generate_summary/prompt.md +0 -11
  296. data/examples/user_input/simple_input_demo/workflow.png +0 -0
  297. data/examples/user_input/simple_input_demo/workflow.yml +0 -35
  298. data/examples/user_input/survey_workflow.png +0 -0
  299. data/examples/user_input/survey_workflow.yml +0 -71
  300. data/examples/user_input/welcome_message/prompt.md +0 -3
  301. data/examples/user_input/workflow.png +0 -0
  302. data/examples/user_input/workflow.yml +0 -73
  303. data/examples/workflow_generator/README.md +0 -27
  304. data/examples/workflow_generator/analyze_user_request/prompt.md +0 -34
  305. data/examples/workflow_generator/create_workflow_files/prompt.md +0 -32
  306. data/examples/workflow_generator/get_user_input/prompt.md +0 -14
  307. data/examples/workflow_generator/info_from_roast.rb +0 -22
  308. data/examples/workflow_generator/workflow.png +0 -0
  309. data/examples/workflow_generator/workflow.yml +0 -34
  310. data/package-lock.json +0 -6
  311. /data/sorbet/rbi/gems/{rack@2.2.17.rbi → rack@2.2.18.rbi} +0 -0
@@ -0,0 +1,29 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ module DSL
6
+ # Contains the cogs already executed in this run.
7
+ class CogExecutionContext
8
+ # Raises if you access a cog in an execution block that hasn't already been run.
9
+ class IncompleteCogExecutionAccessError < StandardError; end
10
+
11
+ def initialize(cogs, bound_names)
12
+ @cogs = cogs
13
+ bind_cog_methods(bound_names)
14
+ end
15
+
16
+ private
17
+
18
+ def bind_cog_methods(bound_names)
19
+ bound_names.map do |name|
20
+ define_singleton_method(name.to_sym, ->(name) do
21
+ @cogs[name].tap do |cog|
22
+ raise IncompleteCogExecutionAccessError unless cog.ran?
23
+ end.output
24
+ end)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,55 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ module DSL
6
+ module Cogs
7
+ class Cmd < Cog
8
+ class Output
9
+ #: String?
10
+ attr_reader :command_output
11
+
12
+ #: String?
13
+ attr_reader :err
14
+
15
+ #: Process::Status?
16
+ attr_reader :status
17
+
18
+ #: (
19
+ #| String? output,
20
+ #| String? error,
21
+ #| Process::Status? status
22
+ #| ) -> void
23
+ def initialize(output, error, status)
24
+ @command_output = output
25
+ @err = error
26
+ @status = status
27
+ end
28
+ end
29
+
30
+ class Config < Cog::Config
31
+ #: () -> void
32
+ def print_all!
33
+ @values[:print_all] = true
34
+ end
35
+
36
+ #: () -> bool
37
+ def print_all?
38
+ !!@values[:print_all]
39
+ end
40
+
41
+ def display!
42
+ print_all!
43
+ end
44
+ end
45
+
46
+ #: (String) -> Output
47
+ def execute(input)
48
+ result = Output.new(*Roast::Helpers::CmdRunner.capture3(input))
49
+ puts result.command_output if @config.print_all?
50
+ result
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,53 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ module DSL
6
+ module Cogs
7
+ class Graph < Roast::DSL::Cog
8
+ #: Proc
9
+ attr_reader :block
10
+
11
+ #: (Symbol) { (Roast::DSL::Cogs::Graph) -> void } -> void
12
+ def initialize(name, &block)
13
+ @name = name
14
+ @block = block
15
+ @graph = Roast::Graph.new
16
+ super(name, proc {})
17
+ end
18
+
19
+ #: () -> void
20
+ def on_invoke
21
+ populate!(@graph)
22
+ end
23
+
24
+ #: () -> Symbol
25
+ def store_id
26
+ @name.to_sym
27
+ end
28
+
29
+ #: (Roast::DSL::Cog) -> void
30
+ def update(other)
31
+ return unless other.is_a?(Roast::DSL::Cogs::Graph)
32
+
33
+ return if other.block.nil?
34
+
35
+ other.populate!(@graph)
36
+ end
37
+
38
+ # Populates the provided graph in-place, with the definition of how to populate in the block
39
+ #: (Roast::DSL::Cogs::Graph) -> void
40
+ def populate!(graph)
41
+ return if @block.nil?
42
+
43
+ @block.call(graph)
44
+ end
45
+
46
+ #: () -> void
47
+ def execute
48
+ @graph.execute
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,65 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ module DSL
6
+ module Cogs
7
+ class << self
8
+ #: (String) -> void
9
+ def load_all_for(rube_rb_fpath)
10
+ rube_rb_fpath = File.expand_path(rube_rb_fpath)
11
+ all_cog_files(rube_rb_fpath).each do |cog_fpath|
12
+ require cog_fpath
13
+ end
14
+ bind_all_cog_invokers
15
+ end
16
+
17
+ #: () -> Array[Class]
18
+ def all_cog_classes
19
+ # rubocop:disable Sorbet/ConstantsFromStrings
20
+ Roast::DSL::Cogs.constants.map do |cog_class_name|
21
+ Roast::DSL::Cogs.const_get(cog_class_name)
22
+ end
23
+ # rubocop:enable Sorbet/ConstantsFromStrings
24
+ end
25
+
26
+ private
27
+
28
+ #: () -> void
29
+ def bind_all_cog_invokers
30
+ all_cog_classes.each do |cog_class|
31
+ # At some point we may want to tuck this all into a Roast::DSL::Binding/Scope/Context to avoid polluting toplevel.
32
+ TOPLEVEL_BINDING.eval(binding_string_for(cog_class))
33
+ end
34
+ end
35
+
36
+ #: (Class) -> String
37
+ def binding_string_for(cog_class)
38
+ <<~RUBY
39
+ def #{cog_class.method_name}(*args, **kwargs, &block)
40
+ #{cog_class.name}.invoke(*args, **kwargs, &block)
41
+ end
42
+ RUBY
43
+ end
44
+
45
+ #: (String) -> Array[String]
46
+ def all_cog_files(rube_rb_fpath)
47
+ dirs = [project_cogs_dir(rube_rb_fpath), internal_cogs_dir]
48
+ dirs.map do |dir|
49
+ Dir.glob(File.join(dir, "*.rb")) # Just toplevel .rb files
50
+ end.flatten
51
+ end
52
+
53
+ #: (String) -> String
54
+ def project_cogs_dir(rube_rb_fpath)
55
+ File.join(File.dirname(rube_rb_fpath), "cogs")
56
+ end
57
+
58
+ #: () -> String
59
+ def internal_cogs_dir
60
+ File.join(File.dirname(__FILE__), "cogs")
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,54 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ module DSL
6
+ class ConfigContext
7
+ def initialize(cogs, config_proc)
8
+ @cogs = cogs
9
+ @executor_scoped_configs = {}
10
+ @cog_scoped_configs = {}
11
+ @config_proc = config_proc
12
+ end
13
+
14
+ def fetch_merged_config(cog_class, name = nil)
15
+ # All configs have an entry, even if it's empty.
16
+ configs = fetch_execution_scope(cog_class)
17
+ instance_configs = fetch_cog_config(cog_class, name) unless name.nil?
18
+ configs = configs.merge(instance_configs) if instance_configs
19
+ configs
20
+ end
21
+
22
+ def prepare!
23
+ bind_default_cogs
24
+ instance_eval(&@config_proc)
25
+ end
26
+
27
+ #: () -> void
28
+ def bind_default_cogs
29
+ bind_cog(Cogs::Cmd, :cmd)
30
+ end
31
+
32
+ def fetch_cog_config(cog_class, name)
33
+ @cog_scoped_configs[cog_class][name]
34
+ end
35
+
36
+ def fetch_or_create_cog_config(cog_class, name)
37
+ @cog_scoped_configs[cog_class][name] = cog_class.config_class.new unless @cog_scoped_configs.key?(name)
38
+ @cog_scoped_configs[cog_class][name]
39
+ end
40
+
41
+ def fetch_execution_scope(cog_class)
42
+ @executor_scoped_configs[cog_class]
43
+ end
44
+
45
+ def bind_cog(cog_class, method_name)
46
+ @cog_scoped_configs[cog_class] = {}
47
+ @executor_scoped_configs[cog_class] = cog_class.config_class.new
48
+ instance_eval do
49
+ define_singleton_method(method_name, &cog_class.on_config)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -4,23 +4,78 @@
4
4
  module Roast
5
5
  module DSL
6
6
  class Executor
7
+ class ExecutorError < Roast::Error; end
8
+ class ExecutorAlreadyPreparedError < ExecutorError; end
9
+ class ExecutorAlreadyCompletedError < ExecutorError; end
10
+
7
11
  class << self
8
12
  def from_file(workflow_path)
9
- execute(File.read(workflow_path))
13
+ run!(File.read(workflow_path))
10
14
  end
11
15
 
12
16
  private
13
17
 
14
- def execute(input)
15
- new.instance_eval(input)
18
+ def run!(workflow_definition)
19
+ executor = new
20
+ executor.prepare!(workflow_definition)
21
+ executor.start!
22
+ end
23
+ end
24
+
25
+ def prepare!(input)
26
+ # You can only initialize an executor once.
27
+ raise ExecutorAlreadyPreparedError if @prepared
28
+
29
+ extract_dsl_procs(input)
30
+ @cogs = Cog::Store.new
31
+ @cog_stack = Cog::Stack.new
32
+
33
+ @config_context = ConfigContext.new(@cogs, @config_proc)
34
+ @config_context.prepare!
35
+ @execution_context = WorkflowExecutionContext.new(@cogs, @cog_stack, @execution_proc)
36
+ @execution_context.prepare!
37
+
38
+ @prepared = true
39
+ end
40
+
41
+ def start!
42
+ # Now we run the cogs!
43
+ # You can only do this once, executors are not reusable to avoid state pollution
44
+ raise ExecutorAlreadyCompletedError if @completed
45
+
46
+ @cog_stack.map do |name, cog|
47
+ cog.run!(
48
+ @config_context.fetch_merged_config(cog.class, name.to_sym),
49
+ @execution_context.cog_execution_context,
50
+ )
16
51
  end
52
+
53
+ @completed = true
17
54
  end
18
55
 
19
- # Define methods to be used in workflows below.
56
+ def prepared?
57
+ @prepared ||= false
58
+ end
59
+
60
+ def completed?
61
+ @completed ||= false
62
+ end
63
+
64
+ #: { () [self: ConfigContext] -> void} -> void
65
+ def config(&block)
66
+ @config_proc = block
67
+ end
68
+
69
+ #: { () [self: WorkflowExecutionContext] -> void} -> void
70
+ def execute(&block)
71
+ @execution_proc = block
72
+ end
20
73
 
21
- def shell(command_string)
22
- output, _status = Roast::Helpers::CmdRunner.capture2e(command_string)
23
- puts output
74
+ # Separating the instance evals ensures that we can reuse the same cog method
75
+ # names between config and execute, while have the backing objects be completely
76
+ # different. This means we have an enforced separation between configuring and running.
77
+ def extract_dsl_procs(input)
78
+ instance_eval(input)
24
79
  end
25
80
  end
26
81
  end
@@ -0,0 +1,47 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ module DSL
6
+ class WorkflowExecutionContext
7
+ def initialize(cogs, cog_stack, execution_proc)
8
+ @cogs = cogs
9
+ @cog_stack = cog_stack
10
+ @execution_proc = execution_proc
11
+ @bound_names = []
12
+ end
13
+
14
+ def prepare!
15
+ bind_default_cogs
16
+ instance_eval(&@execution_proc)
17
+ end
18
+
19
+ def cog_execution_context
20
+ @cog_execution_context ||= CogExecutionContext.new(@cogs, @bound_names)
21
+ end
22
+
23
+ private
24
+
25
+ def add_cog_instance(name, cog)
26
+ @cogs.insert(name, cog)
27
+ @cog_stack.push([name, cog])
28
+ end
29
+
30
+ def output(name)
31
+ @cogs[name].output
32
+ end
33
+
34
+ #: () -> void
35
+ def bind_default_cogs
36
+ bind_cog(Cogs::Cmd, :cmd)
37
+ end
38
+
39
+ def bind_cog(cog_class, name)
40
+ @bound_names << name
41
+ instance_eval do
42
+ define_singleton_method(name, &cog_class.on_create)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,7 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ # Base class for all internal Roast errors.
6
+ class Error < StandardError; end
7
+ end
data/lib/roast/errors.rb CHANGED
@@ -4,12 +4,12 @@
4
4
  module Roast
5
5
  module Errors
6
6
  # Custom error for API resource not found (404) responses
7
- class ResourceNotFoundError < StandardError; end
7
+ class ResourceNotFoundError < Roast::Error; end
8
8
 
9
9
  # Custom error for when API authentication fails
10
- class AuthenticationError < StandardError; end
10
+ class AuthenticationError < Roast::Error; end
11
11
 
12
12
  # Exit the app, for instance via Ctrl-C during an InputStep
13
- class ExitEarly < StandardError; end
13
+ class ExitEarly < Roast::Error; end
14
14
  end
15
15
  end
@@ -0,0 +1,25 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ class Graph
6
+ class Edge
7
+ #: (Node) -> void
8
+ attr_writer :join_node
9
+
10
+ attr_reader :from_node, :to_node
11
+
12
+ #: (Node, Node, ?proc: Proc?) -> void
13
+ def initialize(from_node, to_node, proc: nil)
14
+ @from_node = from_node
15
+ @to_node = to_node
16
+ @proc = proc # TODO: Shadowing proc builtin here
17
+ end
18
+
19
+ #: () -> String
20
+ def to_s
21
+ "#{@from_node} -> #{@to_node}"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,40 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ class Graph
6
+ class Node
7
+ # class InvalidExecutableError < Roast::Error; end
8
+
9
+ attr_reader :name, :executable
10
+
11
+ #: (Symbol, ?executable: Proc | Graph | nil) -> void
12
+ def initialize(name, executable: nil)
13
+ @name = name
14
+ @executable = executable
15
+ end
16
+
17
+ #: () -> T::Boolean
18
+ def subgraph?
19
+ @executable.is_a?(Graph)
20
+ end
21
+
22
+ #: () -> T::Boolean
23
+ def done?
24
+ @name == :DONE
25
+ end
26
+
27
+ #: (Hash) -> void
28
+ def execute(state)
29
+ return if @executable.nil?
30
+
31
+ executable = @executable
32
+ if executable.is_a?(Proc)
33
+ executable.call(state)
34
+ elsif executable.is_a?(Graph)
35
+ executable.execute(state)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ class Graph
6
+ class QuantumEdge
7
+ #: (Roast::Graph::Node, Proc) -> void
8
+ def initialize(from_node, to_proc)
9
+ @from_node = from_node
10
+ @to_proc = to_proc
11
+ end
12
+
13
+ #: (Hash[untyped, untyped], Hash[Symbol, Roast::Graph::Node]) -> Array[Roast::Graph::Edge]
14
+ def collapse(state, nodes)
15
+ to_node_names = @to_proc.call(state)
16
+ to_node_names = to_node_names.is_a?(Array) ? to_node_names : [to_node_names]
17
+
18
+ to_node_names.map do |to_node_name|
19
+ to_node = nodes[to_node_name]
20
+ raise Error, "No node found with name #{to_node_name.inspect}" if to_node.nil?
21
+
22
+ Edge.new(@from_node, to_node)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,93 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ class Graph
6
+ class StateConflictError < Error; end
7
+
8
+ class ThreadedExec
9
+ def initialize(nodes, og_state)
10
+ @nodes = nodes
11
+ @og_state = og_state
12
+ end
13
+
14
+ #: () -> Hash[untyped, untyped]
15
+ def async_execute
16
+ states = threaded_execute(@nodes, @og_state)
17
+ merge_states!(@og_state, states)
18
+ @og_state
19
+ end
20
+
21
+ # Returns a hash of the new states for each node.
22
+ #: (Array[Node], Hash[untyped, untyped]) -> Hash[Symbol, Hash[untyped, untyped]]
23
+ def threaded_execute(nodes, og_state)
24
+ states = {}
25
+ threads = nodes.map do |current_node|
26
+ states[current_node.name] = og_state.dup
27
+ Thread.new do
28
+ current_node.execute(states[current_node.name])
29
+ end
30
+ end
31
+
32
+ threads.map(&:value)
33
+
34
+ states
35
+ end
36
+
37
+ #: (Hash, Hash) -> void
38
+ def merge_states!(orig_state, new_states)
39
+ orig_state.merge!(merge_new_states(orig_state, new_states))
40
+ end
41
+
42
+ # We merge all the new states together first so we can catch any keys that were modified by multiple threads.
43
+ #: (Hash[untyped, untyped], Hash[Symbol, Hash[untyped, untyped]]) -> Hash[untyped, untyped]
44
+ def merge_new_states(orig_state, new_states)
45
+ # We only care about what the threads have changes from the original state.
46
+ changed_states = changed_states(orig_state, new_states)
47
+
48
+ return {} if changed_states.empty?
49
+
50
+ # Grab some entry to be the one we merge all the other changes into.
51
+ base_node_name, base_changed_state = T.must(changed_states.shift)
52
+ base_node_name = T.cast(base_node_name, Symbol)
53
+ base_changed_state = T.cast(base_changed_state, T::Hash[T.untyped, T.untyped])
54
+
55
+ changed_states.each do |node_name, changed_state|
56
+ base_changed_state.merge!(changed_state) do |key, old_value, new_value|
57
+ raise_state_conflict(key, old_value, new_value, [base_node_name, node_name])
58
+ end
59
+ end
60
+
61
+ base_changed_state
62
+ end
63
+
64
+ #: (Hash, Hash[Symbol, Hash]) -> Hash[Symbol, Hash]
65
+ def changed_states(orig_state, new_states)
66
+ new_states.transform_values do |new_state|
67
+ changed_state_entries(orig_state, new_state)
68
+ end
69
+ end
70
+
71
+ # Filter new states content to only include keys that were modified by the new states.
72
+ #: (Hash, Hash) -> Hash
73
+ def changed_state_entries(orig_state, new_state)
74
+ new_state.reject do |key, value|
75
+ orig_state[key] == value
76
+ end
77
+ end
78
+
79
+ #: (Symbol, untyped, untyped, Array[Symbol]) -> void
80
+ def raise_state_conflict(key, old_value, new_value, conflicting_nodes)
81
+ raise StateConflictError, <<~CONFLICT.chomp
82
+ Parallel nodes modified the same state key.
83
+ Conflicting nodes: #{conflicting_nodes.join(", ")}
84
+ Key: :#{key}
85
+ Old value:
86
+ #{old_value.inspect}
87
+ New value:
88
+ #{new_value.inspect}
89
+ CONFLICT
90
+ end
91
+ end
92
+ end
93
+ end