roast-ai 0.4.6 → 0.4.8

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 (286) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +3 -1
  3. data/.gitignore +7 -0
  4. data/.rubocop.yml +14 -0
  5. data/Gemfile +2 -1
  6. data/Gemfile.lock +9 -1
  7. data/Rakefile +16 -4
  8. data/examples/README.md +9 -0
  9. data/examples/available_tools_demo/workflow.yml +2 -2
  10. data/examples/basic_prompt_workflow/workflow.md +1 -0
  11. data/examples/basic_prompt_workflow/workflow.yml +14 -0
  12. data/lib/roast/dsl/executor.rb +2 -1
  13. data/lib/roast/helpers/cmd_runner.rb +199 -0
  14. data/lib/roast/initializers.rb +1 -1
  15. data/lib/roast/tools/apply_diff.rb +1 -1
  16. data/lib/roast/tools/bash.rb +4 -4
  17. data/lib/roast/tools/cmd.rb +3 -5
  18. data/lib/roast/tools/coding_agent.rb +1 -1
  19. data/lib/roast/tools/grep.rb +6 -2
  20. data/lib/roast/tools/read_file.rb +2 -1
  21. data/lib/roast/tools/swarm.rb +2 -7
  22. data/lib/roast/tools.rb +10 -1
  23. data/lib/roast/version.rb +1 -1
  24. data/lib/roast/workflow/base_step.rb +2 -3
  25. data/lib/roast/workflow/command_executor.rb +3 -3
  26. data/lib/roast/workflow/resource_resolver.rb +1 -1
  27. data/lib/roast/workflow/shell_script_step.rb +1 -1
  28. data/lib/roast/workflow/step_loader.rb +2 -7
  29. data/lib/roast.rb +7 -1
  30. data/rubocop/cop/roast/use_cmd_runner.rb +93 -0
  31. data/rubocop/cop/roast.rb +4 -0
  32. data/sorbet/rbi/gems/docile@1.4.1.rbi +377 -0
  33. data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +233 -2
  34. data/sorbet/rbi/gems/racc@1.8.1.rbi +6 -4
  35. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +396 -2
  36. data/sorbet/rbi/gems/regexp_parser@2.10.0.rbi +3788 -2
  37. data/sorbet/rbi/gems/rubocop-ast@1.45.1.rbi +7747 -2
  38. data/sorbet/rbi/gems/rubocop-sorbet@0.10.5.rbi +2386 -0
  39. data/sorbet/rbi/gems/rubocop@1.77.0.rbi +62813 -2
  40. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1311 -2
  41. data/sorbet/rbi/gems/simplecov-html@0.13.2.rbi +225 -0
  42. data/sorbet/rbi/gems/simplecov@0.22.0.rbi +2259 -0
  43. data/sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi +9 -0
  44. data/sorbet/rbi/gems/unicode-display_width@3.1.4.rbi +125 -2
  45. data/sorbet/rbi/gems/unicode-emoji@4.0.4.rbi +244 -2
  46. data/sorbet/tapioca/require.rb +2 -1
  47. metadata +12 -240
  48. data/CHANGELOG.md +0 -364
  49. data/examples/agent_continue/add_documentation/prompt.md +0 -5
  50. data/examples/agent_continue/add_error_handling/prompt.md +0 -5
  51. data/examples/agent_continue/analyze_codebase/prompt.md +0 -7
  52. data/examples/agent_continue/combined_workflow.yml +0 -24
  53. data/examples/agent_continue/continue_adding_features/prompt.md +0 -4
  54. data/examples/agent_continue/create_integration_tests/prompt.md +0 -3
  55. data/examples/agent_continue/document_with_context/prompt.md +0 -5
  56. data/examples/agent_continue/explore_api/prompt.md +0 -6
  57. data/examples/agent_continue/implement_client/prompt.md +0 -6
  58. data/examples/agent_continue/inline_workflow.yml +0 -20
  59. data/examples/agent_continue/refactor_code/prompt.md +0 -2
  60. data/examples/agent_continue/verify_changes/prompt.md +0 -6
  61. data/examples/agent_continue/workflow.yml +0 -27
  62. data/examples/agent_workflow/README.md +0 -75
  63. data/examples/agent_workflow/apply_refactorings/prompt.md +0 -22
  64. data/examples/agent_workflow/identify_code_smells/prompt.md +0 -15
  65. data/examples/agent_workflow/summarize_improvements/prompt.md +0 -18
  66. data/examples/agent_workflow/workflow.png +0 -0
  67. data/examples/agent_workflow/workflow.yml +0 -16
  68. data/examples/api_workflow/README.md +0 -85
  69. data/examples/api_workflow/fetch_api_data/prompt.md +0 -10
  70. data/examples/api_workflow/generate_report/prompt.md +0 -10
  71. data/examples/api_workflow/prompt.md +0 -10
  72. data/examples/api_workflow/transform_data/prompt.md +0 -10
  73. data/examples/api_workflow/workflow.png +0 -0
  74. data/examples/api_workflow/workflow.yml +0 -30
  75. data/examples/apply_diff_demo/README.md +0 -58
  76. data/examples/apply_diff_demo/apply_simple_change/prompt.md +0 -13
  77. data/examples/apply_diff_demo/create_sample_file/prompt.md +0 -11
  78. data/examples/apply_diff_demo/workflow.yml +0 -24
  79. data/examples/available_tools_demo/workflow.png +0 -0
  80. data/examples/bash_prototyping/README.md +0 -53
  81. data/examples/bash_prototyping/analyze_network/prompt.md +0 -13
  82. data/examples/bash_prototyping/analyze_system/prompt.md +0 -11
  83. data/examples/bash_prototyping/api_testing.png +0 -0
  84. data/examples/bash_prototyping/api_testing.yml +0 -14
  85. data/examples/bash_prototyping/check_processes/prompt.md +0 -11
  86. data/examples/bash_prototyping/generate_report/prompt.md +0 -16
  87. data/examples/bash_prototyping/process_json_response/prompt.md +0 -24
  88. data/examples/bash_prototyping/system_analysis.png +0 -0
  89. data/examples/bash_prototyping/system_analysis.yml +0 -14
  90. data/examples/bash_prototyping/test_public_api/prompt.md +0 -22
  91. data/examples/case_when/README.md +0 -58
  92. data/examples/case_when/detect_language/prompt.md +0 -16
  93. data/examples/case_when/workflow.png +0 -0
  94. data/examples/case_when/workflow.yml +0 -58
  95. data/examples/cmd/README.md +0 -99
  96. data/examples/cmd/analyze_project/prompt.md +0 -57
  97. data/examples/cmd/basic_demo/prompt.md +0 -48
  98. data/examples/cmd/basic_workflow.png +0 -0
  99. data/examples/cmd/basic_workflow.yml +0 -17
  100. data/examples/cmd/check_repository/prompt.md +0 -57
  101. data/examples/cmd/create_and_verify/prompt.md +0 -56
  102. data/examples/cmd/dev_workflow.png +0 -0
  103. data/examples/cmd/dev_workflow.yml +0 -26
  104. data/examples/cmd/explore_project/prompt.md +0 -67
  105. data/examples/cmd/explorer_workflow.png +0 -0
  106. data/examples/cmd/explorer_workflow.yml +0 -21
  107. data/examples/cmd/smart_tool_selection/prompt.md +0 -99
  108. data/examples/coding_agent_with_model.yml +0 -20
  109. data/examples/coding_agent_with_retries.yml +0 -30
  110. data/examples/conditional/README.md +0 -161
  111. data/examples/conditional/check_condition/prompt.md +0 -1
  112. data/examples/conditional/simple_workflow.png +0 -0
  113. data/examples/conditional/simple_workflow.yml +0 -15
  114. data/examples/conditional/workflow.png +0 -0
  115. data/examples/conditional/workflow.yml +0 -23
  116. data/examples/context_management_demo/README.md +0 -43
  117. data/examples/context_management_demo/workflow.yml +0 -42
  118. data/examples/direct_coerce_syntax/README.md +0 -32
  119. data/examples/direct_coerce_syntax/workflow.png +0 -0
  120. data/examples/direct_coerce_syntax/workflow.yml +0 -36
  121. data/examples/dot_notation/README.md +0 -37
  122. data/examples/dot_notation/workflow.png +0 -0
  123. data/examples/dot_notation/workflow.yml +0 -44
  124. data/examples/exit_on_error/README.md +0 -50
  125. data/examples/exit_on_error/analyze_lint_output/prompt.md +0 -9
  126. data/examples/exit_on_error/apply_fixes/prompt.md +0 -2
  127. data/examples/exit_on_error/workflow.png +0 -0
  128. data/examples/exit_on_error/workflow.yml +0 -19
  129. data/examples/grading/README.md +0 -71
  130. data/examples/grading/analyze_coverage/prompt.md +0 -52
  131. data/examples/grading/calculate_final_grade.rb +0 -67
  132. data/examples/grading/format_result.rb +0 -64
  133. data/examples/grading/generate_grades/prompt.md +0 -105
  134. data/examples/grading/generate_recommendations/output.txt +0 -17
  135. data/examples/grading/generate_recommendations/prompt.md +0 -60
  136. data/examples/grading/js_test_runner +0 -31
  137. data/examples/grading/rb_test_runner +0 -19
  138. data/examples/grading/read_dependencies/prompt.md +0 -16
  139. data/examples/grading/run_coverage.rb +0 -54
  140. data/examples/grading/verify_mocks_and_stubs/prompt.md +0 -12
  141. data/examples/grading/verify_test_helpers/prompt.md +0 -53
  142. data/examples/grading/workflow.md +0 -8
  143. data/examples/grading/workflow.png +0 -0
  144. data/examples/grading/workflow.rb.md +0 -6
  145. data/examples/grading/workflow.ts+tsx.md +0 -6
  146. data/examples/grading/workflow.yml +0 -41
  147. data/examples/instrumentation.rb +0 -76
  148. data/examples/interpolation/README.md +0 -50
  149. data/examples/interpolation/analyze_file/prompt.md +0 -1
  150. data/examples/interpolation/analyze_patterns/prompt.md +0 -27
  151. data/examples/interpolation/generate_report_for_js/prompt.md +0 -3
  152. data/examples/interpolation/generate_report_for_rb/prompt.md +0 -3
  153. data/examples/interpolation/sample.js +0 -48
  154. data/examples/interpolation/sample.rb +0 -42
  155. data/examples/interpolation/workflow.md +0 -1
  156. data/examples/interpolation/workflow.png +0 -0
  157. data/examples/interpolation/workflow.yml +0 -21
  158. data/examples/iteration/IMPLEMENTATION.md +0 -88
  159. data/examples/iteration/README.md +0 -68
  160. data/examples/iteration/analyze_complexity/prompt.md +0 -22
  161. data/examples/iteration/generate_recommendations/prompt.md +0 -21
  162. data/examples/iteration/generate_report/prompt.md +0 -129
  163. data/examples/iteration/implement_fix/prompt.md +0 -25
  164. data/examples/iteration/prioritize_issues/prompt.md +0 -24
  165. data/examples/iteration/prompts/analyze_file.md +0 -28
  166. data/examples/iteration/prompts/generate_summary.md +0 -24
  167. data/examples/iteration/prompts/update_report.md +0 -29
  168. data/examples/iteration/prompts/write_report.md +0 -22
  169. data/examples/iteration/read_file/prompt.md +0 -9
  170. data/examples/iteration/select_next_issue/prompt.md +0 -25
  171. data/examples/iteration/simple_workflow.md +0 -39
  172. data/examples/iteration/simple_workflow.yml +0 -58
  173. data/examples/iteration/update_fix_count/prompt.md +0 -26
  174. data/examples/iteration/verify_fix/prompt.md +0 -29
  175. data/examples/iteration/workflow.png +0 -0
  176. data/examples/iteration/workflow.yml +0 -42
  177. data/examples/json_handling/README.md +0 -32
  178. data/examples/json_handling/workflow.png +0 -0
  179. data/examples/json_handling/workflow.yml +0 -52
  180. data/examples/mcp/README.md +0 -223
  181. data/examples/mcp/analyze_changes/prompt.md +0 -8
  182. data/examples/mcp/analyze_issues/prompt.md +0 -4
  183. data/examples/mcp/analyze_schema/prompt.md +0 -4
  184. data/examples/mcp/check_data_quality/prompt.md +0 -5
  185. data/examples/mcp/check_documentation/prompt.md +0 -4
  186. data/examples/mcp/create_recommendations/prompt.md +0 -5
  187. data/examples/mcp/database_workflow.png +0 -0
  188. data/examples/mcp/database_workflow.yml +0 -29
  189. data/examples/mcp/env_demo/workflow.png +0 -0
  190. data/examples/mcp/env_demo/workflow.yml +0 -34
  191. data/examples/mcp/fetch_pr_context/prompt.md +0 -4
  192. data/examples/mcp/filesystem_demo/create_test_file/prompt.md +0 -2
  193. data/examples/mcp/filesystem_demo/list_files/prompt.md +0 -6
  194. data/examples/mcp/filesystem_demo/read_with_mcp/prompt.md +0 -7
  195. data/examples/mcp/filesystem_demo/workflow.png +0 -0
  196. data/examples/mcp/filesystem_demo/workflow.yml +0 -38
  197. data/examples/mcp/generate_insights/prompt.md +0 -4
  198. data/examples/mcp/generate_report/prompt.md +0 -6
  199. data/examples/mcp/generate_review/prompt.md +0 -16
  200. data/examples/mcp/github_workflow.png +0 -0
  201. data/examples/mcp/github_workflow.yml +0 -32
  202. data/examples/mcp/multi_mcp_workflow.png +0 -0
  203. data/examples/mcp/multi_mcp_workflow.yml +0 -58
  204. data/examples/mcp/post_review/prompt.md +0 -3
  205. data/examples/mcp/save_report/prompt.md +0 -6
  206. data/examples/mcp/search_issues/prompt.md +0 -2
  207. data/examples/mcp/summarize/prompt.md +0 -1
  208. data/examples/mcp/test_filesystem/prompt.md +0 -6
  209. data/examples/mcp/test_github/prompt.md +0 -8
  210. data/examples/mcp/test_read/prompt.md +0 -1
  211. data/examples/mcp/workflow.png +0 -0
  212. data/examples/mcp/workflow.yml +0 -35
  213. data/examples/no_model_fallback/README.md +0 -17
  214. data/examples/no_model_fallback/analyze_file/prompt.md +0 -1
  215. data/examples/no_model_fallback/analyze_patterns/prompt.md +0 -27
  216. data/examples/no_model_fallback/generate_report_for_md/prompt.md +0 -10
  217. data/examples/no_model_fallback/generate_report_for_rb/prompt.md +0 -3
  218. data/examples/no_model_fallback/sample.rb +0 -42
  219. data/examples/no_model_fallback/workflow.yml +0 -19
  220. data/examples/openrouter_example/README.md +0 -48
  221. data/examples/openrouter_example/analyze_input/prompt.md +0 -16
  222. data/examples/openrouter_example/generate_response/prompt.md +0 -9
  223. data/examples/openrouter_example/workflow.png +0 -0
  224. data/examples/openrouter_example/workflow.yml +0 -12
  225. data/examples/pre_post_processing/README.md +0 -111
  226. data/examples/pre_post_processing/analyze_test_file/prompt.md +0 -23
  227. data/examples/pre_post_processing/improve_test_coverage/prompt.md +0 -17
  228. data/examples/pre_post_processing/optimize_test_performance/prompt.md +0 -25
  229. data/examples/pre_post_processing/post_processing/aggregate_metrics/prompt.md +0 -31
  230. data/examples/pre_post_processing/post_processing/cleanup_environment/prompt.md +0 -28
  231. data/examples/pre_post_processing/post_processing/generate_summary_report/prompt.md +0 -32
  232. data/examples/pre_post_processing/post_processing/output.txt +0 -24
  233. data/examples/pre_post_processing/pre_processing/gather_baseline_metrics/prompt.md +0 -26
  234. data/examples/pre_post_processing/pre_processing/setup_test_environment/prompt.md +0 -11
  235. data/examples/pre_post_processing/validate_changes/prompt.md +0 -24
  236. data/examples/pre_post_processing/workflow.png +0 -0
  237. data/examples/pre_post_processing/workflow.yml +0 -21
  238. data/examples/retry/workflow.yml +0 -23
  239. data/examples/rspec_to_minitest/README.md +0 -68
  240. data/examples/rspec_to_minitest/analyze_spec/prompt.md +0 -30
  241. data/examples/rspec_to_minitest/create_minitest/prompt.md +0 -33
  242. data/examples/rspec_to_minitest/run_and_improve/prompt.md +0 -35
  243. data/examples/rspec_to_minitest/workflow.md +0 -10
  244. data/examples/rspec_to_minitest/workflow.png +0 -0
  245. data/examples/rspec_to_minitest/workflow.yml +0 -40
  246. data/examples/shared_config/README.md +0 -52
  247. data/examples/shared_config/example_with_shared_config/workflow.png +0 -0
  248. data/examples/shared_config/example_with_shared_config/workflow.yml +0 -6
  249. data/examples/shared_config/shared.png +0 -0
  250. data/examples/shared_config/shared.yml +0 -7
  251. data/examples/single_target_prepost/README.md +0 -36
  252. data/examples/single_target_prepost/post_processing/output.txt +0 -27
  253. data/examples/single_target_prepost/pre_processing/gather_dependencies/prompt.md +0 -11
  254. data/examples/single_target_prepost/workflow.png +0 -0
  255. data/examples/single_target_prepost/workflow.yml +0 -20
  256. data/examples/smart_coercion_defaults/README.md +0 -65
  257. data/examples/smart_coercion_defaults/workflow.png +0 -0
  258. data/examples/smart_coercion_defaults/workflow.yml +0 -44
  259. data/examples/step_configuration/README.md +0 -84
  260. data/examples/step_configuration/workflow.png +0 -0
  261. data/examples/step_configuration/workflow.yml +0 -57
  262. data/examples/swarm_example.yml +0 -25
  263. data/examples/tool_config_example/README.md +0 -109
  264. data/examples/tool_config_example/example_step/prompt.md +0 -42
  265. data/examples/tool_config_example/workflow.png +0 -0
  266. data/examples/tool_config_example/workflow.yml +0 -17
  267. data/examples/user_input/README.md +0 -90
  268. data/examples/user_input/funny_name/create_backstory/prompt.md +0 -10
  269. data/examples/user_input/funny_name/workflow.png +0 -0
  270. data/examples/user_input/funny_name/workflow.yml +0 -26
  271. data/examples/user_input/generate_summary/prompt.md +0 -11
  272. data/examples/user_input/simple_input_demo/workflow.png +0 -0
  273. data/examples/user_input/simple_input_demo/workflow.yml +0 -35
  274. data/examples/user_input/survey_workflow.png +0 -0
  275. data/examples/user_input/survey_workflow.yml +0 -71
  276. data/examples/user_input/welcome_message/prompt.md +0 -3
  277. data/examples/user_input/workflow.png +0 -0
  278. data/examples/user_input/workflow.yml +0 -73
  279. data/examples/workflow_generator/README.md +0 -27
  280. data/examples/workflow_generator/analyze_user_request/prompt.md +0 -34
  281. data/examples/workflow_generator/create_workflow_files/prompt.md +0 -32
  282. data/examples/workflow_generator/get_user_input/prompt.md +0 -14
  283. data/examples/workflow_generator/info_from_roast.rb +0 -22
  284. data/examples/workflow_generator/workflow.png +0 -0
  285. data/examples/workflow_generator/workflow.yml +0 -34
  286. data/lib/roast/helpers/timeout_handler.rb +0 -89
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b30690bd6345f217ff752e69865abf269792917e6463bbb70745a812ec15a4b
4
- data.tar.gz: de3732ccd3322fb8ff782d10e52168e0e4292fdb21596ff0eff7201532498c8d
3
+ metadata.gz: '0019e9658942394301e91581ecc45b259c76f1999f17cc8f1ddf4f97692ea639'
4
+ data.tar.gz: feb3d19b4316a2ea2751347a614f816c22cbd53182821eed661488c760a9897a
5
5
  SHA512:
6
- metadata.gz: ba81d35b6484ffbdb3bb5aaed1baacbbc684033e62df23131c6d0bcb7833bc0902600f84a462fb451213d56c43862e49e8db5bfd150fb4a7520b66cd1e078d85
7
- data.tar.gz: 01b3d585d72528c5d8382a13edd379f6af249d75059564adb8eb33671b2dfb252749a09f8e2d8a551b09fad048b735960fcf8f2cf37d1c0f1d06aa758feee50d
6
+ metadata.gz: b8c5442247e1e0aae1cb7aa4509a3724fb1867bf9b05b15cfe1da4a8287db1db8229026ad3be66cc450b57650e8dd00d4eca632021680f3320d2bbf8dd19fc89
7
+ data.tar.gz: bbf18e07720a9cfe48abc2780f0f2cdba7d091bb3b2103318556910693dc54b09980ac0f2133876a811700345cdb6fa9e32d87cd4622f9039fe1bbbb7ffe3151
@@ -29,6 +29,8 @@ jobs:
29
29
  with:
30
30
  ruby-version: ${{ matrix.ruby }}
31
31
  bundler-cache: true
32
- - run: bundle exec rake ci
32
+ - run: bundle exec rake minitest_old
33
+ - run: bundle exec rake minitest_functional
34
+ - run: bundle exec rake rubocop_ci
33
35
  - run: bin/srb tc
34
36
 
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
+ .DS_Store
2
+
1
3
  /.bundle/
2
4
  /.yardoc
3
5
  /_yardoc/
@@ -16,3 +18,8 @@
16
18
  gemfiles/*.lock
17
19
  bin/claude-swarm
18
20
  *.gem
21
+ coverage
22
+
23
+ .dev
24
+ dev.yml
25
+ .shadowenv
data/.rubocop.yml CHANGED
@@ -1,5 +1,8 @@
1
1
  inherit_from: .rubocop_todo.yml
2
2
 
3
+ require:
4
+ - ./rubocop/cop/roast
5
+
3
6
  plugins:
4
7
  - rubocop-sorbet
5
8
 
@@ -23,3 +26,14 @@ Sorbet/FalseSigil:
23
26
  Exclude:
24
27
  - "test/**/*"
25
28
  - "examples/**/*"
29
+
30
+ Roast/UseCmdRunner:
31
+ Enabled: true
32
+ Exclude:
33
+ - 'lib/roast/helpers/cmd_runner.rb'
34
+ - 'roast.gemspec'
35
+
36
+ Style/MethodCallWithArgsParentheses:
37
+ Enabled: true
38
+ Exclude:
39
+ - 'test/**/*.rb'
data/Gemfile CHANGED
@@ -16,9 +16,10 @@ gem "mocha"
16
16
  gem "rake", require: false
17
17
  gem "rubocop-shopify", require: false
18
18
  gem "rubocop-sorbet", require: false
19
+ gem "simplecov", require: false
20
+ gem "minitest-rg"
19
21
  gem "vcr", require: false
20
22
  gem "webmock", require: false
21
- gem "minitest-rg"
22
23
 
23
24
  gem "sorbet", require: false
24
25
  gem "tapioca", require: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- roast-ai (0.4.6)
4
+ roast-ai (0.4.8)
5
5
  activesupport (>= 7.0)
6
6
  cli-kit (~> 5.0)
7
7
  cli-ui (= 2.3.0)
@@ -50,6 +50,7 @@ GEM
50
50
  bigdecimal
51
51
  rexml
52
52
  diff-lcs (1.6.2)
53
+ docile (1.4.1)
53
54
  dotenv (3.1.8)
54
55
  drb (2.2.3)
55
56
  dry-configurable (1.3.0)
@@ -214,6 +215,12 @@ GEM
214
215
  ruby2_keywords (0.0.5)
215
216
  securerandom (0.4.1)
216
217
  shellany (0.0.1)
218
+ simplecov (0.22.0)
219
+ docile (~> 1.1)
220
+ simplecov-html (~> 0.11)
221
+ simplecov_json_formatter (~> 0.1)
222
+ simplecov-html (0.13.2)
223
+ simplecov_json_formatter (0.1.4)
217
224
  sorbet (0.5.12414)
218
225
  sorbet-static (= 0.5.12414)
219
226
  sorbet-runtime (0.5.12414)
@@ -277,6 +284,7 @@ DEPENDENCIES
277
284
  roast-ai!
278
285
  rubocop-shopify
279
286
  rubocop-sorbet
287
+ simplecov
280
288
  sorbet
281
289
  tapioca
282
290
  vcr
data/Rakefile CHANGED
@@ -4,20 +4,32 @@ require "bundler/gem_tasks"
4
4
  require "rubocop/rake_task"
5
5
  require "rake/testtask"
6
6
 
7
- Rake::TestTask.new(:minitest) do |t|
7
+ Rake::TestTask.new(:minitest_all) do |t|
8
8
  t.libs << "test"
9
9
  t.libs << "lib"
10
10
  t.test_files = FileList["test/**/*_test.rb"]
11
11
  end
12
12
 
13
- task test: [:minitest]
13
+ Rake::TestTask.new(:minitest_functional) do |t|
14
+ t.libs << "test"
15
+ t.libs << "lib"
16
+ t.test_files = FileList["test/functional/**/*_test.rb"]
17
+ end
14
18
 
15
- RuboCop::RakeTask.new(:rubocop_ci)
19
+ Rake::TestTask.new(:minitest_old) do |t|
20
+ t.libs << "test"
21
+ t.libs << "lib"
22
+ t.test_files = FileList["test/functional/**/*_test.rb"]
23
+ end
16
24
 
17
- task ci: [:test, :rubocop_ci]
25
+ task test: [:minitest_all]
26
+
27
+ RuboCop::RakeTask.new(:rubocop_ci)
18
28
 
19
29
  RuboCop::RakeTask.new(:rubocop) do |task|
20
30
  task.options = ["--autocorrect"]
21
31
  end
22
32
 
23
33
  task default: [:test, :rubocop]
34
+
35
+ task lint: [:rubocop]
@@ -0,0 +1,9 @@
1
+ These examples demonstrate various usages and features of Roast. They are automatically tested and verified by functional tests (see `test/functional/roast_examples_test.rb`).
2
+
3
+ ### available_tools_demo
4
+
5
+ Demonstrates use of Roast tool implementations in LLM steps. Steps are implemented with `prompt.md` in directories that match the step name
6
+
7
+ ### basic_prompt_workflow
8
+
9
+ Demonstrates using LLM steps to glean insights from a provided data file (`test/fixtures/sample_data/skateboard_orders.csv`).
@@ -1,4 +1,4 @@
1
- model: anthropic:claude-opus-4
1
+ model: gpt-4o-mini
2
2
 
3
3
  tools:
4
4
  - Roast::Tools::Grep
@@ -29,4 +29,4 @@ analyze_files:
29
29
  write_summary:
30
30
  available_tools:
31
31
  - write_file
32
- - echo
32
+ - echo
@@ -0,0 +1 @@
1
+ You are a skateboard shop owner looking for insights on their business.
@@ -0,0 +1,14 @@
1
+ name: Analyze My Business
2
+ description: Examines a CSV list of orders for insights
3
+
4
+ # Default model for all steps
5
+ model: google:gemini-2.5-flash
6
+ tools:
7
+ - Roast::Tools::ReadFile
8
+ - Roast::Tools::Grep
9
+
10
+ steps:
11
+ - Read the provided CSV files.
12
+ - "Where are most of my customers from?"
13
+ - "What is my most popular product?"
14
+ - Summarize the insights from the report.
@@ -19,7 +19,8 @@ module Roast
19
19
  # Define methods to be used in workflows below.
20
20
 
21
21
  def shell(command_string)
22
- puts %x(#{command_string})
22
+ output, _status = Roast::Helpers::CmdRunner.capture2e(command_string)
23
+ puts output
23
24
  end
24
25
  end
25
26
  end
@@ -0,0 +1,199 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ module Helpers
6
+ class CmdRunner
7
+ DEFAULT_TIMEOUT = 30
8
+ MAX_TIMEOUT = 3600 # 1 hour
9
+
10
+ @child_processes = {}
11
+ @child_processes_mutex = Mutex.new
12
+
13
+ class << self
14
+ #: (*untyped, **untyped) -> [String, Process::Status]
15
+ def capture2(*args, **options)
16
+ args = args #: as untyped
17
+ stdout, _stderr, status = capture3(*args, **options)
18
+ [stdout, status]
19
+ end
20
+
21
+ #: (*untyped, **untyped) -> [String, Process::Status]
22
+ def capture2e(*args, **options)
23
+ args = args #: as untyped
24
+ stdout, stderr, status = capture3(*args, **options)
25
+ combined_output = stdout + stderr
26
+ [combined_output, status]
27
+ end
28
+
29
+ #: (*untyped, **untyped) -> [String?, String?, Process::Status?]
30
+ def capture3(*args, **options)
31
+ args = args #: as untyped
32
+ popen3(*args, **options) do |stdin, stdout, stderr, wait_thr|
33
+ stdin.close # Prevent hanging on stdin-waiting commands
34
+
35
+ stdout_thread = threaded_read(stdout)
36
+ stderr_thread = threaded_read(stderr)
37
+
38
+ [stdout_thread.value, stderr_thread.value, wait_thr.value]
39
+ end
40
+ end
41
+
42
+ #: (*untyped, **untyped) -> bool
43
+ def system(*args, **options)
44
+ args = args #: as untyped
45
+ popen3(*args, **options) do |stdin, stdout, stderr, wait_thr|
46
+ stdin.close # Prevent hanging on stdin-waiting commands
47
+
48
+ stdout_thread = threaded_stream(from: stdout, to: $stdout)
49
+ stderr_thread = threaded_stream(from: stderr, to: $stderr)
50
+
51
+ stdout_thread.join
52
+ stderr_thread.join
53
+
54
+ wait_thr.value.success?
55
+ end
56
+ end
57
+
58
+ #: (*untyped, **untyped) ?{ (IO, IO, IO, Thread) -> untyped } -> [IO, IO, IO, Thread] | untyped
59
+ def popen3(*args, **options, &block)
60
+ args = args #: as untyped
61
+
62
+ timeout = options.delete(:timeout)
63
+ validate_timeout(timeout) unless timeout.nil?
64
+
65
+ raise ArgumentError, "Timeout provided but no block given" if !timeout.nil? && !block_given?
66
+
67
+ # Mirror Open3.popen3 behavior - if no block, return the IO objects and thread
68
+ unless block_given?
69
+ stdin, stdout, stderr, wait_thr = Open3.popen3(*args, **options)
70
+ track_child_process(wait_thr.pid, presentable_command(args))
71
+ return [stdin, stdout, stderr, wait_thr]
72
+ end
73
+
74
+ Open3.popen3(*args, **options) do |stdin, stdout, stderr, wait_thr|
75
+ track_child_process(wait_thr.pid, presentable_command(args))
76
+
77
+ runnable = proc { yield(stdin, stdout, stderr, wait_thr) } #: Proc
78
+ timeout.nil? ? runnable.call : Timeout.timeout(timeout, &runnable)
79
+ rescue Timeout::Error => e
80
+ raise e.class, "Command '#{presentable_command(args)}' timed out after #{timeout} seconds: #{e.message}"
81
+ ensure
82
+ cleanup_child_process(wait_thr.pid) unless wait_thr.nil?
83
+ end
84
+ end
85
+
86
+ #: -> void
87
+ def cleanup_all_children
88
+ Thread.new do # Thread to avoid issues with calling a mutex in a signal handler
89
+ child_processes = all_child_processes
90
+ Thread.current.exit if child_processes.empty?
91
+
92
+ child_processes.each do |pid, info|
93
+ Roast::Helpers::Logger.info("Cleaning up PID #{pid}: #{info[:command]}")
94
+ cleanup_child_process(pid)
95
+ end
96
+ end.join
97
+ end
98
+
99
+ #: (Integer?) -> Integer
100
+ def normalize_timeout(timeout)
101
+ return DEFAULT_TIMEOUT if timeout.nil? || timeout <= 0
102
+
103
+ [timeout, MAX_TIMEOUT].min
104
+ end
105
+
106
+ private
107
+
108
+ #: (Integer) -> void
109
+ def validate_timeout(timeout)
110
+ if timeout <= 0 || timeout > MAX_TIMEOUT
111
+ raise ArgumentError, "Invalid timeout value: #{timeout.inspect}"
112
+ end
113
+ end
114
+
115
+ #: (Array) -> String
116
+ def presentable_command(args)
117
+ args.flatten.map(&:to_s).join(" ")
118
+ end
119
+
120
+ #: (IO) -> Thread
121
+ def threaded_read(stream)
122
+ Thread.new do
123
+ buffer = ""
124
+ stream.each_line do |line|
125
+ buffer += line
126
+ end
127
+ buffer
128
+ rescue IOError => e
129
+ Roast::Helpers::Logger.debug("IOError while capturing output: #{e.message}")
130
+ end
131
+ end
132
+
133
+ #: (from: IO, to: IO) -> Thread
134
+ def threaded_stream(from:, to:)
135
+ Thread.new do
136
+ from.each_line do |line|
137
+ to.puts(line)
138
+ end
139
+ rescue IOError => e
140
+ Roast::Helpers::Logger.debug("IOError while streaming output: #{e.message}")
141
+ end
142
+ end
143
+
144
+ #: (Integer, String) -> void
145
+ def track_child_process(pid, command)
146
+ @child_processes_mutex.synchronize do
147
+ @child_processes[pid] = {
148
+ command: command,
149
+ started_at: Time.now,
150
+ }
151
+ end
152
+ end
153
+
154
+ #: (Integer) -> void
155
+ def untrack_child_process(pid)
156
+ @child_processes_mutex.synchronize { @child_processes.delete(pid) }
157
+ end
158
+
159
+ #: -> Hash[Integer, { command: String, started_at: Time }]
160
+ def all_child_processes
161
+ @child_processes_mutex.synchronize { @child_processes.dup }
162
+ end
163
+
164
+ #: (Integer) -> void
165
+ def cleanup_child_process(pid)
166
+ untrack_child_process(pid)
167
+
168
+ return unless process_running?(pid)
169
+
170
+ [0.1, 0.2, 0.5].each do |sleep_time|
171
+ Process.kill("TERM", pid)
172
+ break unless process_running?(pid)
173
+
174
+ sleep(sleep_time) # Grace period to let the process terminate
175
+ end
176
+
177
+ # Force kill if still alive
178
+ Process.kill("KILL", pid) if process_running?(pid)
179
+ rescue Errno::ESRCH
180
+ # Process already terminated, which is fine
181
+ rescue Errno::EPERM
182
+ # Permission denied - process may be owned by different user
183
+ Roast::Helpers::Logger.debug("Could not kill process #{pid}: Permission denied")
184
+ rescue => e
185
+ # Catch any other unexpected errors during cleanup
186
+ Roast::Helpers::Logger.debug("Unexpected error during process cleanup: #{e.message}")
187
+ end
188
+
189
+ #: (Integer) -> bool
190
+ def process_running?(pid)
191
+ Process.getpgid(pid)
192
+ true
193
+ rescue Errno::ESRCH
194
+ false
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
@@ -7,7 +7,7 @@ module Roast
7
7
  def config_root(starting_path = Dir.pwd, ending_path = File.dirname(Dir.home))
8
8
  paths = []
9
9
  candidate = starting_path
10
- while candidate != ending_path
10
+ while candidate != ending_path && candidate != "/"
11
11
  paths << File.join(candidate, ".roast")
12
12
  candidate = File.dirname(candidate)
13
13
  end
@@ -93,7 +93,7 @@ module Roast
93
93
  File.write(temp_file, updated_content)
94
94
 
95
95
  # Run git diff
96
- diff_output = %x(git diff --no-index --no-prefix "#{file_path}" "#{temp_file}" 2>/dev/null)
96
+ diff_output, _status = Roast::Helpers::CmdRunner.capture2e("git", "diff", "--no-index", "--no-prefix", file_path, temp_file)
97
97
 
98
98
  if diff_output.empty?
99
99
  Roast::Helpers::Logger.info("No differences found (files are identical)\n")
@@ -3,7 +3,6 @@
3
3
 
4
4
  require "English"
5
5
  require "roast/helpers/logger"
6
- require "roast/helpers/timeout_handler"
7
6
 
8
7
  module Roast
9
8
  module Tools
@@ -33,13 +32,14 @@ module Roast
33
32
  Roast::Helpers::Logger.warn("⚠️ WARNING: Unrestricted bash execution - use with caution!\n")
34
33
  end
35
34
 
36
- result, exit_status = Roast::Helpers::TimeoutHandler.call(
35
+ timeout = Roast::Helpers::CmdRunner.normalize_timeout(timeout)
36
+
37
+ result, status = Roast::Helpers::CmdRunner.capture2e(
37
38
  "#{command} 2>&1",
38
39
  timeout: timeout,
39
- working_directory: Dir.pwd,
40
40
  )
41
41
 
42
- format_output(command, result, exit_status)
42
+ format_output(command, result, status.exitstatus)
43
43
  rescue Timeout::Error => e
44
44
  Roast::Helpers::Logger.error(e.message + "\n")
45
45
  e.message
@@ -3,7 +3,6 @@
3
3
 
4
4
  require "English"
5
5
  require "roast/helpers/logger"
6
- require "roast/helpers/timeout_handler"
7
6
 
8
7
  module Roast
9
8
  module Tools
@@ -140,7 +139,7 @@ module Roast
140
139
  end
141
140
 
142
141
  def execute_command(command, command_prefix, timeout)
143
- timeout = Roast::Helpers::TimeoutHandler.validate_timeout(timeout)
142
+ timeout = Roast::Helpers::CmdRunner.normalize_timeout(timeout)
144
143
 
145
144
  full_command = if command_prefix == "dev"
146
145
  "bash -l -c '#{command.gsub("'", "\\'")}'"
@@ -148,13 +147,12 @@ module Roast
148
147
  command
149
148
  end
150
149
 
151
- result, exit_status = Roast::Helpers::TimeoutHandler.call(
150
+ result, status = Roast::Helpers::CmdRunner.capture2e(
152
151
  full_command,
153
152
  timeout: timeout,
154
- working_directory: Dir.pwd,
155
153
  )
156
154
 
157
- format_output(command, result, exit_status)
155
+ format_output(command, result, status.exitstatus)
158
156
  rescue Timeout::Error => e
159
157
  Roast::Helpers::Logger.error(e.message + "\n")
160
158
  e.message
@@ -105,7 +105,7 @@ module Roast
105
105
  command = "cat #{temp_file.path} | #{command_to_run}"
106
106
  result = ""
107
107
 
108
- Open3.popen3(command) do |stdin, stdout, stderr, wait_thread|
108
+ Roast::Helpers::CmdRunner.popen3(command) do |stdin, stdout, stderr, wait_thread|
109
109
  stdin.close
110
110
  if expect_json_output
111
111
  stdout.each_line do |line|
@@ -28,13 +28,17 @@ module Roast
28
28
  def call(string)
29
29
  Roast::Helpers::Logger.info("🔍 Grepping for string: #{string}\n")
30
30
 
31
- unless system("command -v rg >/dev/null 2>&1")
31
+ # Check if ripgrep is available by trying to run it with --version
32
+ unless Roast::Helpers::CmdRunner.system("rg --version > /dev/null 2>&1")
32
33
  raise "ripgrep is not available. Please install it using your package manager (e.g., brew install rg) and make sure it's on your PATH."
33
34
  end
34
35
 
35
36
  # Use Open3 to safely pass the string as an argument, avoiding shell injection
36
37
  cmd = ["rg", "-C", "4", "--trim", "--color=never", "--heading", "-F", "--", string, "."]
37
- stdout, _stderr, _status = Open3.capture3(*cmd)
38
+ stdout, stderr, status = Roast::Helpers::CmdRunner.capture3(*cmd)
39
+ unless status.success?
40
+ return "Error grepping for string: Command failed: #{stderr}"
41
+ end
38
42
 
39
43
  # Limit output to MAX_RESULT_LINES
40
44
  lines = stdout.lines
@@ -34,7 +34,8 @@ module Roast
34
34
  path = File.expand_path(path)
35
35
  Roast::Helpers::Logger.info("📖 Reading file: #{path}\n")
36
36
  if File.directory?(path)
37
- %x(ls -la #{path})
37
+ output, _status = Roast::Helpers::CmdRunner.capture2e("ls", "-la", path)
38
+ output
38
39
  else
39
40
  File.read(path)
40
41
  end
@@ -85,14 +85,9 @@ module Roast
85
85
  # Build the swarm command with proper escaping
86
86
  command = build_swarm_command(prompt, config_path)
87
87
 
88
- result = ""
89
-
90
88
  # Execute the command directly with the prompt included
91
- IO.popen(command, err: [:child, :out]) do |io|
92
- result = io.read
93
- end
94
-
95
- exit_status = $CHILD_STATUS.exitstatus
89
+ result, status = Roast::Helpers::CmdRunner.capture2e(command)
90
+ exit_status = status.exitstatus
96
91
 
97
92
  format_output(command, result, exit_status)
98
93
  end
data/lib/roast/tools.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "roast/helpers/cmd_runner"
5
+
4
6
  module Roast
5
7
  # @requires_ancestor: Kernel
6
8
  module Tools
@@ -33,7 +35,14 @@ module Roast
33
35
  Signal.trap("INT") do
34
36
  puts "\n\nCaught CTRL-C! Printing before exiting:\n"
35
37
  puts JSON.pretty_generate(object_to_inspect)
36
- exit(1)
38
+
39
+ begin
40
+ Roast::Helpers::CmdRunner.cleanup_all_children
41
+ rescue => e
42
+ puts "Error interrupting tracked child processes: #{e.message}"
43
+ end
44
+
45
+ exit(130)
37
46
  end
38
47
  end
39
48
 
data/lib/roast/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Roast
5
- VERSION = "0.4.6"
5
+ VERSION = "0.4.8"
6
6
  end
@@ -10,10 +10,9 @@ module Roast
10
10
  delegate :append_to_final_output, :transcript, to: :workflow
11
11
  delegate_missing_to :workflow
12
12
 
13
- # TODO: is this really the model we want to default to, and is this the right place to set it?
14
- def initialize(workflow, model: "anthropic:claude-opus-4", name: nil, context_path: nil)
13
+ def initialize(workflow, model: nil, name: nil, context_path: nil)
15
14
  @workflow = workflow
16
- @model = model
15
+ @model = model || workflow.model || StepLoader::DEFAULT_MODEL
17
16
  @name = normalize_name(name)
18
17
  @context_path = context_path || ContextPathResolver.resolve(self.class)
19
18
  @print_response = false
@@ -24,14 +24,14 @@ module Roast
24
24
  def execute(command_string, exit_on_error: true)
25
25
  command = extract_command(command_string)
26
26
 
27
- output = %x(#{command})
28
- exit_status = $CHILD_STATUS.exitstatus
27
+ output, status = Roast::Helpers::CmdRunner.capture2e(command)
28
+ exit_status = status.exitstatus
29
29
 
30
30
  handle_execution_result(
31
31
  command: command,
32
32
  output: output,
33
33
  exit_status: exit_status,
34
- success: $CHILD_STATUS.success?,
34
+ success: status.success?,
35
35
  exit_on_error: exit_on_error,
36
36
  )
37
37
  rescue ArgumentError, CommandExecutionError
@@ -52,7 +52,7 @@ module Roast
52
52
  def process_shell_command(command)
53
53
  # If it's a bash command with the $(command) syntax
54
54
  if command =~ /^\$\((.*)\)$/
55
- return Open3.capture2e({}, ::Regexp.last_match(1).to_s).first.strip
55
+ return Roast::Helpers::CmdRunner.capture2e({}, ::Regexp.last_match(1).to_s).first.strip
56
56
  end
57
57
 
58
58
  # Not a shell command, return as is
@@ -48,7 +48,7 @@ module Roast
48
48
  log_debug("Executing shell script: #{cmd}")
49
49
  log_debug("Environment: #{env.inspect}")
50
50
 
51
- Open3.capture3(env, cmd, chdir: Dir.pwd)
51
+ Roast::Helpers::CmdRunner.capture3(env, cmd, chdir: Dir.pwd)
52
52
  end
53
53
 
54
54
  def build_command
@@ -208,8 +208,8 @@ module Roast
208
208
  def configure_step(step, step_name, is_last_step: nil)
209
209
  step_config = config_hash[step_name]
210
210
 
211
- # Always set the model
212
- step.model = determine_model(step_config)
211
+ # Only set the model if explicitly specified for this step
212
+ step.model = step_config["model"] if step_config&.key?("model")
213
213
 
214
214
  # Pass resource to step if supported
215
215
  step.resource = workflow.resource if step.respond_to?(:resource=)
@@ -223,11 +223,6 @@ module Roast
223
223
  end
224
224
  end
225
225
 
226
- # Determine which model to use for the step
227
- def determine_model(step_config)
228
- step_config&.dig("model") || config_hash["model"] || DEFAULT_MODEL
229
- end
230
-
231
226
  # Apply configuration settings to a step
232
227
  def apply_step_configuration(step, step_config)
233
228
  step.print_response = step_config["print_response"] if step_config.key?("print_response")