roast-ai 0.4.8 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +1 -0
  4. data/Gemfile.lock +3 -3
  5. data/README.md +9 -5
  6. data/dsl/less_simple.rb +112 -0
  7. data/dsl/prototype.rb +17 -0
  8. data/dsl/simple.rb +5 -7
  9. data/dsl/step_communication.rb +18 -0
  10. data/examples/grading/README.md +46 -0
  11. data/examples/grading/analyze_coverage/prompt.md +52 -0
  12. data/examples/grading/calculate_final_grade.rb +64 -0
  13. data/examples/grading/format_result.rb +61 -0
  14. data/examples/grading/generate_grades/prompt.md +105 -0
  15. data/examples/grading/generate_recommendations/output.txt +17 -0
  16. data/examples/grading/generate_recommendations/prompt.md +60 -0
  17. data/examples/grading/read_dependencies/prompt.md +15 -0
  18. data/examples/grading/verify_mocks_and_stubs/prompt.md +12 -0
  19. data/examples/grading/verify_test_helpers/prompt.md +53 -0
  20. data/examples/grading/workflow.md +5 -0
  21. data/examples/grading/workflow.yml +28 -0
  22. data/lib/roast/dsl/cog/config.rb +31 -0
  23. data/lib/roast/dsl/cog/stack.rb +21 -0
  24. data/lib/roast/dsl/cog/store.rb +26 -0
  25. data/lib/roast/dsl/cog.rb +70 -0
  26. data/lib/roast/dsl/cog_execution_context.rb +29 -0
  27. data/lib/roast/dsl/cogs/cmd.rb +55 -0
  28. data/lib/roast/dsl/cogs/graph.rb +53 -0
  29. data/lib/roast/dsl/cogs.rb +65 -0
  30. data/lib/roast/dsl/config_context.rb +54 -0
  31. data/lib/roast/dsl/executor.rb +62 -7
  32. data/lib/roast/dsl/workflow_execution_context.rb +47 -0
  33. data/lib/roast/error.rb +7 -0
  34. data/lib/roast/errors.rb +3 -3
  35. data/lib/roast/graph/edge.rb +25 -0
  36. data/lib/roast/graph/node.rb +40 -0
  37. data/lib/roast/graph/quantum_edge.rb +27 -0
  38. data/lib/roast/graph/threaded_exec.rb +93 -0
  39. data/lib/roast/graph.rb +233 -0
  40. data/lib/roast/resources/api_resource.rb +2 -2
  41. data/lib/roast/resources/url_resource.rb +2 -2
  42. data/lib/roast/tools/apply_diff.rb +1 -1
  43. data/lib/roast/tools/ask_user.rb +1 -1
  44. data/lib/roast/tools/bash.rb +1 -1
  45. data/lib/roast/tools/cmd.rb +2 -2
  46. data/lib/roast/tools/coding_agent.rb +2 -2
  47. data/lib/roast/tools/grep.rb +1 -1
  48. data/lib/roast/tools/read_file.rb +1 -1
  49. data/lib/roast/tools/search_file.rb +1 -1
  50. data/lib/roast/tools/swarm.rb +1 -1
  51. data/lib/roast/tools/update_files.rb +2 -2
  52. data/lib/roast/tools/write_file.rb +1 -1
  53. data/lib/roast/tools.rb +1 -1
  54. data/lib/roast/value_objects/api_token.rb +1 -1
  55. data/lib/roast/value_objects/uri_base.rb +1 -1
  56. data/lib/roast/value_objects/workflow_path.rb +1 -1
  57. data/lib/roast/version.rb +1 -1
  58. data/lib/roast/workflow/base_workflow.rb +38 -2
  59. data/lib/roast/workflow/command_executor.rb +1 -1
  60. data/lib/roast/workflow/configuration_loader.rb +1 -1
  61. data/lib/roast/workflow/error_handler.rb +1 -1
  62. data/lib/roast/workflow/step_executor_registry.rb +1 -1
  63. data/lib/roast/workflow/step_loader.rb +1 -1
  64. data/lib/roast/workflow/workflow_executor.rb +1 -1
  65. data/lib/roast.rb +1 -1
  66. data/sorbet/config +2 -0
  67. data/sorbet/rbi/annotations/.gitattributes +1 -0
  68. data/sorbet/rbi/annotations/activesupport.rbi +495 -0
  69. data/sorbet/rbi/annotations/faraday.rbi +17 -0
  70. data/sorbet/rbi/annotations/minitest.rbi +119 -0
  71. data/sorbet/rbi/annotations/mocha.rbi +34 -0
  72. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  73. data/sorbet/rbi/annotations/webmock.rbi +9 -0
  74. data/sorbet/rbi/gems/rbs-inline@0.12.0.rbi +2170 -0
  75. data/sorbet/rbi/gems/{rexml@3.4.1.rbi → rexml@3.4.2.rbi} +284 -239
  76. data/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +11 -0
  77. data/sorbet/rbi/shims/lib/roast/dsl/workflow_execution_context.rbi +11 -0
  78. data/sorbet/rbi/todo.rbi +7 -0
  79. metadata +46 -5
  80. data/package-lock.json +0 -6
  81. /data/sorbet/rbi/gems/{rack@2.2.17.rbi → rack@2.2.18.rbi} +0 -0
data/lib/roast/errors.rb CHANGED
@@ -4,12 +4,12 @@
4
4
  module Roast
5
5
  module Errors
6
6
  # Custom error for API resource not found (404) responses
7
- class ResourceNotFoundError < StandardError; end
7
+ class ResourceNotFoundError < Roast::Error; end
8
8
 
9
9
  # Custom error for when API authentication fails
10
- class AuthenticationError < StandardError; end
10
+ class AuthenticationError < Roast::Error; end
11
11
 
12
12
  # Exit the app, for instance via Ctrl-C during an InputStep
13
- class ExitEarly < StandardError; end
13
+ class ExitEarly < Roast::Error; end
14
14
  end
15
15
  end
@@ -0,0 +1,25 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ class Graph
6
+ class Edge
7
+ #: (Node) -> void
8
+ attr_writer :join_node
9
+
10
+ attr_reader :from_node, :to_node
11
+
12
+ #: (Node, Node, ?proc: Proc?) -> void
13
+ def initialize(from_node, to_node, proc: nil)
14
+ @from_node = from_node
15
+ @to_node = to_node
16
+ @proc = proc # TODO: Shadowing proc builtin here
17
+ end
18
+
19
+ #: () -> String
20
+ def to_s
21
+ "#{@from_node} -> #{@to_node}"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,40 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ class Graph
6
+ class Node
7
+ # class InvalidExecutableError < Roast::Error; end
8
+
9
+ attr_reader :name, :executable
10
+
11
+ #: (Symbol, ?executable: Proc | Graph | nil) -> void
12
+ def initialize(name, executable: nil)
13
+ @name = name
14
+ @executable = executable
15
+ end
16
+
17
+ #: () -> T::Boolean
18
+ def subgraph?
19
+ @executable.is_a?(Graph)
20
+ end
21
+
22
+ #: () -> T::Boolean
23
+ def done?
24
+ @name == :DONE
25
+ end
26
+
27
+ #: (Hash) -> void
28
+ def execute(state)
29
+ return if @executable.nil?
30
+
31
+ executable = @executable
32
+ if executable.is_a?(Proc)
33
+ executable.call(state)
34
+ elsif executable.is_a?(Graph)
35
+ executable.execute(state)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ class Graph
6
+ class QuantumEdge
7
+ #: (Roast::Graph::Node, Proc) -> void
8
+ def initialize(from_node, to_proc)
9
+ @from_node = from_node
10
+ @to_proc = to_proc
11
+ end
12
+
13
+ #: (Hash[untyped, untyped], Hash[Symbol, Roast::Graph::Node]) -> Array[Roast::Graph::Edge]
14
+ def collapse(state, nodes)
15
+ to_node_names = @to_proc.call(state)
16
+ to_node_names = to_node_names.is_a?(Array) ? to_node_names : [to_node_names]
17
+
18
+ to_node_names.map do |to_node_name|
19
+ to_node = nodes[to_node_name]
20
+ raise Error, "No node found with name #{to_node_name.inspect}" if to_node.nil?
21
+
22
+ Edge.new(@from_node, to_node)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,93 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ class Graph
6
+ class StateConflictError < Error; end
7
+
8
+ class ThreadedExec
9
+ def initialize(nodes, og_state)
10
+ @nodes = nodes
11
+ @og_state = og_state
12
+ end
13
+
14
+ #: () -> Hash[untyped, untyped]
15
+ def async_execute
16
+ states = threaded_execute(@nodes, @og_state)
17
+ merge_states!(@og_state, states)
18
+ @og_state
19
+ end
20
+
21
+ # Returns a hash of the new states for each node.
22
+ #: (Array[Node], Hash[untyped, untyped]) -> Hash[Symbol, Hash[untyped, untyped]]
23
+ def threaded_execute(nodes, og_state)
24
+ states = {}
25
+ threads = nodes.map do |current_node|
26
+ states[current_node.name] = og_state.dup
27
+ Thread.new do
28
+ current_node.execute(states[current_node.name])
29
+ end
30
+ end
31
+
32
+ threads.map(&:value)
33
+
34
+ states
35
+ end
36
+
37
+ #: (Hash, Hash) -> void
38
+ def merge_states!(orig_state, new_states)
39
+ orig_state.merge!(merge_new_states(orig_state, new_states))
40
+ end
41
+
42
+ # We merge all the new states together first so we can catch any keys that were modified by multiple threads.
43
+ #: (Hash[untyped, untyped], Hash[Symbol, Hash[untyped, untyped]]) -> Hash[untyped, untyped]
44
+ def merge_new_states(orig_state, new_states)
45
+ # We only care about what the threads have changes from the original state.
46
+ changed_states = changed_states(orig_state, new_states)
47
+
48
+ return {} if changed_states.empty?
49
+
50
+ # Grab some entry to be the one we merge all the other changes into.
51
+ base_node_name, base_changed_state = T.must(changed_states.shift)
52
+ base_node_name = T.cast(base_node_name, Symbol)
53
+ base_changed_state = T.cast(base_changed_state, T::Hash[T.untyped, T.untyped])
54
+
55
+ changed_states.each do |node_name, changed_state|
56
+ base_changed_state.merge!(changed_state) do |key, old_value, new_value|
57
+ raise_state_conflict(key, old_value, new_value, [base_node_name, node_name])
58
+ end
59
+ end
60
+
61
+ base_changed_state
62
+ end
63
+
64
+ #: (Hash, Hash[Symbol, Hash]) -> Hash[Symbol, Hash]
65
+ def changed_states(orig_state, new_states)
66
+ new_states.transform_values do |new_state|
67
+ changed_state_entries(orig_state, new_state)
68
+ end
69
+ end
70
+
71
+ # Filter new states content to only include keys that were modified by the new states.
72
+ #: (Hash, Hash) -> Hash
73
+ def changed_state_entries(orig_state, new_state)
74
+ new_state.reject do |key, value|
75
+ orig_state[key] == value
76
+ end
77
+ end
78
+
79
+ #: (Symbol, untyped, untyped, Array[Symbol]) -> void
80
+ def raise_state_conflict(key, old_value, new_value, conflicting_nodes)
81
+ raise StateConflictError, <<~CONFLICT.chomp
82
+ Parallel nodes modified the same state key.
83
+ Conflicting nodes: #{conflicting_nodes.join(", ")}
84
+ Key: :#{key}
85
+ Old value:
86
+ #{old_value.inspect}
87
+ New value:
88
+ #{new_value.inspect}
89
+ CONFLICT
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,233 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ class Graph
6
+ class Error < StandardError; end
7
+ class AddEdgeError < Error; end
8
+ class EdgeTopologyError < Error; end
9
+
10
+ #: (Symbol) { (Graph) -> void } -> void
11
+ def subgraph(name, &block)
12
+ subgraph = Graph.new
13
+ block.call(subgraph)
14
+ nodes[name] = Node.new(name, executable: subgraph)
15
+ end
16
+
17
+ #: (Symbol) { () -> void } -> void
18
+ def node(name, &block)
19
+ nodes[name] = Node.new(name, executable: block)
20
+ end
21
+
22
+ #: (from: Symbol | Array[Symbol], ?to: Symbol | Array[Symbol] | nil) ?{ () -> void } -> void
23
+ def edge(from:, to: nil, &block)
24
+ from_nodes = from.is_a?(Array) ? from.map { |from_node| nodes[from_node] } : [nodes[from]].compact
25
+
26
+ if from_nodes.empty?
27
+ raise AddEdgeError, "Cannot create edge from #{from.inspect} to #{to.inspect} because #{from.inspect} does not exist"
28
+ end
29
+
30
+ if block.nil? && !to.nil?
31
+ to_nodes = to.is_a?(Array) ? to.map { |to_node| nodes[to_node] } : [nodes[to]].compact
32
+
33
+ if to_nodes.empty?
34
+ raise AddEdgeError, "Cannot create edge from #{from.inspect} to #{to.inspect} because #{to.inspect} does not exist"
35
+ end
36
+
37
+ from_nodes.each do |from_node|
38
+ to_nodes.each do |to_node|
39
+ insert_edge(Edge.new(T.must(from_node), T.must(to_node)))
40
+ end
41
+ end
42
+ elsif !block.nil? && to.nil?
43
+ from_nodes.each do |from_node|
44
+ quantum_edges[T.must(from_node).name] = QuantumEdge.new(T.must(from_node), T.must(block))
45
+ end
46
+ elsif !block.nil? && !to.nil?
47
+ raise AddEdgeError, "Must provide either a to node or a block, not both"
48
+ else
49
+ raise AddEdgeError, "Must provide either a to node or a block"
50
+ end
51
+ end
52
+
53
+ #: (?Hash[untyped, untyped]?) -> void
54
+ def execute(init_state = nil)
55
+ return if nodes.empty?
56
+
57
+ # HACK: Move the DONE node to the end of the nodes array.
58
+ # In reality we should have a separate "ordered" representation of the nodes, and we store the
59
+ # main thing as a set.
60
+ nodes[:DONE] = T.must(nodes.delete(:DONE))
61
+
62
+ self.state = init_state unless init_state.nil?
63
+
64
+ current_nodes = T.let([T.must(nodes.values.first)], T::Array[Roast::Graph::Node])
65
+
66
+ until current_nodes.any? { |node| T.must(node).done? }
67
+ if current_nodes.size == 1
68
+ T.must(current_nodes.first).execute(state)
69
+ else
70
+ ThreadedExec.new(current_nodes, state).async_execute
71
+ end
72
+
73
+ current_nodes = find_next(current_nodes)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ #: () -> Hash[Symbol, Node]
80
+ def nodes
81
+ @nodes ||= { START: Node.new(:START), DONE: Node.new(:DONE) }
82
+ end
83
+
84
+ #: () -> Hash[Symbol, Array[Edge]]
85
+ def edges
86
+ @edges ||= {}
87
+ end
88
+
89
+ #: () -> Hash[Symbol, Roast::Graph::QuantumEdge]
90
+ def quantum_edges
91
+ @quantum_edges ||= {}
92
+ end
93
+
94
+ #: () -> Hash
95
+ def state
96
+ @state ||= {}
97
+ end
98
+
99
+ #: (Hash) -> void
100
+ def state=(new_state)
101
+ raise Error, "State already set, cannot set it again" if !@state.nil? && !new_state.nil?
102
+
103
+ @state = new_state
104
+ end
105
+
106
+ #: (Array[Node]) -> Array[Node]
107
+ def find_next(current_nodes)
108
+ raise Error, "Somehow got an empty array of nodes" if current_nodes.empty?
109
+
110
+ collapse_quantum_edges(current_nodes)
111
+
112
+ next_edges = if current_nodes.size == 1
113
+ next_edges_for_node(T.must(current_nodes.first))
114
+ elsif current_nodes.size > 1
115
+ next_edges_for_nodes(current_nodes)
116
+ end
117
+
118
+ if next_edges.nil?
119
+ raise EdgeTopologyError, "No next edges found for #{current_nodes.map(&:name).join(", ")}, please define edges for this node"
120
+ end
121
+
122
+ next_nodes = next_edges.map(&:to_node).uniq
123
+
124
+ # If we're doing paralell nodes, we need to lookahead to ensuer we join back at the same node.
125
+ if next_nodes.size > 1
126
+ raise EdgeTopologyError, "Parallel execution many to many nodes is not supported" if current_nodes.size != 1
127
+
128
+ collapse_quantum_edges(next_nodes)
129
+ raise_unless_all_point_to_same_next_node?(next_nodes)
130
+ end
131
+
132
+ next_nodes
133
+ end
134
+
135
+ #: (Array[Node]) -> bool
136
+ def raise_unless_all_point_to_same_next_node?(nodes)
137
+ next_nodes = nodes.map { |node| edges_from(node) }.compact.flatten.map(&:to_node).uniq
138
+ # TODO: Deal with when next_nodes here is empty, should be generic "if you define any edges, you must define them all"
139
+ if next_nodes.size > 1
140
+ Roast::Helpers::Logger.info("Next nodes: #{next_nodes.map(&:name).join(", ")}")
141
+ raise EdgeTopologyError, "Parallel nodes #{nodes.map(&:name).join(", ")} have different next nodes: #{next_nodes.inspect}"
142
+ end
143
+
144
+ true
145
+ end
146
+
147
+ #: (Array[Node]) -> void
148
+ def collapse_quantum_edges(current_nodes)
149
+ curr_quantum_edges = current_nodes.map do |node|
150
+ quantum_edges[node.name]
151
+ end.compact
152
+
153
+ return if curr_quantum_edges.empty?
154
+
155
+ if curr_quantum_edges.size > 1
156
+ raise EdgeTopologyError, <<~MANY_Q_EDGES
157
+ Multiple quantum edges for nodes:
158
+ Nodes: #{current_nodes.map(&:name).join(", ")}"
159
+ Quantum Edges: #{quantum_edges.inspect}
160
+ MANY_Q_EDGES
161
+ end
162
+
163
+ edges = curr_quantum_edges.map do |quantum_edge|
164
+ quantum_edge.collapse(state, nodes)
165
+ end
166
+
167
+ edges.flatten.each do |edge|
168
+ insert_edge(edge)
169
+ end
170
+ end
171
+
172
+ #: (Node) -> Array[Edge]
173
+ def next_edges_for_node(current_node)
174
+ next_edges = edges_from(current_node)
175
+ # If the user never defined any edges, we'll just use the next node in the file.
176
+ next_edges ||= [edge_from_next_loaded(current_node.name)].compact if edges.empty?
177
+ T.must(next_edges)
178
+ end
179
+
180
+ #: (Array[Node]) -> Array[Edge]
181
+ def next_edges_for_nodes(current_nodes)
182
+ maybe_next_edges = current_nodes.map { |node| edges_from(node) }.compact.flatten
183
+
184
+ # Verify there are same number of edges as nodes.
185
+ if maybe_next_edges.size != current_nodes.size
186
+ raise EdgeTopologyError, <<~WRONG_NUM_EDGES
187
+ Parallel nodes have different numbers of edges:
188
+ Next Edges: #{maybe_next_edges}
189
+ Parallel nodes: #{current_nodes.map(&:name).join(", ")}
190
+ WRONG_NUM_EDGES
191
+ end
192
+
193
+ # Verify all the edges go to the same place.
194
+ uniq_to_nodes = maybe_next_edges.map(&:to_node).uniq!
195
+ if T.must(uniq_to_nodes).size != 1
196
+ # TODO: Present which edges are going to different places.
197
+ raise EdgeTopologyError, <<~WRONG_NUM_EDGES
198
+ Parallel nodes end up at different nodes:
199
+ Next Edges: #{maybe_next_edges}
200
+ Parallel nodes: #{current_nodes.map(&:name).join(", ")}
201
+ WRONG_NUM_EDGES
202
+ end
203
+
204
+ maybe_next_edges
205
+ end
206
+
207
+ #: (Node) -> Array[Edge]?
208
+ def edges_from(from_node)
209
+ edges[from_node.name]
210
+ end
211
+
212
+ #: (Edge) -> void
213
+ def insert_edge(edge)
214
+ edges[edge.from_node.name] ||= []
215
+ T.must(edges[edge.from_node.name]) << edge
216
+ end
217
+
218
+ #: (Symbol) -> Edge?
219
+ def edge_from_next_loaded(current_node_name)
220
+ next_node = next_loaded_node(current_node_name)
221
+ return if next_node.nil?
222
+
223
+ Edge.new(T.must(nodes[current_node_name]), next_node)
224
+ end
225
+
226
+ #: (Symbol) -> Node?
227
+ def next_loaded_node(current_node_name)
228
+ current_index = nodes.keys.index(current_node_name)
229
+ next_index = (T.must(current_index) + 1)
230
+ nodes.values[next_index]
231
+ end
232
+ end
233
+ end
@@ -74,7 +74,7 @@ module Roast
74
74
 
75
75
  # Consider 2xx and 3xx as success
76
76
  response.code.to_i < 400
77
- rescue StandardError => e
77
+ rescue Roast::Error => e
78
78
  # Log the error but don't crash
79
79
  Roast::Helpers::Logger.error("Error checking API existence: #{e.message}")
80
80
  false
@@ -96,7 +96,7 @@ module Roast
96
96
  begin
97
97
  uri = URI.parse(target)
98
98
  Net::HTTP.get(uri)
99
- rescue StandardError => e
99
+ rescue Roast::Error => e
100
100
  # Log the error but don't crash
101
101
  Roast::Helpers::Logger.error("Error fetching API contents: #{e.message}")
102
102
  nil
@@ -23,7 +23,7 @@ module Roast
23
23
 
24
24
  # Consider 2xx and 3xx as success
25
25
  response.code.to_i < 400
26
- rescue StandardError => e
26
+ rescue Roast::Error => e
27
27
  # Log the error but don't crash
28
28
  Roast::Helpers::Logger.error("Error checking URL existence: #{e.message}")
29
29
  false
@@ -36,7 +36,7 @@ module Roast
36
36
  begin
37
37
  uri = URI.parse(target)
38
38
  Net::HTTP.get(uri)
39
- rescue StandardError => e
39
+ rescue Roast::Error => e
40
40
  # Log the error but don't crash
41
41
  Roast::Helpers::Logger.error("Error fetching URL contents: #{e.message}")
42
42
  nil
@@ -64,7 +64,7 @@ module Roast
64
64
  Roast::Helpers::Logger.info(cancel_msg + "\n")
65
65
  cancel_msg
66
66
  end
67
- rescue StandardError => e
67
+ rescue Roast::Error => e
68
68
  error_message = "Error applying diff: #{e.message}"
69
69
  Roast::Helpers::Logger.error(error_message + "\n")
70
70
  Roast::Helpers::Logger.debug(e.backtrace.join("\n") + "\n") if ENV["DEBUG"]
@@ -28,7 +28,7 @@ module Roast
28
28
 
29
29
  Roast::Helpers::Logger.info("User responded: #{response}\n")
30
30
  response
31
- rescue StandardError => e
31
+ rescue Roast::Error => e
32
32
  "Error getting user input: #{e.message}".tap do |error_message|
33
33
  Roast::Helpers::Logger.error(error_message + "\n")
34
34
  Roast::Helpers::Logger.debug(e.backtrace.join("\n") + "\n") if ENV["DEBUG"]
@@ -43,7 +43,7 @@ module Roast
43
43
  rescue Timeout::Error => e
44
44
  Roast::Helpers::Logger.error(e.message + "\n")
45
45
  e.message
46
- rescue StandardError => e
46
+ rescue Roast::Error => e
47
47
  handle_error(e)
48
48
  end
49
49
 
@@ -93,7 +93,7 @@ module Roast
93
93
  Roast::Helpers::Logger.info("🔧 Running command: #{full_command}\n")
94
94
 
95
95
  execute_command(full_command, command_prefix, timeout)
96
- rescue StandardError => e
96
+ rescue Roast::Error => e
97
97
  handle_error(e)
98
98
  end
99
99
 
@@ -108,7 +108,7 @@ module Roast
108
108
  command_prefix = command.split(" ").first
109
109
 
110
110
  execute_command(command, command_prefix, timeout)
111
- rescue StandardError => e
111
+ rescue Roast::Error => e
112
112
  handle_error(e)
113
113
  end
114
114
 
@@ -7,7 +7,7 @@ module Roast
7
7
  extend self
8
8
  include Roast::Helpers::MetadataAccess
9
9
 
10
- class CodingAgentError < StandardError; end
10
+ class CodingAgentError < Roast::Error; end
11
11
 
12
12
  CONFIG_CODING_AGENT_COMMAND = "coding_agent_command"
13
13
  private_constant :CONFIG_CODING_AGENT_COMMAND
@@ -59,7 +59,7 @@ module Roast
59
59
  Roast::Helpers::Logger.debug(e.backtrace.join("\n") + "\n") if ENV["DEBUG"]
60
60
  end
61
61
  Roast::Helpers::Logger.error("🤖 CodingAgent did not complete successfully after multiple retries")
62
- rescue StandardError => e
62
+ rescue Roast::Error => e
63
63
  "🤖 Error running CodingAgent: #{e.message}".tap do |error_message|
64
64
  Roast::Helpers::Logger.error(error_message + "\n")
65
65
  Roast::Helpers::Logger.debug(e.backtrace.join("\n") + "\n") if ENV["DEBUG"]
@@ -47,7 +47,7 @@ module Roast
47
47
  else
48
48
  stdout
49
49
  end
50
- rescue StandardError => e
50
+ rescue Roast::Error => e
51
51
  "Error grepping for string: #{e.message}".tap do |error_message|
52
52
  Roast::Helpers::Logger.error(error_message + "\n")
53
53
  Roast::Helpers::Logger.debug(e.backtrace.join("\n") + "\n") if ENV["DEBUG"]
@@ -39,7 +39,7 @@ module Roast
39
39
  else
40
40
  File.read(path)
41
41
  end
42
- rescue StandardError => e
42
+ rescue Roast::Error => 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"]
@@ -44,7 +44,7 @@ module Roast
44
44
 
45
45
  results.map { |result| File.join(path, result) }.join("\n") # purposely give the AI list of actual paths so that it can read without searching first
46
46
  end
47
- rescue StandardError => e
47
+ rescue Roast::Error => e
48
48
  "Error searching for '#{glob_pattern}' in '#{path}': #{e.message}".tap do |error_message|
49
49
  Roast::Helpers::Logger.error(error_message + "\n")
50
50
  Roast::Helpers::Logger.debug(e.backtrace.join("\n") + "\n") if ENV["DEBUG"]
@@ -60,7 +60,7 @@ module Roast
60
60
  Roast::Helpers::Logger.info("🐝 Running Claude Swarm with config: #{config_path}\n")
61
61
 
62
62
  execute_swarm(prompt, config_path)
63
- rescue StandardError => e
63
+ rescue Roast::Error => e
64
64
  handle_error(e)
65
65
  end
66
66
 
@@ -70,7 +70,7 @@ module Roast
70
70
 
71
71
  # Apply changes atomically
72
72
  apply_changes(file_changes, base_path, create_files)
73
- rescue StandardError => e
73
+ rescue Roast::Error => e
74
74
  "Error applying patch: #{e.message}".tap do |error_message|
75
75
  Roast::Helpers::Logger.error(error_message + "\n")
76
76
  Roast::Helpers::Logger.debug(e.backtrace.join("\n") + "\n") if ENV["DEBUG"]
@@ -316,7 +316,7 @@ module Roast
316
316
  end
317
317
 
318
318
  "Successfully applied patch to #{modified_files.size} file(s): #{modified_files.join(", ")}"
319
- rescue StandardError => e
319
+ rescue Roast::Error => e
320
320
  # Restore backups if any change fails
321
321
  backup_files.each do |path, content|
322
322
  File.write(path, content) if File.exist?(path)
@@ -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 StandardError => e
52
+ rescue Roast::Error => 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/tools.rb CHANGED
@@ -24,7 +24,7 @@ module Roast
24
24
 
25
25
  #{File.read(file)}
26
26
  PROMPT
27
- rescue StandardError => e
27
+ rescue Roast::Error => e
28
28
  Roast::Helpers::Logger.error("In current directory: #{Dir.pwd}\n")
29
29
  Roast::Helpers::Logger.error("Error reading file #{file}\n")
30
30
 
@@ -5,7 +5,7 @@ module Roast
5
5
  module ValueObjects
6
6
  # Value object representing an API token with validation
7
7
  class ApiToken
8
- class InvalidTokenError < StandardError; end
8
+ class InvalidTokenError < Roast::Error; end
9
9
 
10
10
  attr_reader :value
11
11
 
@@ -5,7 +5,7 @@ module Roast
5
5
  module ValueObjects
6
6
  # Value object representing a URI base with validation
7
7
  class UriBase
8
- class InvalidUriBaseError < StandardError; end
8
+ class InvalidUriBaseError < Roast::Error; end
9
9
 
10
10
  attr_reader :value
11
11
 
@@ -5,7 +5,7 @@ module Roast
5
5
  module ValueObjects
6
6
  # Value object representing a workflow file path with validation and resolution
7
7
  class WorkflowPath
8
- class InvalidPathError < StandardError; end
8
+ class InvalidPathError < Roast::Error; end
9
9
 
10
10
  attr_reader :value
11
11
 
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.8"
5
+ VERSION = "0.4.9"
6
6
  end