roast-ai 0.4.9 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/docs/write-comments.md +36 -0
  3. data/.github/CODEOWNERS +1 -1
  4. data/.github/workflows/ci.yaml +10 -6
  5. data/.gitignore +0 -1
  6. data/.rubocop.yml +7 -1
  7. data/CLAUDE.md +2 -2
  8. data/CONTRIBUTING.md +2 -0
  9. data/Gemfile +18 -18
  10. data/Gemfile.lock +46 -57
  11. data/README.md +118 -1432
  12. data/README_LEGACY.md +1464 -0
  13. data/Rakefile +39 -4
  14. data/dev.yml +29 -0
  15. data/dsl/agent_sessions.rb +20 -0
  16. data/dsl/async_cogs.rb +49 -0
  17. data/dsl/async_cogs_complex.rb +67 -0
  18. data/dsl/call.rb +44 -0
  19. data/dsl/collect_from.rb +72 -0
  20. data/dsl/demo/Gemfile +4 -0
  21. data/dsl/demo/Gemfile.lock +120 -0
  22. data/dsl/demo/cogs/local.rb +15 -0
  23. data/dsl/demo/simple_external_cog.rb +17 -0
  24. data/dsl/json_output.rb +28 -0
  25. data/dsl/map.rb +55 -0
  26. data/dsl/map_reduce.rb +37 -0
  27. data/dsl/map_with_index.rb +49 -0
  28. data/dsl/next_break.rb +40 -0
  29. data/dsl/next_break_parallel.rb +44 -0
  30. data/dsl/outputs.rb +39 -0
  31. data/dsl/outputs_bang.rb +36 -0
  32. data/dsl/parallel_map.rb +37 -0
  33. data/dsl/plugin-gem-example/.gitignore +8 -0
  34. data/dsl/plugin-gem-example/Gemfile +13 -0
  35. data/dsl/plugin-gem-example/Gemfile.lock +178 -0
  36. data/dsl/plugin-gem-example/lib/other.rb +17 -0
  37. data/dsl/plugin-gem-example/lib/plugin_gem_example.rb +5 -0
  38. data/dsl/plugin-gem-example/lib/simple.rb +15 -0
  39. data/dsl/plugin-gem-example/lib/version.rb +10 -0
  40. data/dsl/plugin-gem-example/plugin-gem-example.gemspec +28 -0
  41. data/dsl/prompts/simple_prompt.md.erb +3 -0
  42. data/dsl/prototype.rb +10 -4
  43. data/dsl/repeat_loop_results.rb +53 -0
  44. data/dsl/ruby_cog.rb +72 -0
  45. data/dsl/simple_agent.rb +18 -0
  46. data/dsl/simple_chat.rb +26 -0
  47. data/dsl/simple_repeat.rb +29 -0
  48. data/dsl/skip.rb +36 -0
  49. data/dsl/step_communication.rb +10 -5
  50. data/dsl/targets_and_params.rb +57 -0
  51. data/dsl/temperature.rb +17 -0
  52. data/dsl/temporary_directory.rb +22 -0
  53. data/dsl/tutorial/01_your_first_workflow/README.md +179 -0
  54. data/dsl/tutorial/01_your_first_workflow/configured_chat.rb +33 -0
  55. data/dsl/tutorial/01_your_first_workflow/hello.rb +23 -0
  56. data/dsl/tutorial/02_chaining_cogs/README.md +310 -0
  57. data/dsl/tutorial/02_chaining_cogs/code_review.rb +104 -0
  58. data/dsl/tutorial/02_chaining_cogs/session_resumption.rb +92 -0
  59. data/dsl/tutorial/02_chaining_cogs/simple_chain.rb +84 -0
  60. data/dsl/tutorial/03_targets_and_params/README.md +230 -0
  61. data/dsl/tutorial/03_targets_and_params/multiple_targets.rb +65 -0
  62. data/dsl/tutorial/03_targets_and_params/single_target.rb +65 -0
  63. data/dsl/tutorial/04_configuration_options/README.md +209 -0
  64. data/dsl/tutorial/04_configuration_options/control_display_and_temperature.rb +104 -0
  65. data/dsl/tutorial/04_configuration_options/simple_config.rb +68 -0
  66. data/dsl/tutorial/05_control_flow/README.md +156 -0
  67. data/dsl/tutorial/05_control_flow/conditional_execution.rb +62 -0
  68. data/dsl/tutorial/05_control_flow/handling_failures.rb +77 -0
  69. data/dsl/tutorial/06_reusable_scopes/README.md +172 -0
  70. data/dsl/tutorial/06_reusable_scopes/accessing_scope_outputs.rb +126 -0
  71. data/dsl/tutorial/06_reusable_scopes/basic_scope.rb +63 -0
  72. data/dsl/tutorial/06_reusable_scopes/parameterized_scope.rb +78 -0
  73. data/dsl/tutorial/07_processing_collections/README.md +152 -0
  74. data/dsl/tutorial/07_processing_collections/basic_map.rb +70 -0
  75. data/dsl/tutorial/07_processing_collections/parallel_map.rb +74 -0
  76. data/dsl/tutorial/08_iterative_workflows/README.md +231 -0
  77. data/dsl/tutorial/08_iterative_workflows/basic_repeat.rb +57 -0
  78. data/dsl/tutorial/08_iterative_workflows/conditional_break.rb +57 -0
  79. data/dsl/tutorial/09_async_cogs/README.md +197 -0
  80. data/dsl/tutorial/09_async_cogs/basic_async.rb +38 -0
  81. data/dsl/tutorial/README.md +222 -0
  82. data/dsl/working_directory.rb +16 -0
  83. data/exe/roast +1 -1
  84. data/internal/documentation/architectural-notes.md +115 -0
  85. data/internal/documentation/doc-comments-external.md +686 -0
  86. data/internal/documentation/doc-comments-internal.md +342 -0
  87. data/internal/documentation/doc-comments.md +211 -0
  88. data/lib/roast/dsl/cog/config.rb +280 -4
  89. data/lib/roast/dsl/cog/input.rb +73 -0
  90. data/lib/roast/dsl/cog/output.rb +313 -0
  91. data/lib/roast/dsl/cog/registry.rb +71 -0
  92. data/lib/roast/dsl/cog/stack.rb +3 -2
  93. data/lib/roast/dsl/cog/store.rb +11 -8
  94. data/lib/roast/dsl/cog.rb +108 -31
  95. data/lib/roast/dsl/cog_input_context.rb +44 -0
  96. data/lib/roast/dsl/cog_input_manager.rb +156 -0
  97. data/lib/roast/dsl/cogs/agent/config.rb +465 -0
  98. data/lib/roast/dsl/cogs/agent/input.rb +81 -0
  99. data/lib/roast/dsl/cogs/agent/output.rb +59 -0
  100. data/lib/roast/dsl/cogs/agent/provider.rb +51 -0
  101. data/lib/roast/dsl/cogs/agent/providers/claude/claude_invocation.rb +185 -0
  102. data/lib/roast/dsl/cogs/agent/providers/claude/message.rb +73 -0
  103. data/lib/roast/dsl/cogs/agent/providers/claude/messages/assistant_message.rb +36 -0
  104. data/lib/roast/dsl/cogs/agent/providers/claude/messages/result_message.rb +61 -0
  105. data/lib/roast/dsl/cogs/agent/providers/claude/messages/system_message.rb +47 -0
  106. data/lib/roast/dsl/cogs/agent/providers/claude/messages/text_message.rb +36 -0
  107. data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_result_message.rb +47 -0
  108. data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_use_message.rb +46 -0
  109. data/lib/roast/dsl/cogs/agent/providers/claude/messages/unknown_message.rb +27 -0
  110. data/lib/roast/dsl/cogs/agent/providers/claude/messages/user_message.rb +37 -0
  111. data/lib/roast/dsl/cogs/agent/providers/claude/tool_result.rb +51 -0
  112. data/lib/roast/dsl/cogs/agent/providers/claude/tool_use.rb +48 -0
  113. data/lib/roast/dsl/cogs/agent/providers/claude.rb +31 -0
  114. data/lib/roast/dsl/cogs/agent/stats.rb +92 -0
  115. data/lib/roast/dsl/cogs/agent/usage.rb +62 -0
  116. data/lib/roast/dsl/cogs/agent.rb +75 -0
  117. data/lib/roast/dsl/cogs/chat/config.rb +453 -0
  118. data/lib/roast/dsl/cogs/chat/input.rb +92 -0
  119. data/lib/roast/dsl/cogs/chat/output.rb +64 -0
  120. data/lib/roast/dsl/cogs/chat/session.rb +68 -0
  121. data/lib/roast/dsl/cogs/chat.rb +81 -0
  122. data/lib/roast/dsl/cogs/cmd.rb +291 -27
  123. data/lib/roast/dsl/cogs/ruby.rb +171 -0
  124. data/lib/roast/dsl/command_runner.rb +191 -0
  125. data/lib/roast/dsl/config_context.rb +2 -47
  126. data/lib/roast/dsl/config_manager.rb +143 -0
  127. data/lib/roast/dsl/control_flow.rb +41 -0
  128. data/lib/roast/dsl/execution_context.rb +9 -0
  129. data/lib/roast/dsl/execution_manager.rb +267 -0
  130. data/lib/roast/dsl/nil_assertions.rb +23 -0
  131. data/lib/roast/dsl/system_cog/params.rb +32 -0
  132. data/lib/roast/dsl/system_cog.rb +36 -0
  133. data/lib/roast/dsl/system_cogs/call.rb +162 -0
  134. data/lib/roast/dsl/system_cogs/map.rb +448 -0
  135. data/lib/roast/dsl/system_cogs/repeat.rb +242 -0
  136. data/lib/roast/dsl/workflow.rb +123 -0
  137. data/lib/roast/dsl/workflow_context.rb +20 -0
  138. data/lib/roast/dsl/workflow_params.rb +24 -0
  139. data/lib/roast/sorbet_runtime_stub.rb +154 -0
  140. data/lib/roast/tools/apply_diff.rb +1 -3
  141. data/lib/roast/tools/cmd.rb +4 -3
  142. data/lib/roast/tools/read_file.rb +1 -1
  143. data/lib/roast/tools/update_files.rb +1 -1
  144. data/lib/roast/tools/write_file.rb +1 -1
  145. data/lib/roast/version.rb +1 -1
  146. data/lib/roast/workflow/base_workflow.rb +4 -0
  147. data/lib/roast/workflow/step_loader.rb +14 -2
  148. data/lib/roast-ai.rb +4 -0
  149. data/lib/roast.rb +60 -22
  150. data/{roast.gemspec → roast-ai.gemspec} +10 -13
  151. data/sorbet/config +1 -0
  152. data/sorbet/rbi/gems/async@2.34.0.rbi +1577 -0
  153. data/sorbet/rbi/gems/cli-kit@5.2.0.rbi +2063 -0
  154. data/sorbet/rbi/gems/{cli-ui@2.3.0.rbi → cli-ui@2.7.0-6bdefd1d06305e5d6ae312ac76f9c88f88658dda.rbi} +1418 -1013
  155. data/sorbet/rbi/gems/console@1.34.2.rbi +1193 -0
  156. data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +50 -0
  157. data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +35 -0
  158. data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +41 -0
  159. data/sorbet/rbi/gems/io-event@1.14.0.rbi +724 -0
  160. data/sorbet/rbi/gems/marcel@1.1.0.rbi +239 -0
  161. data/sorbet/rbi/gems/metrics@0.15.0.rbi +9 -0
  162. data/sorbet/rbi/gems/ruby_llm@1.8.2.rbi +5703 -0
  163. data/sorbet/rbi/gems/traces@0.18.2.rbi +9 -0
  164. data/sorbet/rbi/shims/lib/roast/dsl/cog_input_context.rbi +1197 -0
  165. data/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +314 -2
  166. data/sorbet/rbi/shims/lib/roast/dsl/execution_context.rbi +498 -0
  167. data/sorbet/tapioca/config.yml +6 -0
  168. data/sorbet/tapioca/require.rb +2 -0
  169. metadata +198 -34
  170. data/dsl/less_simple.rb +0 -112
  171. data/dsl/simple.rb +0 -8
  172. data/lib/roast/dsl/cog_execution_context.rb +0 -29
  173. data/lib/roast/dsl/cogs/graph.rb +0 -53
  174. data/lib/roast/dsl/cogs.rb +0 -65
  175. data/lib/roast/dsl/executor.rb +0 -82
  176. data/lib/roast/dsl/workflow_execution_context.rb +0 -47
  177. data/sorbet/rbi/gems/cgi@0.5.0.rbi +0 -2961
  178. data/sorbet/rbi/gems/claude_swarm@0.1.19.rbi +0 -568
  179. data/sorbet/rbi/gems/cli-kit@5.0.1.rbi +0 -1991
  180. data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +0 -672
  181. data/sorbet/rbi/gems/dry-core@1.1.0.rbi +0 -1894
  182. data/sorbet/rbi/gems/dry-inflector@1.2.0.rbi +0 -659
  183. data/sorbet/rbi/gems/dry-initializer@3.2.0.rbi +0 -781
  184. data/sorbet/rbi/gems/dry-logic@1.6.0.rbi +0 -1127
  185. data/sorbet/rbi/gems/dry-schema@1.14.1.rbi +0 -3727
  186. data/sorbet/rbi/gems/dry-types@1.8.3.rbi +0 -3969
  187. data/sorbet/rbi/gems/fast-mcp-annotations@1.5.3.rbi +0 -1588
  188. data/sorbet/rbi/gems/mime-types-data@3.2025.0617.rbi +0 -136
  189. data/sorbet/rbi/gems/mime-types@3.7.0.rbi +0 -1342
  190. data/sorbet/rbi/gems/rack@2.2.18.rbi +0 -5659
  191. data/sorbet/rbi/gems/rbs-inline@0.12.0.rbi +0 -2170
  192. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +0 -435
  193. data/sorbet/rbi/gems/yard@0.9.37.rbi +0 -18492
  194. data/sorbet/rbi/shims/lib/roast/dsl/workflow_execution_context.rbi +0 -11
@@ -0,0 +1,123 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ module DSL
6
+ class Workflow
7
+ class WorkflowError < Roast::Error; end
8
+ class WorkflowNotPreparedError < WorkflowError; end
9
+ class WorkflowAlreadyPreparedError < WorkflowError; end
10
+ class WorkflowAlreadyStartedError < WorkflowError; end
11
+ class InvalidCogReference < WorkflowError; end
12
+
13
+ class << self
14
+ #: (String, WorkflowParams) -> void
15
+ def from_file(workflow_path, params)
16
+ Dir.mktmpdir("roast-") do |tmpdir|
17
+ workflow_context = WorkflowContext.new(params:, tmpdir:)
18
+ workflow = new(workflow_path, workflow_context)
19
+ workflow.prepare!
20
+ workflow.start!
21
+ end
22
+ end
23
+ end
24
+
25
+ #: (String, WorkflowContext) -> void
26
+ def initialize(workflow_path, workflow_context)
27
+ @workflow_path = Pathname.new(workflow_path) #: Pathname
28
+ @workflow_context = workflow_context #: WorkflowContext
29
+ @workflow_definition = File.read(workflow_path) #: String
30
+ @cog_registry = Cog::Registry.new #: Cog::Registry
31
+ @config_procs = [] #: Array[^() -> void]
32
+ @execution_procs = { nil: [] } #: Hash[Symbol?, Array[^() -> void]]
33
+ @config_manager = nil #: ConfigManager?
34
+ @execution_manager = nil #: ExecutionManager?
35
+ end
36
+
37
+ #: () -> void
38
+ def prepare!
39
+ raise WorkflowAlreadyPreparedError if preparing? || prepared?
40
+
41
+ @preparing = true
42
+ extract_dsl_procs!
43
+ @config_manager = ConfigManager.new(@cog_registry, @config_procs)
44
+ @config_manager.not_nil!.prepare!
45
+ # TODO: probably we should just not pass the params as the top-level scope value anymore
46
+ @execution_manager = ExecutionManager.new(@cog_registry, @config_manager.not_nil!, @execution_procs, @workflow_context, scope_value: @workflow_context.params)
47
+ @execution_manager.not_nil!.prepare!
48
+
49
+ @prepared = true
50
+ end
51
+
52
+ #: () -> void
53
+ def start!
54
+ raise WorkflowNotPreparedError unless @config_manager.present? && @execution_manager.present?
55
+ raise WorkflowAlreadyStartedError if started? || completed?
56
+
57
+ @started = true
58
+ begin
59
+ @execution_manager.run!
60
+ rescue ControlFlow::Break
61
+ # treat `break!` like `next!` in the top-level executor scope
62
+ # TODO: maybe do something with the message passed to break!
63
+ end
64
+ @completed = true
65
+ end
66
+
67
+ #: () -> bool
68
+ def preparing?
69
+ @preparing ||= false
70
+ end
71
+
72
+ #: () -> bool
73
+ def prepared?
74
+ @prepared ||= false
75
+ end
76
+
77
+ #: () -> bool
78
+ def started?
79
+ @started ||= false
80
+ end
81
+
82
+ #: () -> bool
83
+ def completed?
84
+ @completed ||= false
85
+ end
86
+
87
+ #: { () [self: Roast::DSL::ConfigContext] -> void } -> void
88
+ def config(&block)
89
+ @config_procs << block
90
+ end
91
+
92
+ #: (?Symbol?) { () [self: Roast::DSL::ExecutionContext] -> void } -> void
93
+ def execute(scope = nil, &block)
94
+ (@execution_procs[scope] ||= []) << block
95
+ end
96
+
97
+ def use(cogs = [], from: nil)
98
+ require from if from
99
+
100
+ Array.wrap(cogs).each do |cog_name|
101
+ path = @workflow_path.realdirpath.dirname.join("cogs/#{cog_name}")
102
+ require path.to_s if from.nil?
103
+
104
+ cog_class_name = cog_name.camelize
105
+ raise InvalidCogReference, "Expected #{cog_class_name} class, not found in #{path}" unless Object.const_defined?(cog_class_name)
106
+
107
+ cog_class = cog_class_name.constantize # rubocop:disable Sorbet/ConstantsFromStrings
108
+ @cog_registry.use(cog_class)
109
+ end
110
+ end
111
+
112
+ private
113
+
114
+ # Evaluate the top-level workflow definition
115
+ # This collects the procs passed to `config` and `execute` calls in the workflow definition,
116
+ # but does not evaluate any of them individually yet.
117
+ #: () -> void
118
+ def extract_dsl_procs!
119
+ instance_eval(@workflow_definition, @workflow_path.realpath.to_s, 1)
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,20 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ module DSL
6
+ class WorkflowContext
7
+ #: WorkflowParams
8
+ attr_reader :params
9
+
10
+ #: String
11
+ attr_reader :tmpdir
12
+
13
+ #: (params: WorkflowParams, tmpdir: String) -> void
14
+ def initialize(params:, tmpdir:)
15
+ @params = params
16
+ @tmpdir = tmpdir
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ module DSL
6
+ class WorkflowParams
7
+ #: Array[String]
8
+ attr_reader :targets
9
+
10
+ #: Array[Symbol]
11
+ attr_reader :args
12
+
13
+ #: Hash[Symbol, String]
14
+ attr_reader :kwargs
15
+
16
+ #: (Array[String], Array[Symbol], Hash[Symbol, String]) -> void
17
+ def initialize(targets, args, kwargs)
18
+ @targets = targets
19
+ @args = args
20
+ @kwargs = kwargs
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,154 @@
1
+ # typed: ignore
2
+ # frozen_string_literal: true
3
+
4
+ module T
5
+ class << self
6
+ def absurd(value); end
7
+ def all(type_a, type_b, *types); end
8
+ def any(type_a, type_b, *types); end
9
+ def attached_class; end
10
+ def class_of(klass); end
11
+ def enum(values); end
12
+ def nilable(type); end
13
+ def noreturn; end
14
+ def self_type; end
15
+ def type_alias(type = nil, &_blk); end
16
+ def type_parameter(name); end
17
+ def untyped; end
18
+
19
+ def assert_type!(value, _type, _checked: true)
20
+ value
21
+ end
22
+
23
+ def cast(value, _type, _checked: true)
24
+ value
25
+ end
26
+
27
+ def let(value, _type, _checked: true)
28
+ value
29
+ end
30
+
31
+ def must(arg, _msg = nil)
32
+ arg
33
+ end
34
+
35
+ def proc
36
+ T::Proc.new
37
+ end
38
+
39
+ def reveal_type(value)
40
+ value
41
+ end
42
+
43
+ def unsafe(value)
44
+ value
45
+ end
46
+ end
47
+
48
+ module Sig
49
+ def sig(arg0 = nil, &blk); end
50
+ end
51
+
52
+ module Helpers
53
+ def abstract!; end
54
+ def interface!; end
55
+ def final!; end
56
+ def sealed!; end
57
+ def mixes_in_class_methods(mod); end
58
+ end
59
+
60
+ module Generic
61
+ include(T::Helpers)
62
+
63
+ def type_parameters(*params); end
64
+ def type_member(variance = :invariant, fixed: nil, lower: nil, upper: BasicObject); end
65
+ def type_template(variance = :invariant, fixed: nil, lower: nil, upper: BasicObject); end
66
+
67
+ def [](*types)
68
+ self
69
+ end
70
+ end
71
+
72
+ module Array
73
+ class << self
74
+ def [](type); end
75
+ end
76
+ end
77
+
78
+ Boolean = Object.new.freeze
79
+
80
+ module Configuration
81
+ class << self
82
+ def call_validation_error_handler(signature, opts); end
83
+ def call_validation_error_handler=(value); end
84
+ def default_checked_level=(default_checked_level); end
85
+ def enable_checking_for_sigs_marked_checked_tests; end
86
+ def enable_final_checks_on_hooks; end
87
+ def enable_legacy_t_enum_migration_mode; end
88
+ def reset_final_checks_on_hooks; end
89
+ def hard_assert_handler(str, extra); end
90
+ def hard_assert_handler=(value); end
91
+ def inline_type_error_handler(error); end
92
+ def inline_type_error_handler=(value); end
93
+ def log_info_handler(str, extra); end
94
+ def log_info_handler=(value); end
95
+ def scalar_types; end
96
+ def scalar_types=(values); end
97
+ def sealed_violation_whitelist; end
98
+ def sealed_violation_whitelist=(sealed_violation_whitelist); end
99
+ def sig_builder_error_handler=(value); end
100
+ def sig_validation_error_handler(error, opts); end
101
+ def sig_validation_error_handler=(value); end
102
+ def soft_assert_handler(str, extra); end
103
+ def soft_assert_handler=(value); end
104
+ end
105
+ end
106
+
107
+ module Enumerable
108
+ class << self
109
+ def [](type); end
110
+ end
111
+ end
112
+
113
+ module Enumerator
114
+ class << self
115
+ def [](type); end
116
+ end
117
+ end
118
+
119
+ module Hash
120
+ class << self
121
+ def [](keys, values); end
122
+ end
123
+ end
124
+
125
+ class Proc
126
+ def bind(*_)
127
+ self
128
+ end
129
+
130
+ def params(*_param)
131
+ self
132
+ end
133
+
134
+ def void
135
+ self
136
+ end
137
+
138
+ def returns(_type)
139
+ self
140
+ end
141
+ end
142
+
143
+ module Range
144
+ class << self
145
+ def [](type); end
146
+ end
147
+ end
148
+
149
+ module Set
150
+ class << self
151
+ def [](type); end
152
+ end
153
+ end
154
+ end
@@ -1,8 +1,6 @@
1
1
  # typed: false
2
2
  # frozen_string_literal: true
3
3
 
4
- require "cli/ui"
5
-
6
4
  module Roast
7
5
  module Tools
8
6
  module ApplyDiff
@@ -64,7 +62,7 @@ module Roast
64
62
  Roast::Helpers::Logger.info(cancel_msg + "\n")
65
63
  cancel_msg
66
64
  end
67
- rescue Roast::Error => e
65
+ rescue Roast::Error, Errno::EACCES, Errno::ENOENT => e
68
66
  error_message = "Error applying diff: #{e.message}"
69
67
  Roast::Helpers::Logger.error(error_message + "\n")
70
68
  Roast::Helpers::Logger.debug(e.backtrace.join("\n") + "\n") if ENV["DEBUG"]
@@ -153,9 +153,10 @@ module Roast
153
153
  )
154
154
 
155
155
  format_output(command, result, status.exitstatus)
156
- rescue Timeout::Error => e
157
- Roast::Helpers::Logger.error(e.message + "\n")
158
- e.message
156
+ rescue Timeout::Error, Errno::ENOENT => e
157
+ error_message = "Error running command: #{e.message}"
158
+ Roast::Helpers::Logger.error(error_message + "\n")
159
+ error_message
159
160
  end
160
161
 
161
162
  def format_output(command, result, exit_status)
@@ -39,7 +39,7 @@ module Roast
39
39
  else
40
40
  File.read(path)
41
41
  end
42
- rescue Roast::Error => e
42
+ rescue Roast::Error, Errno::ENOENT, Errno::EACCES => e
43
43
  "Error reading file: #{e.message}".tap do |error_message|
44
44
  Roast::Helpers::Logger.error(error_message + "\n")
45
45
  Roast::Helpers::Logger.debug(e.backtrace.join("\n") + "\n") if ENV["DEBUG"]
@@ -306,7 +306,7 @@ module Roast
306
306
  if new_content_lines
307
307
  content_lines = new_content_lines
308
308
  else
309
- raise "Hunk could not be applied cleanly: #{hunk[:header]}"
309
+ raise Roast::Error, "Hunk could not be applied cleanly: #{hunk[:header]}"
310
310
  end
311
311
  end
312
312
 
@@ -49,7 +49,7 @@ module Roast
49
49
  Roast::Helpers::Logger.error(restriction_message)
50
50
  "Error: Path must start with '#{restrict_path}' to use the write_file tool, try again."
51
51
  end
52
- rescue Roast::Error => e
52
+ rescue Roast::Error, Errno::EACCES, Errno::ENOENT => e
53
53
  "Error writing file: #{e.message}".tap do |error_message|
54
54
  Roast::Helpers::Logger.error(error_message + "\n")
55
55
  Roast::Helpers::Logger.debug(e.backtrace.join("\n") + "\n") if ENV["DEBUG"]
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.9"
5
+ VERSION = "0.5.0"
6
6
  end
@@ -175,7 +175,11 @@ module Roast
175
175
 
176
176
  if error_detail && !error_detail.empty? && !message.include?(error_detail)
177
177
  " (#{error_detail})"
178
+ else
179
+ ""
178
180
  end
181
+ else
182
+ ""
179
183
  end
180
184
 
181
185
  message
@@ -169,7 +169,14 @@ module Roast
169
169
  raise StepExecutionError.new("Syntax error in step file: #{e.message}", step_name: step_name, original_error: e)
170
170
  end
171
171
 
172
- step_class = step_name.classify.constantize
172
+ step_class = if Object.const_defined?(step_name.camelize, false)
173
+ step_name.camelize.constantize
174
+ elsif Object.const_defined?(step_name.classify, false)
175
+ step_name.classify.constantize
176
+ else
177
+ raise StepExecutionError.new("No class named #{step_name.camelize} found in step file #{file_path}", step_name: step_name)
178
+ end
179
+
173
180
  context = File.dirname(file_path)
174
181
  # For Ruby steps, we instantiate the specific class directly
175
182
  # Convert step_name to StepName value object
@@ -208,7 +215,12 @@ module Roast
208
215
  def configure_step(step, step_name, is_last_step: nil)
209
216
  step_config = config_hash[step_name]
210
217
 
211
- # Only set the model if explicitly specified for this step
218
+ # Apply global model if no step-specific model is configured
219
+ if config_hash.key?("model") && !step_config&.key?("model")
220
+ step.model = config_hash["model"]
221
+ end
222
+
223
+ # Apply step-specific model (overrides global model)
212
224
  step.model = step_config["model"] if step_config&.key?("model")
213
225
 
214
226
  # Pass resource to step if supported
data/lib/roast-ai.rb ADDED
@@ -0,0 +1,4 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "roast"
data/lib/roast.rb CHANGED
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Standard library requires
5
+ require "benchmark"
5
6
  require "digest"
6
7
  require "English"
7
8
  require "erb"
@@ -11,6 +12,7 @@ require "logger"
11
12
  require "net/http"
12
13
  require "open3"
13
14
  require "pathname"
15
+ require "pp"
14
16
  require "securerandom"
15
17
  require "shellwords"
16
18
  require "tempfile"
@@ -28,23 +30,37 @@ require "active_support/core_ext/string"
28
30
  require "active_support/core_ext/string/inflections"
29
31
  require "active_support/isolated_execution_state"
30
32
  require "active_support/notifications"
31
- require "cli/ui"
33
+ require "async"
34
+ require "async/semaphore"
32
35
  require "cli/kit"
36
+ require "cli/ui"
33
37
  require "diff/lcs"
34
38
  require "json-schema"
35
39
  require "raix"
36
40
  require "raix/chat_completion"
37
41
  require "raix/function_dispatch"
38
42
  require "ruby-graphviz"
43
+ require "ruby_llm"
39
44
  require "thor"
40
45
  require "timeout"
41
46
 
47
+ unless defined?(T)
48
+ # NOTE: stubs for sorbet-runtime were being imported from cli-kit. They were removed in cli-kit v5.2
49
+ # Ideally we will not need them at all in the future, but for now I have brought them into the project
50
+ # because a large quantity of legacy code is using sorbet runtime assertions.
51
+ require("roast/sorbet_runtime_stub")
52
+ end
53
+
54
+ # Require project components that will not get automatically loaded
55
+ require "roast/dsl/nil_assertions"
56
+
42
57
  # Autoloading setup
43
58
  require "zeitwerk"
44
59
 
45
60
  # Set up Zeitwerk autoloader
46
61
  loader = Zeitwerk::Loader.for_gem
47
62
  loader.inflector.inflect("dsl" => "DSL")
63
+ loader.ignore("#{__dir__}/roast-ai.rb")
48
64
  loader.setup
49
65
 
50
66
  module Roast
@@ -59,7 +75,7 @@ module Roast
59
75
  option :replay, type: :string, aliases: "-r", desc: "Resume workflow from a specific step. Format: step_name or session_timestamp:step_name"
60
76
  option :pause, type: :string, aliases: "-p", desc: "Pause workflow after a specific step. Format: step_name"
61
77
  option :file_storage, type: :boolean, aliases: "-f", desc: "Use filesystem storage for sessions instead of SQLite"
62
- option :executor, type: :string, default: "default", desc: "Set workflow executor - experimental syntax"
78
+ option :executor, type: :string, default: "default", desc: "Set workflow executor (use 'dsl' for Roast 1.0 Feature Preview)"
63
79
 
64
80
  def execute(*paths)
65
81
  raise Thor::Error, "Workflow configuration file is required" if paths.empty?
@@ -67,8 +83,13 @@ module Roast
67
83
  workflow_path, *files = paths
68
84
 
69
85
  if options[:executor] == "dsl"
70
- puts "⚠️ WARNING: This is an experimental syntax and may break at any time. Don't depend on it."
71
- Roast::DSL::Executor.from_file(workflow_path)
86
+ $stderr.puts "⚠️ WARNING: You are using Roast 1.0 feature preview. This syntax has not yet been officially released and should not be considered fully stable."
87
+ targets, workflow_args, workflow_kwargs = parse_custom_workflow_args(files, ARGV)
88
+ targets.unshift(options[:target]) if options[:target]
89
+ workflow_params = Roast::DSL::WorkflowParams.new(targets, workflow_args, workflow_kwargs)
90
+ Dir.chdir(ENV["ROAST_WORKING_DIRECTORY"] || Dir.pwd) do
91
+ Roast::DSL::Workflow.from_file(workflow_path, workflow_params)
92
+ end
72
93
  else
73
94
  expanded_workflow_path = if workflow_path.include?("workflow.yml")
74
95
  File.expand_path(workflow_path)
@@ -286,6 +307,24 @@ module Roast
286
307
 
287
308
  private
288
309
 
310
+ #: (Array[String], Array[String]) -> [Array[String], Array[Symbol], Hash[Symbol, String]]
311
+ def parse_custom_workflow_args(parsed_args, raw_args)
312
+ separator_index = raw_args.index("--")
313
+ extra_args = (separator_index ? raw_args[(separator_index + 1)..] : []) || []
314
+ targets = parsed_args.shift(parsed_args.length - extra_args.length)
315
+ args = []
316
+ kwargs = {}
317
+ parsed_args.each do |arg|
318
+ if arg.include?("=")
319
+ key, value = arg.split("=", 2)
320
+ kwargs[key.to_sym] = value if key
321
+ else
322
+ args << arg.to_sym
323
+ end
324
+ end
325
+ [targets, args, kwargs]
326
+ end
327
+
289
328
  def show_example_picker
290
329
  examples = available_examples
291
330
 
@@ -295,7 +334,7 @@ module Roast
295
334
  end
296
335
 
297
336
  puts "Select an option:"
298
- choices = ["Pick from examples", "New from prompt (beta)"]
337
+ choices = ["Pick from examples", "New from prompt (coming soon)"]
299
338
 
300
339
  selected = run_picker(choices, "Select initialization method:")
301
340
 
@@ -303,8 +342,8 @@ module Roast
303
342
  when "Pick from examples"
304
343
  example_choice = run_picker(examples, "Select an example:")
305
344
  copy_example(example_choice) if example_choice
306
- when "New from prompt (beta)"
307
- create_from_prompt
345
+ when "New from prompt (coming soon)"
346
+ show_coming_soon_message
308
347
  end
309
348
  end
310
349
 
@@ -348,22 +387,21 @@ module Roast
348
387
  puts "Successfully copied example '#{example_name}' to current directory."
349
388
  end
350
389
 
351
- def create_from_prompt
352
- puts("Create a new workflow from a description")
390
+ def show_coming_soon_message
391
+ puts
392
+ puts ::CLI::UI.fmt("{{bold:Workflow Generator - Coming Soon}}")
393
+ puts
394
+ puts "The 'New from prompt' workflow generator is being rebuilt to ensure"
395
+ puts "generated workflows are reliable and properly validated."
396
+ puts
397
+ puts "This feature will return with:"
398
+ puts " • Better AI-generated workflow quality"
399
+ puts " • Validation to ensure generated workflows actually work"
400
+ puts " • Integration with Roast's upcoming DSL features"
401
+ puts
402
+ puts "For now, please use 'Pick from examples' to get started."
403
+ puts "You can then customize the example workflow for your needs."
353
404
  puts
354
-
355
- # Execute the workflow generator
356
- generator_path = File.join(Roast::ROOT, "examples", "workflow_generator", "workflow.yml")
357
-
358
- begin
359
- # Execute the workflow generator (it will handle user input)
360
- Roast::Workflow::WorkflowRunner.new(generator_path, [], {}).begin!
361
-
362
- puts
363
- puts("Workflow generation complete!")
364
- rescue => e
365
- puts("Error generating workflow: #{e.message}")
366
- end
367
405
  end
368
406
 
369
407
  class << self
@@ -15,17 +15,10 @@ Gem::Specification.new do |spec|
15
15
  spec.homepage = "https://github.com/Shopify/roast"
16
16
  spec.license = "MIT"
17
17
 
18
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19
- # to allow pushing to a single host or delete this section to allow pushing to any host.
20
- if spec.respond_to?(:metadata)
21
- spec.metadata["allowed_push_host"] = "https://rubygems.org"
22
- spec.metadata["homepage_uri"] = spec.homepage
23
- spec.metadata["source_code_uri"] = "https://github.com/Shopify/roast"
24
- spec.metadata["changelog_uri"] = "https://github.com/Shopify/roast/blob/main/CHANGELOG.md"
25
- else
26
- raise "RubyGems 2.0 or newer is required to protect against " \
27
- "public gem pushes."
28
- end
18
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/Shopify/roast"
21
+ spec.metadata["changelog_uri"] = "https://github.com/Shopify/roast/blob/main/CHANGELOG.md"
29
22
 
30
23
  # Specify which files should be added to the gem when it is released.
31
24
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -37,13 +30,17 @@ Gem::Specification.new do |spec|
37
30
  spec.require_paths = ["lib"]
38
31
 
39
32
  spec.add_dependency("activesupport", ">= 7.0")
40
- spec.add_dependency("cli-kit", "~> 5.0")
41
- spec.add_dependency("cli-ui", "2.3.0")
33
+ spec.add_dependency("async", "~> 2.34")
34
+ spec.add_dependency("benchmark", "~> 0.4.1")
35
+ spec.add_dependency("cli-kit", "~> 5.2")
36
+ spec.add_dependency("cli-ui", "~> 2.7")
42
37
  spec.add_dependency("diff-lcs", "~> 1.5")
43
38
  spec.add_dependency("json-schema")
44
39
  spec.add_dependency("open_router", "~> 0.3")
45
40
  spec.add_dependency("raix", "~> 1.0.2")
41
+ spec.add_dependency("rake", "~> 13.3.0") # NOTE: required by Thor
46
42
  spec.add_dependency("ruby-graphviz", "~> 1.2")
43
+ spec.add_dependency("ruby_llm")
47
44
  spec.add_dependency("sqlite3", "~> 2.6")
48
45
  spec.add_dependency("thor", "~> 1.3")
49
46
  spec.add_dependency("zeitwerk", "~> 2.6")
data/sorbet/config CHANGED
@@ -6,5 +6,6 @@
6
6
  --ignore=vendor/
7
7
  --ignore=test/
8
8
  --ignore=bin/
9
+ --ignore=dsl/demo
9
10
  --enable-experimental-requires-ancestor
10
11
  --enable-experimental-rbs-comments