roast-ai 0.4.10 → 0.5.1

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 (175) 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/.ruby-version +1 -1
  8. data/CLAUDE.md +2 -2
  9. data/CONTRIBUTING.md +2 -0
  10. data/Gemfile +19 -18
  11. data/Gemfile.lock +35 -58
  12. data/README.md +118 -1432
  13. data/README_LEGACY.md +1464 -0
  14. data/Rakefile +39 -4
  15. data/dev.yml +29 -0
  16. data/dsl/agent_sessions.rb +20 -0
  17. data/dsl/async_cogs.rb +49 -0
  18. data/dsl/async_cogs_complex.rb +67 -0
  19. data/dsl/call.rb +44 -0
  20. data/dsl/collect_from.rb +72 -0
  21. data/dsl/json_output.rb +28 -0
  22. data/dsl/map.rb +55 -0
  23. data/dsl/map_reduce.rb +37 -0
  24. data/dsl/map_with_index.rb +49 -0
  25. data/dsl/next_break.rb +45 -0
  26. data/dsl/next_break_parallel.rb +44 -0
  27. data/dsl/outputs.rb +39 -0
  28. data/dsl/outputs_bang.rb +36 -0
  29. data/dsl/parallel_map.rb +37 -0
  30. data/dsl/prompts/simple_prompt.md.erb +3 -0
  31. data/dsl/prototype.rb +5 -7
  32. data/dsl/repeat_loop_results.rb +53 -0
  33. data/dsl/ruby_cog.rb +72 -0
  34. data/dsl/simple_agent.rb +18 -0
  35. data/dsl/simple_chat.rb +15 -1
  36. data/dsl/simple_repeat.rb +29 -0
  37. data/dsl/skip.rb +36 -0
  38. data/dsl/step_communication.rb +2 -3
  39. data/dsl/targets_and_params.rb +57 -0
  40. data/dsl/temperature.rb +17 -0
  41. data/dsl/temporary_directory.rb +22 -0
  42. data/dsl/tutorial/01_your_first_workflow/README.md +179 -0
  43. data/dsl/tutorial/01_your_first_workflow/configured_chat.rb +33 -0
  44. data/dsl/tutorial/01_your_first_workflow/hello.rb +23 -0
  45. data/dsl/tutorial/02_chaining_cogs/README.md +310 -0
  46. data/dsl/tutorial/02_chaining_cogs/code_review.rb +104 -0
  47. data/dsl/tutorial/02_chaining_cogs/session_resumption.rb +92 -0
  48. data/dsl/tutorial/02_chaining_cogs/simple_chain.rb +84 -0
  49. data/dsl/tutorial/03_targets_and_params/README.md +230 -0
  50. data/dsl/tutorial/03_targets_and_params/multiple_targets.rb +65 -0
  51. data/dsl/tutorial/03_targets_and_params/single_target.rb +65 -0
  52. data/dsl/tutorial/04_configuration_options/README.md +209 -0
  53. data/dsl/tutorial/04_configuration_options/control_display_and_temperature.rb +104 -0
  54. data/dsl/tutorial/04_configuration_options/simple_config.rb +68 -0
  55. data/dsl/tutorial/05_control_flow/README.md +156 -0
  56. data/dsl/tutorial/05_control_flow/conditional_execution.rb +62 -0
  57. data/dsl/tutorial/05_control_flow/handling_failures.rb +77 -0
  58. data/dsl/tutorial/06_reusable_scopes/README.md +172 -0
  59. data/dsl/tutorial/06_reusable_scopes/accessing_scope_outputs.rb +126 -0
  60. data/dsl/tutorial/06_reusable_scopes/basic_scope.rb +63 -0
  61. data/dsl/tutorial/06_reusable_scopes/parameterized_scope.rb +78 -0
  62. data/dsl/tutorial/07_processing_collections/README.md +152 -0
  63. data/dsl/tutorial/07_processing_collections/basic_map.rb +70 -0
  64. data/dsl/tutorial/07_processing_collections/parallel_map.rb +74 -0
  65. data/dsl/tutorial/08_iterative_workflows/README.md +231 -0
  66. data/dsl/tutorial/08_iterative_workflows/basic_repeat.rb +57 -0
  67. data/dsl/tutorial/08_iterative_workflows/conditional_break.rb +57 -0
  68. data/dsl/tutorial/09_async_cogs/README.md +197 -0
  69. data/dsl/tutorial/09_async_cogs/basic_async.rb +38 -0
  70. data/dsl/tutorial/README.md +222 -0
  71. data/dsl/working_directory.rb +16 -0
  72. data/exe/roast +1 -1
  73. data/internal/documentation/architectural-notes.md +115 -0
  74. data/internal/documentation/doc-comments-external.md +686 -0
  75. data/internal/documentation/doc-comments-internal.md +342 -0
  76. data/internal/documentation/doc-comments.md +211 -0
  77. data/lib/roast/dsl/cog/config.rb +274 -3
  78. data/lib/roast/dsl/cog/input.rb +53 -10
  79. data/lib/roast/dsl/cog/output.rb +297 -8
  80. data/lib/roast/dsl/cog/registry.rb +35 -3
  81. data/lib/roast/dsl/cog/stack.rb +1 -1
  82. data/lib/roast/dsl/cog/store.rb +5 -5
  83. data/lib/roast/dsl/cog.rb +70 -14
  84. data/lib/roast/dsl/cog_input_context.rb +36 -1
  85. data/lib/roast/dsl/cog_input_manager.rb +116 -7
  86. data/lib/roast/dsl/cogs/agent/config.rb +465 -0
  87. data/lib/roast/dsl/cogs/agent/input.rb +81 -0
  88. data/lib/roast/dsl/cogs/agent/output.rb +59 -0
  89. data/lib/roast/dsl/cogs/agent/provider.rb +51 -0
  90. data/lib/roast/dsl/cogs/agent/providers/claude/claude_invocation.rb +185 -0
  91. data/lib/roast/dsl/cogs/agent/providers/claude/message.rb +73 -0
  92. data/lib/roast/dsl/cogs/agent/providers/claude/messages/assistant_message.rb +36 -0
  93. data/lib/roast/dsl/cogs/agent/providers/claude/messages/result_message.rb +61 -0
  94. data/lib/roast/dsl/cogs/agent/providers/claude/messages/system_message.rb +47 -0
  95. data/lib/roast/dsl/cogs/agent/providers/claude/messages/text_message.rb +36 -0
  96. data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_result_message.rb +47 -0
  97. data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_use_message.rb +46 -0
  98. data/lib/roast/dsl/cogs/agent/providers/claude/messages/unknown_message.rb +27 -0
  99. data/lib/roast/dsl/cogs/agent/providers/claude/messages/user_message.rb +37 -0
  100. data/lib/roast/dsl/cogs/agent/providers/claude/tool_result.rb +51 -0
  101. data/lib/roast/dsl/cogs/agent/providers/claude/tool_use.rb +48 -0
  102. data/lib/roast/dsl/cogs/agent/providers/claude.rb +31 -0
  103. data/lib/roast/dsl/cogs/agent/stats.rb +92 -0
  104. data/lib/roast/dsl/cogs/agent/usage.rb +62 -0
  105. data/lib/roast/dsl/cogs/agent.rb +75 -0
  106. data/lib/roast/dsl/cogs/chat/config.rb +453 -0
  107. data/lib/roast/dsl/cogs/chat/input.rb +92 -0
  108. data/lib/roast/dsl/cogs/chat/output.rb +64 -0
  109. data/lib/roast/dsl/cogs/chat/session.rb +68 -0
  110. data/lib/roast/dsl/cogs/chat.rb +59 -56
  111. data/lib/roast/dsl/cogs/cmd.rb +251 -61
  112. data/lib/roast/dsl/cogs/ruby.rb +171 -0
  113. data/lib/roast/dsl/command_runner.rb +191 -0
  114. data/lib/roast/dsl/config_manager.rb +58 -11
  115. data/lib/roast/dsl/control_flow.rb +41 -0
  116. data/lib/roast/dsl/execution_manager.rb +162 -32
  117. data/lib/roast/dsl/nil_assertions.rb +23 -0
  118. data/lib/roast/dsl/system_cog/params.rb +32 -0
  119. data/lib/roast/dsl/system_cog.rb +36 -0
  120. data/lib/roast/dsl/system_cogs/call.rb +163 -0
  121. data/lib/roast/dsl/system_cogs/map.rb +454 -0
  122. data/lib/roast/dsl/system_cogs/repeat.rb +242 -0
  123. data/lib/roast/dsl/workflow.rb +26 -16
  124. data/lib/roast/dsl/workflow_context.rb +20 -0
  125. data/lib/roast/dsl/workflow_params.rb +24 -0
  126. data/lib/roast/helpers/minitest_coverage_runner.rb +1 -1
  127. data/lib/roast/sorbet_runtime_stub.rb +154 -0
  128. data/lib/roast/tools/apply_diff.rb +1 -3
  129. data/lib/roast/tools/cmd.rb +4 -3
  130. data/lib/roast/tools/read_file.rb +1 -1
  131. data/lib/roast/tools/update_files.rb +1 -1
  132. data/lib/roast/tools/write_file.rb +1 -1
  133. data/lib/roast/version.rb +1 -1
  134. data/lib/roast/workflow/base_workflow.rb +4 -0
  135. data/lib/roast/workflow/step_loader.rb +14 -2
  136. data/lib/roast-ai.rb +4 -0
  137. data/lib/roast.rb +58 -21
  138. data/{roast.gemspec → roast-ai.gemspec} +9 -13
  139. data/sorbet/rbi/gems/async@2.34.0.rbi +1577 -0
  140. data/sorbet/rbi/gems/cli-kit@5.2.0.rbi +2063 -0
  141. data/sorbet/rbi/gems/{cli-ui@2.3.0.rbi → cli-ui@2.7.0-6bdefd1d06305e5d6ae312ac76f9c88f88658dda.rbi} +1418 -1013
  142. data/sorbet/rbi/gems/console@1.34.2.rbi +1193 -0
  143. data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +50 -0
  144. data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +35 -0
  145. data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +41 -0
  146. data/sorbet/rbi/gems/io-event@1.14.0.rbi +724 -0
  147. data/sorbet/rbi/gems/metrics@0.15.0.rbi +9 -0
  148. data/sorbet/rbi/gems/traces@0.18.2.rbi +9 -0
  149. data/sorbet/rbi/shims/lib/roast/dsl/cog_input_context.rbi +1185 -5
  150. data/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +311 -5
  151. data/sorbet/rbi/shims/lib/roast/dsl/execution_context.rbi +486 -5
  152. data/sorbet/tapioca/config.yml +6 -0
  153. data/sorbet/tapioca/require.rb +2 -0
  154. metadata +157 -30
  155. data/dsl/less_simple.rb +0 -112
  156. data/dsl/scoped_executors.rb +0 -28
  157. data/dsl/simple.rb +0 -8
  158. data/lib/roast/dsl/cogs/execute.rb +0 -46
  159. data/lib/roast/dsl/cogs/graph.rb +0 -53
  160. data/sorbet/rbi/gems/cgi@0.5.0.rbi +0 -2961
  161. data/sorbet/rbi/gems/claude_swarm@0.1.19.rbi +0 -568
  162. data/sorbet/rbi/gems/cli-kit@5.0.1.rbi +0 -1991
  163. data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +0 -672
  164. data/sorbet/rbi/gems/dry-core@1.1.0.rbi +0 -1894
  165. data/sorbet/rbi/gems/dry-inflector@1.2.0.rbi +0 -659
  166. data/sorbet/rbi/gems/dry-initializer@3.2.0.rbi +0 -781
  167. data/sorbet/rbi/gems/dry-logic@1.6.0.rbi +0 -1127
  168. data/sorbet/rbi/gems/dry-schema@1.14.1.rbi +0 -3727
  169. data/sorbet/rbi/gems/dry-types@1.8.3.rbi +0 -3969
  170. data/sorbet/rbi/gems/fast-mcp-annotations@1.5.3.rbi +0 -1588
  171. data/sorbet/rbi/gems/mime-types-data@3.2025.0617.rbi +0 -136
  172. data/sorbet/rbi/gems/mime-types@3.7.0.rbi +0 -1342
  173. data/sorbet/rbi/gems/rack@2.2.19.rbi +0 -5676
  174. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +0 -435
  175. data/sorbet/rbi/gems/yard@0.9.37.rbi +0 -18492
@@ -4,14 +4,495 @@
4
4
  module Roast
5
5
  module DSL
6
6
  class ExecutionContext
7
- #: (?Symbol?) {(Roast::DSL::Cogs::Cmd::Input) [self: Roast::DSL::CogInputContext] -> (String | Array[String] | nil)} -> void
8
- def cmd(name = nil, &block); end
9
7
 
10
- #: (?Symbol?) {(Roast::DSL::Cogs::Chat::Input) [self: Roast::DSL::CogInputContext] -> String?} -> void
8
+ # Define the output of the current execution scope
9
+ #
10
+ # The `outputs!` block defines the 'primary' return value of this execution scope when it is invoked
11
+ # via the `call`, `map`, or `repeat` cogs. The block receives the current scope value and
12
+ # index as arguments, and executes in a `Roast::DSL::CogInputContext` where you can
13
+ # access any cog outputs from within the current scope.
14
+ #
15
+ # The `outputs!` block **always runs**, even if `break!` or `next!` are called within the
16
+ # scope. It is effectively a finalizer for the execution scope (though it will not run if
17
+ # the workflow aborts with an error).
18
+ #
19
+ # The alternative `outputs` block is identical to `outputs!`, but with relaxed handling of `CogOutputAccessError`s.
20
+ # `outputs!` will raise an exception if you attempt to access a cog that did not run (e.g., due to `skip!`)
21
+ # using its bang method for that cog -- i.e., the normal behaviour in a cog input block.
22
+ # `outputs`, on the other hand, will swallow these `CogOutputAccessError`s.
23
+ #
24
+ # It is recommended to use `outputs!` as the default unless you specifically need the relaxed error handling of
25
+ # `outputs`.
26
+ #
27
+ # The `outputs!` block **always runs**, even if `break!` or `next!` are called within the scope. It is
28
+ # effectively a finalizer for the execution scope (though it will not run if the workflow aborts with an error).
29
+ #
30
+ # NOTE: You can only have one output block in an execution scope: either `outputs!` or `outputs` depending
31
+ # on your requirements for that scope.
32
+ #
33
+ # ### Usage
34
+ # ```ruby
35
+ # execute(:refine_content) do
36
+ # chat(:improve) do |my, content, idx|
37
+ # my.prompt = "Improve this content (iteration #{idx}): #{content}"
38
+ # end
39
+ #
40
+ # ruby { |_, _, idx| break! if idx >= 3 }
41
+ #
42
+ # # This always runs, even when break! is called
43
+ # outputs! { chat!(:improve).response }
44
+ # end
45
+ #
46
+ # execute do
47
+ # repeat(:refine, run: :refine_content) { "Initial draft content" }
48
+ #
49
+ # ruby { puts "Final version: #{from(repeat!(:refine))}" }
50
+ # end
51
+ # ```
52
+ #
53
+ # ### See Also
54
+ # - `outputs` - Relaxed error handling (returns nil for cogs that didn't run)
55
+ # - `from` - Extract output from a `call` cog, or a single iteration of `map` or `repeat`
56
+ # - `collect` - Collect results from a `map` or `repeat` cog
57
+ # - `reduce` - Reduce results from a `map` or `repeat` cog
58
+ #
59
+ #: () {(untyped, Integer) [self: Roast::DSL::CogInputContext] -> untyped} -> void
60
+ def outputs!(&block); end
61
+
62
+ # Define the output of the current execution scope (relaxed error handling)
63
+ #
64
+ # NOTE: Unless you explicitly want the relaxed error handling, using `outputs!` is recommended.
65
+ #
66
+ # The `outputs` block defines the 'primary' return value of this execution scope when it is invoked
67
+ # via the `call`, `map`, or `repeat` cogs. The block receives the current scope value and
68
+ # index as arguments, and executes in a `Roast::DSL::CogInputContext` where you can
69
+ # access any cog outputs from within the current scope.
70
+ #
71
+ # The `outputs` block **always runs**, even if `break!` or `next!` are called within the
72
+ # scope. It is effectively a finalizer for the execution scope (though it will not run if
73
+ # the workflow aborts with an error).
74
+ #
75
+ # The `outputs` variant silently handles `CogOutputAccessError`s when accessing cogs that did not run
76
+ # (e.g., due to `skip!`), returning `nil` instead of raising an exception. This is a convenience
77
+ # formulation for use when you know your scope may abort early, and you only need to compute an
78
+ # `outputs` value when the scope runs fully. Use `outputs!` if you do want all such errors to be raised.
79
+ #
80
+ # NOTE: You can only have one output block in an execution scope: either `outputs!` or `outputs` depending
81
+ # on your requirements for that scope.
82
+ #
83
+ # ### Usage
84
+ # ```ruby
85
+ # execute(:analyze_text) do
86
+ # chat(:analysis) do |my, text|
87
+ # my.prompt = "Analyze this text and identify key themes: #{text}"
88
+ # end
89
+ #
90
+ # # Define what this scope returns
91
+ # outputs do |text|
92
+ # {
93
+ # original: text,
94
+ # analysis: chat!(:analysis).response,
95
+ # word_count: text.split.length
96
+ # }
97
+ # end
98
+ # end
99
+ #
100
+ # execute do
101
+ # call(:process, run: :analyze_text) { "The quick brown fox jumps over the lazy dog" }
102
+ #
103
+ # ruby { puts "Result: #{from(call!(:process))[:analysis]}" }
104
+ # end
105
+ # ```
106
+ #
107
+ # ### See Also
108
+ # - `outputs!` - Raises errors when accessing cogs that did not run
109
+ # - `from` - Extract output from a `call` cog, or a single iteration of `map` or `repeat`
110
+ # - `collect` - Collect results from a `map` or `repeat` cog
111
+ # - `reduce` - Reduce results from a `map` or `repeat` cog
112
+ #
113
+ #: () {(untyped, Integer) [self: Roast::DSL::CogInputContext] -> untyped} -> void
114
+ def outputs(&block); end
115
+
116
+ # Invoke a named execution scope with a provided value
117
+ #
118
+ # The `call` cog executes a named execution scope (defined with `execute(:name)`) with a
119
+ # provided value and optional index. The executed scope can access this value and index
120
+ # through cog input block parameters.
121
+ #
122
+ # The index parameter is primarily for compatibility, allowing a single execution scope to
123
+ # be invoked by both `call` (with a specific index) and `map` (with iteration indices).
124
+ #
125
+ # ### Usage
126
+ # ```ruby
127
+ # execute(:summarize_article) do
128
+ # chat(:summary) do |my, article_text|
129
+ # my.prompt = "Summarize this article in 2-3 sentences: #{article_text}"
130
+ # end
131
+ # end
132
+ #
133
+ # # The nameless execute scope is the workflow entry point
134
+ # execute do
135
+ # call(:process_article, run: :summarize_article) do |my|
136
+ # my.value = "Long article text goes here..."
137
+ # end
138
+ # end
139
+ # ```
140
+ #
141
+ # ### Input Options
142
+ #
143
+ # Set these attributes on the `my` input object within the block.
144
+ #
145
+ # - `value` (required) - The value to pass to the execution scope
146
+ # - `index` (optional) - The index value to pass to the scope (defaults to 0)
147
+ #
148
+ # You can also return a value from the block, which will be used as `my.value` if not set explicitly.
149
+ #
150
+ # ### See Also
151
+ # - `map` - Execute a scope for each item in a collection
152
+ # - `repeat` - Execute a scope multiple times in a loop
153
+ # - `from` - Extract the result from the called scope
154
+ #
155
+ #: (?Symbol?, run: Symbol) ?{(Roast::DSL::SystemCogs::Call::Input, untyped, Integer) [self: Roast::DSL::CogInputContext] -> untyped} -> void
156
+ def call(name = nil, run:, &block); end
157
+
158
+ # Execute a scope for each item in a collection
159
+ #
160
+ # The `map` cog executes a named execution scope (defined with `execute(:name)`) for each
161
+ # item in a collection. Supports both serial and parallel execution modes. Each iteration
162
+ # receives the current item as its value and the iteration index.
163
+ #
164
+ # ### Usage
165
+ # ```ruby
166
+ # execute(:review_document) do
167
+ # chat(:review) do |my, document, idx|
168
+ # # The document from the collection is available as the second block parameter
169
+ # # The index in the collection is available as the third block parameter
170
+ # my.prompt = "Review document #{idx + 1}: #{document}. Provide feedback."
171
+ # end
172
+ # end
173
+ #
174
+ # execute do
175
+ # map(:review_all, run: :review_document) do |my|
176
+ # my.items = ["proposal.md", "design.md", "implementation.md"]
177
+ # end
178
+ # end
179
+ # ```
180
+ #
181
+ # ### Input Options
182
+ #
183
+ # Set these attributes on the `my` input object within the block:
184
+ #
185
+ # - `items` (required) - The collection of items to iterate over (any enumerable)
186
+ # - `initial_index` (optional) - The starting index for the first iteration (defaults to 0)
187
+ #
188
+ # You can also return a collection from the block, which will be used as `my.items` if not set explicitly.
189
+ #
190
+ # ### Flow Control
191
+ #
192
+ # Within the executed scope, call `break!` inside a cog's input block to terminate the map early.
193
+ # Any iterations not yet started will not run (their outputs will be `nil`) and any iterations in progress
194
+ # in parallel will be stopped (their outputs will also be `nil`)
195
+ # The `outputs` block will still run for the iteration in which `break!` is called.
196
+ #
197
+ # Within the executed scope, call `next!` inside a cog's input block to terminate the current iteration and begin
198
+ # next iteration immediately. The `outputs` block will still run for the current iteration, even when `next!` is called.
199
+ #
200
+ # ### Parallel Execution
201
+ #
202
+ # Configure parallel execution in the `config` block for this cog:
203
+ # - `parallel(n)` - Execute up to `n` iterations concurrently
204
+ # - `parallel!` - Execute all iterations concurrently with no limit
205
+ # - `no_parallel!` - Execute serially, one at a time (default)
206
+ #
207
+ # ### See Also
208
+ # - `call` - Execute a scope once with a single value
209
+ # - `repeat` - Execute a scope multiple times in a loop
210
+ # - `collect` - Collect all iteration results into an array
211
+ # - `reduce` - Reduce iteration results to a single value
212
+ #
213
+ #: (?Symbol?, run: Symbol) {(Roast::DSL::SystemCogs::Map::Input, untyped, Integer) [self: Roast::DSL::CogInputContext] -> untyped} -> void
214
+ def map(name = nil, run:, &block); end
215
+
216
+ # Execute a scope multiple times in a loop
217
+ #
218
+ # The `repeat` cog executes a named execution scope (defined with `execute(:name)`) repeatedly
219
+ # until `break!` is called. The output from each iteration becomes the input value for the
220
+ # next iteration, allowing steps to be attempted repeatedly until a condition is met and for
221
+ # the loop body behavior to evolve from one iteration to the next.
222
+ #
223
+ # ### Usage
224
+ # ```ruby
225
+ # execute(:improve_until_acceptable) do
226
+ # chat(:improve) do |my, content, idx|
227
+ # my.prompt = "Improve this content (iteration #{idx}): #{content}"
228
+ # end
229
+ #
230
+ # chat(:evaluate) do |my|
231
+ # my.prompt = "Rate this content quality (1-10): #{chat!(:improve).response}"
232
+ # end
233
+ #
234
+ # # The loop will continue indefinitely until break! is called in a cog's input block.
235
+ # ruby { |_, _, idx| break! if chat!(:evaluate).text.to_i >= 8 || idx >= 5 }
236
+ #
237
+ # # The outputs block always runs, even on the iteration when break! is called.
238
+ # # The output of one iteration of the loop becomes the input to the next iteration.
239
+ # outputs! { chat!(:improve).response }
240
+ # end
241
+ #
242
+ # execute do
243
+ # repeat(:refine, run: :improve_until_acceptable) do |my|
244
+ # my.value = "Initial draft content"
245
+ # end
246
+ # end
247
+ # ```
248
+ #
249
+ # ### Input Options
250
+ #
251
+ # Set these attributes on the `my` input object within the block:
252
+ #
253
+ # - `value` (required) - The initial value to pass to the first iteration
254
+ # - `index` (optional) - The starting index for the first iteration (defaults to 0)
255
+ #
256
+ # You can also return a value from the block, which will be used as `my.value` if not set explicitly.
257
+ #
258
+ # ### Loop Control
259
+ #
260
+ # Within the executed scope, call `break!` inside a cog's input block to exit the loop. The
261
+ # final output of the `repeat` cog is the output of the iteration where `break!` is called.
262
+ # The `outputs` block always runs, even when `break!` is called (it acts as a finalizer).
263
+ #
264
+ # Within the executed scope, call `next!` inside a cog's input bloxk to exit the current iteration of the loop
265
+ # and begin the next iteration. The `outputs` block always run on each iteration, even when `next!` is called.
266
+ #
267
+ # ### See Also
268
+ # - `call` - Execute a scope once with a single value
269
+ # - `map` - Execute a scope for each item in a collection
270
+ # - `collect` - Access all iteration results (via `.results`)
271
+ # - `reduce` - Reduce iteration results to a single value (via `.results`)
272
+ #
273
+ #: (?Symbol?, run: Symbol) {(Roast::DSL::SystemCogs::Repeat::Input, untyped, Integer) [self: Roast::DSL::CogInputContext] -> untyped} -> void
274
+ def repeat(name = nil, run:, &block); end
275
+
276
+ # Run a coding agent on the local machine
277
+ #
278
+ # The `agent` cog runs a coding agent on the local machine with access to local files,
279
+ # tools, and MCP servers. It is designed for coding tasks and any work requiring local
280
+ # filesystem access.
281
+ #
282
+ # The agent supports automatic session resumption across invocations.
283
+ #
284
+ # ### Usage
285
+ # ```ruby
286
+ # execute do
287
+ # agent(:code_analyzer) { "Analyze the code in src/ and suggest improvements" }
288
+ #
289
+ # # Resume from a previous session
290
+ # agent(:continue_analysis) do |my|
291
+ # my.prompt = "Now apply those improvements"
292
+ # my.session = agent!(:code_analyzer).session
293
+ # end
294
+ # end
295
+ # ```
296
+ #
297
+ # ### Input Options
298
+ #
299
+ # Set these attributes on the `my` input object within the block:
300
+ #
301
+ # - `prompt` (required) - The prompt to send to the agent
302
+ # - `session` (optional) - Session identifier for conversation continuity
303
+ #
304
+ # You can also return a String from the block, which will be used as `my.prompt` if not set explicitly.
305
+ #
306
+ # ### Output
307
+ #
308
+ # Access these attributes on the output object:
309
+ # - `response` - The agent's final text response
310
+ # - `session` - Session identifier for resuming the conversation
311
+ # - `stats` - Execution statistics (tokens, cost, etc.)
312
+ # - `text` - The response text with whitespace stripped (same as `.response.strip`)
313
+ # - `lines` - Array of lines from the response, each with whitespace stripped (same as `.response.lines.map(&:strip)`)
314
+ # - `json` - Parse the response as JSON, returning nil if parsing fails
315
+ # - `json!` - Parse the response as JSON, raising an error if parsing fails
316
+ #
317
+ # JSON parsing will aggressively and intelligently attempt to extract a JSON object from within surrounding
318
+ # text produced by the agent, so you should not need to worry if the agent's response looks like "Here is the
319
+ # JSON object you asked for: ..."
320
+ #
321
+ # ### See Also
322
+ # - `chat` - Pure LLM interaction without local system access
323
+ #
324
+ #: (?Symbol?) {(Roast::DSL::Cogs::Agent::Input, untyped, Integer) [self: Roast::DSL::CogInputContext] -> (String | void)} -> void
325
+ def agent(name = nil, &block); end
326
+
327
+ # Perform pure LLM interaction
328
+ #
329
+ # The `chat` cog provides pure LLM interaction without local system access. While it
330
+ # cannot access local files or run local tools, it can still perform complex reasoning
331
+ # and access any provider-hosted tools and MCP servers according to the capabilities of the
332
+ # model and the facilities that it may be equipped with by the LLM provider.
333
+ #
334
+ # ### Usage
335
+ # ```ruby
336
+ # execute do
337
+ # chat(:analyzer) do |my|
338
+ # data = JSON.parse(File.read(target!))
339
+ # my.prompt = "Analyze this data and provide insights: #{data}"
340
+ # end
341
+ #
342
+ # chat(:summarizer) do |my|
343
+ # my.prompt = "Summarize this: #{chat!(:analyzer).response}"
344
+ # end
345
+ #
346
+ # # Resume a conversation by passing the session
347
+ # chat(:followup) do |my|
348
+ # my.prompt = "Can you elaborate on the second point?"
349
+ # my.session = chat!(:analyzer).session
350
+ # end
351
+ # end
352
+ # ```
353
+ #
354
+ # ### Input Options
355
+ #
356
+ # Set these attributes on the `my` input object within the block:
357
+ #
358
+ # - `prompt` (required) - The prompt to send to the language model
359
+ # - `session` (optional) - Session object for conversation continuity
360
+ #
361
+ # You can also return a String from the block, which will be used as `my.prompt` if not set explicitly.
362
+ #
363
+ # ### Output
364
+ #
365
+ # Access these attributes on the output object:
366
+ # - `response` - The LLM's text response
367
+ # - `session` - Session object for resuming the conversation
368
+ # - `text` - The response text with whitespace stripped (same as `.response.strip`)
369
+ # - `lines` - Array of lines from the response, each with whitespace stripped (same as `.response.lines.map(&:strip)`)
370
+ # - `json` - Parse the response as JSON, returning nil if parsing fails
371
+ # - `json!` - Parse the response as JSON, raising an error if parsing fails
372
+ #
373
+ # JSON parsing will aggressively and intelligently attempt to extract a JSON object from within surrounding
374
+ # text produced by the LLM, so you should not need to worry if the LLM's response looks like "Here is the
375
+ # JSON object you asked for: ..."
376
+ #
377
+ # ### See Also
378
+ # - `agent` - Run a coding agent with local filesystem access
379
+ #
380
+ #: (?Symbol?) {(Roast::DSL::Cogs::Chat::Input, untyped, Integer) [self: Roast::DSL::CogInputContext] -> (String | void)} -> void
11
381
  def chat(name = nil, &block); end
12
382
 
13
- #: (?Symbol?) {(Roast::DSL::Cogs::Execute::Input) [self: Roast::DSL::CogInputContext] -> (Symbol | nil)} -> void
14
- def execute(name = nil, &block); end
383
+ # Execute a shell command
384
+ #
385
+ # The `cmd` cog executes shell commands and captures their output and exit status. It can
386
+ # be configured to write its STDOUT and STDERR to the console as well.
387
+ #
388
+ # ### Usage
389
+ # ```ruby
390
+ # execute do
391
+ # cmd(:list_files) do |my|
392
+ # my.command = "ls"
393
+ # my.args = ["-la", "/tmp"]
394
+ # end
395
+ #
396
+ # # Simpler syntax: return command and args as array
397
+ # cmd(:git_status) { ["git", "status", "--short"] }
398
+ #
399
+ # # Return a command string (can include pipes and redirects)
400
+ # cmd(:process_data) { "cat data.txt | grep 'pattern' | wc -l" }
401
+ #
402
+ # # Complex shell command with pipe
403
+ # cmd(:find_errors) do
404
+ # "find . -name '*.log' | xargs grep 'ERROR' | head -n 10"
405
+ # end
406
+ # end
407
+ # ```
408
+ #
409
+ # ### Usage Style
410
+ #
411
+ # __Full command in a single string:__ Use this style when you want to take advantage of shell syntax like
412
+ # globbing, or use pipes, output redirection, etc.
413
+ #
414
+ # __Command and array of arguments:__ Use this style when you do not need shell features, and you specifically
415
+ # want to *avoid* having to escape values in command arguments.
416
+ #
417
+ # ### Input Options
418
+ #
419
+ # Set these attributes on the `my` input object within the block:
420
+ #
421
+ # - `command` (required) - The command to execute
422
+ # - `args` (optional) - Array of arguments to pass to the command
423
+ #
424
+ # You can also return from the block:
425
+ # - A String - used as the command (can include pipes, redirects, and other shell features)
426
+ # - An Array - first element is the command, remaining elements are args
427
+ #
428
+ # ### Output
429
+ #
430
+ # Access these attributes on the output object:
431
+ # - `out` - Standard output (STDOUT) from the command
432
+ # - `err` - Standard error (STDERR) from the command
433
+ # - `status` - The exit status of the command process
434
+ # - `text` - The STDOUT text with whitespace stripped (same as `.out.strip`)
435
+ # - `lines` - Array of lines from STDOUT, each with whitespace stripped (same as `.out.lines.map(&:strip)`)
436
+ # - `json` - Parse STDOUT as JSON, returning nil if parsing fails
437
+ # - `json!` - Parse STDOUT as JSON, raising an error if parsing fails
438
+ #
439
+ # ### See Also
440
+ # - `ruby` - Evaluate Ruby code within the workflow context
441
+ #
442
+ #: (?Symbol?) {(Roast::DSL::Cogs::Cmd::Input, untyped, Integer) [self: Roast::DSL::CogInputContext] -> (String | Array[String] | void)} -> void
443
+ def cmd(name = nil, &block); end
444
+
445
+ # Evaluate Ruby code within the workflow context
446
+ #
447
+ # The `ruby` cog executes Ruby code in its input block. The return value of the block is
448
+ # passed through unchanged as the cog's output. This allows you to perform transformations,
449
+ # create data structures, or perform calculations that are easiest to accomplish with simple
450
+ # Ruby code.
451
+ #
452
+ # ### Usage
453
+ # ```ruby
454
+ # execute do
455
+ # ruby(:compute) { { sum: 1 + 2 + 3, product: 4 * 5 } }
456
+ #
457
+ # ruby(:transform) do
458
+ # data = ruby!(:compute).value
459
+ # "Sum: #{data[:sum]}, Product: #{data[:product]}"
460
+ # end
461
+ #
462
+ # # Access hash values directly on output via dynamic method dispatch
463
+ # ruby(:use_value) do |my|
464
+ # my.value = ruby!(:compute).sum # Accesses hash[:sum]
465
+ # end
466
+ #
467
+ # # Use for control flow
468
+ # ruby { |_, _, idx| break! if idx >= 3 }
469
+ # end
470
+ # ```
471
+ #
472
+ # ### Input Options
473
+ #
474
+ # Set these attributes on the `my` input object within the block:
475
+ #
476
+ # - `value` (required) - The value to pass through as output
477
+ #
478
+ # You can also return any Ruby object from the block, which will be used as `my.value` if
479
+ # not set explicitly.
480
+ #
481
+ # ### Output
482
+ #
483
+ # The output provides convenient dynamic method dispatch:
484
+ # - If the value responds to a method, it delegates to that method
485
+ # - If the value is a Hash, methods correspond to hash keys
486
+ # - Hash values that are Procs can be called directly as methods
487
+ # - Use `[]` for direct hash key access
488
+ # - Use `call()` to invoke Procs stored in the value
489
+ # - Use `.value` to access the raw output value
490
+ #
491
+ # ### See Also
492
+ # - `cmd` - Execute shell commands
493
+ #
494
+ #: (?Symbol?) {(Roast::DSL::Cogs::Ruby::Input, untyped, Integer) [self: Roast::DSL::CogInputContext] -> untyped} -> void
495
+ def ruby(name = nil, &block); end
15
496
  end
16
497
  end
17
498
  end
@@ -5,6 +5,12 @@ gem:
5
5
  # - gem_name
6
6
  # doc: true
7
7
  # workers: 5
8
+ exclude:
9
+ # yard, via yard-sorbet, is required by tapioca itself, but unused in this project
10
+ # yard depends on rake, which seems not to be declared properly as a dependency of that gem
11
+ # since we don't use yard or rake ourselves, the easiest option is not to generate RBI files for these gems
12
+ - yard
13
+ - yard-sorbet
8
14
  dsl:
9
15
  # Add your `dsl` command parameters here:
10
16
  #
@@ -10,6 +10,8 @@ require "active_support/core_ext/string"
10
10
  require "active_support/core_ext/string/inflections"
11
11
  require "active_support/isolated_execution_state"
12
12
  require "active_support/notifications"
13
+ require "async"
14
+ require "async/semaphore"
13
15
  require "bundler/setup"
14
16
  require "cli/kit"
15
17
  require "cli/ui"