roast-ai 0.4.7 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +1 -0
- data/Gemfile.lock +3 -3
- data/README.md +9 -5
- data/Rakefile +2 -0
- data/dsl/less_simple.rb +112 -0
- data/dsl/prototype.rb +17 -0
- data/dsl/simple.rb +5 -7
- data/dsl/step_communication.rb +18 -0
- data/examples/README.md +9 -0
- data/examples/available_tools_demo/workflow.yml +1 -1
- data/examples/basic_prompt_workflow/workflow.md +1 -0
- data/examples/basic_prompt_workflow/workflow.yml +14 -0
- data/examples/grading/README.md +1 -26
- data/examples/grading/analyze_coverage/prompt.md +1 -1
- data/examples/grading/calculate_final_grade.rb +10 -13
- data/examples/grading/format_result.rb +5 -8
- data/examples/grading/generate_grades/prompt.md +1 -1
- data/examples/grading/generate_recommendations/prompt.md +1 -1
- data/examples/grading/read_dependencies/prompt.md +0 -1
- data/examples/grading/verify_test_helpers/prompt.md +1 -1
- data/examples/grading/workflow.md +1 -4
- data/examples/grading/workflow.yml +3 -16
- data/lib/roast/dsl/cog/config.rb +31 -0
- data/lib/roast/dsl/cog/stack.rb +21 -0
- data/lib/roast/dsl/cog/store.rb +26 -0
- data/lib/roast/dsl/cog.rb +70 -0
- data/lib/roast/dsl/cog_execution_context.rb +29 -0
- data/lib/roast/dsl/cogs/cmd.rb +55 -0
- data/lib/roast/dsl/cogs/graph.rb +53 -0
- data/lib/roast/dsl/cogs.rb +65 -0
- data/lib/roast/dsl/config_context.rb +54 -0
- data/lib/roast/dsl/executor.rb +62 -7
- data/lib/roast/dsl/workflow_execution_context.rb +47 -0
- data/lib/roast/error.rb +7 -0
- data/lib/roast/errors.rb +3 -3
- data/lib/roast/graph/edge.rb +25 -0
- data/lib/roast/graph/node.rb +40 -0
- data/lib/roast/graph/quantum_edge.rb +27 -0
- data/lib/roast/graph/threaded_exec.rb +93 -0
- data/lib/roast/graph.rb +233 -0
- data/lib/roast/resources/api_resource.rb +2 -2
- data/lib/roast/resources/url_resource.rb +2 -2
- data/lib/roast/tools/apply_diff.rb +1 -1
- data/lib/roast/tools/ask_user.rb +1 -1
- data/lib/roast/tools/bash.rb +1 -1
- data/lib/roast/tools/cmd.rb +2 -2
- data/lib/roast/tools/coding_agent.rb +2 -2
- data/lib/roast/tools/grep.rb +1 -1
- data/lib/roast/tools/read_file.rb +1 -1
- data/lib/roast/tools/search_file.rb +1 -1
- data/lib/roast/tools/swarm.rb +1 -1
- data/lib/roast/tools/update_files.rb +2 -2
- data/lib/roast/tools/write_file.rb +1 -1
- data/lib/roast/tools.rb +1 -1
- data/lib/roast/value_objects/api_token.rb +1 -1
- data/lib/roast/value_objects/uri_base.rb +1 -1
- data/lib/roast/value_objects/workflow_path.rb +1 -1
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/base_step.rb +2 -3
- data/lib/roast/workflow/base_workflow.rb +38 -2
- data/lib/roast/workflow/command_executor.rb +1 -1
- data/lib/roast/workflow/configuration_loader.rb +1 -1
- data/lib/roast/workflow/error_handler.rb +1 -1
- data/lib/roast/workflow/step_executor_registry.rb +1 -1
- data/lib/roast/workflow/step_loader.rb +3 -8
- data/lib/roast/workflow/workflow_executor.rb +1 -1
- data/lib/roast.rb +7 -2
- data/sorbet/config +2 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/activesupport.rbi +495 -0
- data/sorbet/rbi/annotations/faraday.rbi +17 -0
- data/sorbet/rbi/annotations/minitest.rbi +119 -0
- data/sorbet/rbi/annotations/mocha.rbi +34 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/annotations/webmock.rbi +9 -0
- data/sorbet/rbi/gems/rbs-inline@0.12.0.rbi +2170 -0
- data/sorbet/rbi/gems/{rexml@3.4.1.rbi → rexml@3.4.2.rbi} +284 -239
- data/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +11 -0
- data/sorbet/rbi/shims/lib/roast/dsl/workflow_execution_context.rbi +11 -0
- data/sorbet/rbi/todo.rbi +7 -0
- metadata +37 -231
- data/CHANGELOG.md +0 -369
- data/examples/agent_continue/add_documentation/prompt.md +0 -5
- data/examples/agent_continue/add_error_handling/prompt.md +0 -5
- data/examples/agent_continue/analyze_codebase/prompt.md +0 -7
- data/examples/agent_continue/combined_workflow.yml +0 -24
- data/examples/agent_continue/continue_adding_features/prompt.md +0 -4
- data/examples/agent_continue/create_integration_tests/prompt.md +0 -3
- data/examples/agent_continue/document_with_context/prompt.md +0 -5
- data/examples/agent_continue/explore_api/prompt.md +0 -6
- data/examples/agent_continue/implement_client/prompt.md +0 -6
- data/examples/agent_continue/inline_workflow.yml +0 -20
- data/examples/agent_continue/refactor_code/prompt.md +0 -2
- data/examples/agent_continue/verify_changes/prompt.md +0 -6
- data/examples/agent_continue/workflow.yml +0 -27
- data/examples/agent_workflow/README.md +0 -75
- data/examples/agent_workflow/apply_refactorings/prompt.md +0 -22
- data/examples/agent_workflow/identify_code_smells/prompt.md +0 -15
- data/examples/agent_workflow/summarize_improvements/prompt.md +0 -18
- data/examples/agent_workflow/workflow.png +0 -0
- data/examples/agent_workflow/workflow.yml +0 -16
- data/examples/api_workflow/README.md +0 -85
- data/examples/api_workflow/fetch_api_data/prompt.md +0 -10
- data/examples/api_workflow/generate_report/prompt.md +0 -10
- data/examples/api_workflow/prompt.md +0 -10
- data/examples/api_workflow/transform_data/prompt.md +0 -10
- data/examples/api_workflow/workflow.png +0 -0
- data/examples/api_workflow/workflow.yml +0 -30
- data/examples/apply_diff_demo/README.md +0 -58
- data/examples/apply_diff_demo/apply_simple_change/prompt.md +0 -13
- data/examples/apply_diff_demo/create_sample_file/prompt.md +0 -11
- data/examples/apply_diff_demo/workflow.yml +0 -24
- data/examples/available_tools_demo/workflow.png +0 -0
- data/examples/bash_prototyping/README.md +0 -53
- data/examples/bash_prototyping/analyze_network/prompt.md +0 -13
- data/examples/bash_prototyping/analyze_system/prompt.md +0 -11
- data/examples/bash_prototyping/api_testing.png +0 -0
- data/examples/bash_prototyping/api_testing.yml +0 -14
- data/examples/bash_prototyping/check_processes/prompt.md +0 -11
- data/examples/bash_prototyping/generate_report/prompt.md +0 -16
- data/examples/bash_prototyping/process_json_response/prompt.md +0 -24
- data/examples/bash_prototyping/system_analysis.png +0 -0
- data/examples/bash_prototyping/system_analysis.yml +0 -14
- data/examples/bash_prototyping/test_public_api/prompt.md +0 -22
- data/examples/case_when/README.md +0 -58
- data/examples/case_when/detect_language/prompt.md +0 -16
- data/examples/case_when/workflow.png +0 -0
- data/examples/case_when/workflow.yml +0 -58
- data/examples/cmd/README.md +0 -99
- data/examples/cmd/analyze_project/prompt.md +0 -57
- data/examples/cmd/basic_demo/prompt.md +0 -48
- data/examples/cmd/basic_workflow.png +0 -0
- data/examples/cmd/basic_workflow.yml +0 -16
- data/examples/cmd/check_repository/prompt.md +0 -57
- data/examples/cmd/create_and_verify/prompt.md +0 -56
- data/examples/cmd/dev_workflow.png +0 -0
- data/examples/cmd/dev_workflow.yml +0 -26
- data/examples/cmd/explore_project/prompt.md +0 -67
- data/examples/cmd/explorer_workflow.png +0 -0
- data/examples/cmd/explorer_workflow.yml +0 -21
- data/examples/cmd/smart_tool_selection/prompt.md +0 -99
- data/examples/coding_agent_with_model.yml +0 -20
- data/examples/coding_agent_with_retries.yml +0 -30
- data/examples/conditional/README.md +0 -161
- data/examples/conditional/check_condition/prompt.md +0 -1
- data/examples/conditional/simple_workflow.png +0 -0
- data/examples/conditional/simple_workflow.yml +0 -15
- data/examples/conditional/workflow.png +0 -0
- data/examples/conditional/workflow.yml +0 -23
- data/examples/context_management_demo/README.md +0 -43
- data/examples/context_management_demo/workflow.yml +0 -42
- data/examples/direct_coerce_syntax/README.md +0 -32
- data/examples/direct_coerce_syntax/workflow.png +0 -0
- data/examples/direct_coerce_syntax/workflow.yml +0 -36
- data/examples/dot_notation/README.md +0 -37
- data/examples/dot_notation/workflow.png +0 -0
- data/examples/dot_notation/workflow.yml +0 -44
- data/examples/exit_on_error/README.md +0 -50
- data/examples/exit_on_error/analyze_lint_output/prompt.md +0 -9
- data/examples/exit_on_error/apply_fixes/prompt.md +0 -2
- data/examples/exit_on_error/workflow.png +0 -0
- data/examples/exit_on_error/workflow.yml +0 -19
- data/examples/grading/js_test_runner +0 -31
- data/examples/grading/rb_test_runner +0 -19
- data/examples/grading/run_coverage.rb +0 -54
- data/examples/grading/workflow.png +0 -0
- data/examples/grading/workflow.rb.md +0 -6
- data/examples/grading/workflow.ts+tsx.md +0 -6
- data/examples/instrumentation.rb +0 -76
- data/examples/interpolation/README.md +0 -50
- data/examples/interpolation/analyze_file/prompt.md +0 -1
- data/examples/interpolation/analyze_patterns/prompt.md +0 -27
- data/examples/interpolation/generate_report_for_js/prompt.md +0 -3
- data/examples/interpolation/generate_report_for_rb/prompt.md +0 -3
- data/examples/interpolation/sample.js +0 -48
- data/examples/interpolation/sample.rb +0 -42
- data/examples/interpolation/workflow.md +0 -1
- data/examples/interpolation/workflow.png +0 -0
- data/examples/interpolation/workflow.yml +0 -21
- data/examples/iteration/IMPLEMENTATION.md +0 -88
- data/examples/iteration/README.md +0 -68
- data/examples/iteration/analyze_complexity/prompt.md +0 -22
- data/examples/iteration/generate_recommendations/prompt.md +0 -21
- data/examples/iteration/generate_report/prompt.md +0 -129
- data/examples/iteration/implement_fix/prompt.md +0 -25
- data/examples/iteration/prioritize_issues/prompt.md +0 -24
- data/examples/iteration/prompts/analyze_file.md +0 -28
- data/examples/iteration/prompts/generate_summary.md +0 -24
- data/examples/iteration/prompts/update_report.md +0 -29
- data/examples/iteration/prompts/write_report.md +0 -22
- data/examples/iteration/read_file/prompt.md +0 -9
- data/examples/iteration/select_next_issue/prompt.md +0 -25
- data/examples/iteration/simple_workflow.md +0 -39
- data/examples/iteration/simple_workflow.yml +0 -58
- data/examples/iteration/update_fix_count/prompt.md +0 -26
- data/examples/iteration/verify_fix/prompt.md +0 -29
- data/examples/iteration/workflow.png +0 -0
- data/examples/iteration/workflow.yml +0 -42
- data/examples/json_handling/README.md +0 -32
- data/examples/json_handling/workflow.png +0 -0
- data/examples/json_handling/workflow.yml +0 -52
- data/examples/mcp/README.md +0 -223
- data/examples/mcp/analyze_changes/prompt.md +0 -8
- data/examples/mcp/analyze_issues/prompt.md +0 -4
- data/examples/mcp/analyze_schema/prompt.md +0 -4
- data/examples/mcp/check_data_quality/prompt.md +0 -5
- data/examples/mcp/check_documentation/prompt.md +0 -4
- data/examples/mcp/create_recommendations/prompt.md +0 -5
- data/examples/mcp/database_workflow.png +0 -0
- data/examples/mcp/database_workflow.yml +0 -29
- data/examples/mcp/env_demo/workflow.png +0 -0
- data/examples/mcp/env_demo/workflow.yml +0 -34
- data/examples/mcp/fetch_pr_context/prompt.md +0 -4
- data/examples/mcp/filesystem_demo/create_test_file/prompt.md +0 -2
- data/examples/mcp/filesystem_demo/list_files/prompt.md +0 -6
- data/examples/mcp/filesystem_demo/read_with_mcp/prompt.md +0 -7
- data/examples/mcp/filesystem_demo/workflow.png +0 -0
- data/examples/mcp/filesystem_demo/workflow.yml +0 -38
- data/examples/mcp/generate_insights/prompt.md +0 -4
- data/examples/mcp/generate_report/prompt.md +0 -6
- data/examples/mcp/generate_review/prompt.md +0 -16
- data/examples/mcp/github_workflow.png +0 -0
- data/examples/mcp/github_workflow.yml +0 -32
- data/examples/mcp/multi_mcp_workflow.png +0 -0
- data/examples/mcp/multi_mcp_workflow.yml +0 -58
- data/examples/mcp/post_review/prompt.md +0 -3
- data/examples/mcp/save_report/prompt.md +0 -6
- data/examples/mcp/search_issues/prompt.md +0 -2
- data/examples/mcp/summarize/prompt.md +0 -1
- data/examples/mcp/test_filesystem/prompt.md +0 -6
- data/examples/mcp/test_github/prompt.md +0 -8
- data/examples/mcp/test_read/prompt.md +0 -1
- data/examples/mcp/workflow.png +0 -0
- data/examples/mcp/workflow.yml +0 -35
- data/examples/no_model_fallback/README.md +0 -17
- data/examples/no_model_fallback/analyze_file/prompt.md +0 -1
- data/examples/no_model_fallback/analyze_patterns/prompt.md +0 -27
- data/examples/no_model_fallback/generate_report_for_md/prompt.md +0 -10
- data/examples/no_model_fallback/generate_report_for_rb/prompt.md +0 -3
- data/examples/no_model_fallback/sample.rb +0 -42
- data/examples/no_model_fallback/workflow.yml +0 -19
- data/examples/openrouter_example/README.md +0 -48
- data/examples/openrouter_example/analyze_input/prompt.md +0 -16
- data/examples/openrouter_example/generate_response/prompt.md +0 -9
- data/examples/openrouter_example/workflow.png +0 -0
- data/examples/openrouter_example/workflow.yml +0 -12
- data/examples/pre_post_processing/README.md +0 -111
- data/examples/pre_post_processing/analyze_test_file/prompt.md +0 -23
- data/examples/pre_post_processing/improve_test_coverage/prompt.md +0 -17
- data/examples/pre_post_processing/optimize_test_performance/prompt.md +0 -25
- data/examples/pre_post_processing/post_processing/aggregate_metrics/prompt.md +0 -31
- data/examples/pre_post_processing/post_processing/cleanup_environment/prompt.md +0 -28
- data/examples/pre_post_processing/post_processing/generate_summary_report/prompt.md +0 -32
- data/examples/pre_post_processing/post_processing/output.txt +0 -24
- data/examples/pre_post_processing/pre_processing/gather_baseline_metrics/prompt.md +0 -26
- data/examples/pre_post_processing/pre_processing/setup_test_environment/prompt.md +0 -11
- data/examples/pre_post_processing/validate_changes/prompt.md +0 -24
- data/examples/pre_post_processing/workflow.png +0 -0
- data/examples/pre_post_processing/workflow.yml +0 -21
- data/examples/retry/workflow.yml +0 -23
- data/examples/rspec_to_minitest/README.md +0 -68
- data/examples/rspec_to_minitest/analyze_spec/prompt.md +0 -30
- data/examples/rspec_to_minitest/create_minitest/prompt.md +0 -33
- data/examples/rspec_to_minitest/run_and_improve/prompt.md +0 -35
- data/examples/rspec_to_minitest/workflow.md +0 -10
- data/examples/rspec_to_minitest/workflow.png +0 -0
- data/examples/rspec_to_minitest/workflow.yml +0 -40
- data/examples/shared_config/README.md +0 -52
- data/examples/shared_config/example_with_shared_config/workflow.png +0 -0
- data/examples/shared_config/example_with_shared_config/workflow.yml +0 -6
- data/examples/shared_config/shared.png +0 -0
- data/examples/shared_config/shared.yml +0 -7
- data/examples/single_target_prepost/README.md +0 -36
- data/examples/single_target_prepost/post_processing/output.txt +0 -27
- data/examples/single_target_prepost/pre_processing/gather_dependencies/prompt.md +0 -11
- data/examples/single_target_prepost/workflow.png +0 -0
- data/examples/single_target_prepost/workflow.yml +0 -20
- data/examples/smart_coercion_defaults/README.md +0 -65
- data/examples/smart_coercion_defaults/workflow.png +0 -0
- data/examples/smart_coercion_defaults/workflow.yml +0 -44
- data/examples/step_configuration/README.md +0 -84
- data/examples/step_configuration/workflow.png +0 -0
- data/examples/step_configuration/workflow.yml +0 -57
- data/examples/swarm_example.yml +0 -25
- data/examples/tool_config_example/README.md +0 -109
- data/examples/tool_config_example/example_step/prompt.md +0 -42
- data/examples/tool_config_example/workflow.png +0 -0
- data/examples/tool_config_example/workflow.yml +0 -17
- data/examples/user_input/README.md +0 -90
- data/examples/user_input/funny_name/create_backstory/prompt.md +0 -10
- data/examples/user_input/funny_name/workflow.png +0 -0
- data/examples/user_input/funny_name/workflow.yml +0 -25
- data/examples/user_input/generate_summary/prompt.md +0 -11
- data/examples/user_input/simple_input_demo/workflow.png +0 -0
- data/examples/user_input/simple_input_demo/workflow.yml +0 -35
- data/examples/user_input/survey_workflow.png +0 -0
- data/examples/user_input/survey_workflow.yml +0 -71
- data/examples/user_input/welcome_message/prompt.md +0 -3
- data/examples/user_input/workflow.png +0 -0
- data/examples/user_input/workflow.yml +0 -73
- data/examples/workflow_generator/README.md +0 -27
- data/examples/workflow_generator/analyze_user_request/prompt.md +0 -34
- data/examples/workflow_generator/create_workflow_files/prompt.md +0 -32
- data/examples/workflow_generator/get_user_input/prompt.md +0 -14
- data/examples/workflow_generator/info_from_roast.rb +0 -22
- data/examples/workflow_generator/workflow.png +0 -0
- data/examples/workflow_generator/workflow.yml +0 -34
- data/package-lock.json +0 -6
- /data/sorbet/rbi/gems/{rack@2.2.17.rbi → rack@2.2.18.rbi} +0 -0
data/lib/roast/graph.rb
ADDED
@@ -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
|
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
|
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
|
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
|
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
|
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"]
|
data/lib/roast/tools/ask_user.rb
CHANGED
@@ -28,7 +28,7 @@ module Roast
|
|
28
28
|
|
29
29
|
Roast::Helpers::Logger.info("User responded: #{response}\n")
|
30
30
|
response
|
31
|
-
rescue
|
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"]
|
data/lib/roast/tools/bash.rb
CHANGED
data/lib/roast/tools/cmd.rb
CHANGED
@@ -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
|
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
|
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 <
|
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
|
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"]
|
data/lib/roast/tools/grep.rb
CHANGED
@@ -47,7 +47,7 @@ module Roast
|
|
47
47
|
else
|
48
48
|
stdout
|
49
49
|
end
|
50
|
-
rescue
|
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
|
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
|
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"]
|
data/lib/roast/tools/swarm.rb
CHANGED
@@ -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
|
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
|
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
|
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
data/lib/roast/version.rb
CHANGED
@@ -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
|
-
|
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
|
@@ -113,7 +113,7 @@ module Roast
|
|
113
113
|
log_and_raise_error(error, message, step_model || model, kwargs, execution_time)
|
114
114
|
rescue => e
|
115
115
|
execution_time = Time.now - start_time
|
116
|
-
log_and_raise_error(e, e
|
116
|
+
log_and_raise_error(e, enhanced_message(e), step_model || model, kwargs, execution_time)
|
117
117
|
end
|
118
118
|
|
119
119
|
def with_model(model)
|
@@ -142,7 +142,43 @@ module Roast
|
|
142
142
|
execution_time: execution_time,
|
143
143
|
})
|
144
144
|
|
145
|
-
|
145
|
+
# If we have an enhanced message, create a new error with it
|
146
|
+
if message != error.message
|
147
|
+
new_error = error.class.new(message)
|
148
|
+
new_error.set_backtrace(error.backtrace) if error.backtrace
|
149
|
+
raise new_error
|
150
|
+
else
|
151
|
+
raise error
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def enhanced_message(error)
|
156
|
+
original_message = error.message
|
157
|
+
|
158
|
+
url, status = if error.respond_to?(:response) && error.response.is_a?(Hash)
|
159
|
+
[error.response[:url], error.response[:status]]
|
160
|
+
elsif error.respond_to?(:response_status)
|
161
|
+
[nil, error.response_status]
|
162
|
+
end
|
163
|
+
|
164
|
+
message = if url && status
|
165
|
+
"API call to #{url} failed with status #{status}: #{original_message}"
|
166
|
+
elsif status
|
167
|
+
"API call failed with status #{status}: #{original_message}"
|
168
|
+
else
|
169
|
+
original_message
|
170
|
+
end
|
171
|
+
|
172
|
+
message += if error.respond_to?(:response_body)
|
173
|
+
body = error.response_body
|
174
|
+
error_detail = body.is_a?(Hash) ? body.dig("error", "message") : body.to_s
|
175
|
+
|
176
|
+
if error_detail && !error_detail.empty? && !message.include?(error_detail)
|
177
|
+
" (#{error_detail})"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
message
|
146
182
|
end
|
147
183
|
|
148
184
|
def read_sidecar_prompt
|
@@ -4,7 +4,7 @@
|
|
4
4
|
module Roast
|
5
5
|
module Workflow
|
6
6
|
class CommandExecutor
|
7
|
-
class CommandExecutionError <
|
7
|
+
class CommandExecutionError < Roast::Error
|
8
8
|
attr_reader :command, :exit_status, :original_error, :output
|
9
9
|
|
10
10
|
def initialize(message, command:, exit_status: nil, original_error: nil)
|
@@ -5,7 +5,7 @@ module Roast
|
|
5
5
|
module Workflow
|
6
6
|
# Handles loading and parsing of workflow configuration files
|
7
7
|
class ConfigurationLoader
|
8
|
-
class ValidationError <
|
8
|
+
class ValidationError < Roast::Error; end
|
9
9
|
|
10
10
|
class << self
|
11
11
|
# Load configuration from a YAML file
|
@@ -6,7 +6,7 @@ module Roast
|
|
6
6
|
# Registry pattern for step executors - eliminates case statements
|
7
7
|
# and follows Open/Closed Principle
|
8
8
|
class StepExecutorRegistry
|
9
|
-
class UnknownStepTypeError <
|
9
|
+
class UnknownStepTypeError < Roast::Error; end
|
10
10
|
|
11
11
|
@executors = {}
|
12
12
|
@type_matchers = []
|
@@ -8,7 +8,7 @@ module Roast
|
|
8
8
|
DEFAULT_MODEL = "gpt-4o-mini"
|
9
9
|
|
10
10
|
# Custom exception classes
|
11
|
-
class StepLoaderError <
|
11
|
+
class StepLoaderError < Roast::Error
|
12
12
|
attr_reader :step_name, :original_error
|
13
13
|
|
14
14
|
def initialize(message, step_name: nil, original_error: nil)
|
@@ -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
|
-
#
|
212
|
-
step.model =
|
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")
|
@@ -11,7 +11,7 @@ module Roast
|
|
11
11
|
# by introducing the StepRunner interface.
|
12
12
|
class WorkflowExecutor
|
13
13
|
# Define custom exception classes for specific error scenarios
|
14
|
-
class WorkflowExecutorError <
|
14
|
+
class WorkflowExecutorError < Roast::Error
|
15
15
|
attr_reader :step_name, :original_error
|
16
16
|
|
17
17
|
def initialize(message, step_name: nil, original_error: nil)
|
data/lib/roast.rb
CHANGED
@@ -21,6 +21,7 @@ require "yaml"
|
|
21
21
|
# Third-party gem requires
|
22
22
|
require "active_support"
|
23
23
|
require "active_support/cache"
|
24
|
+
require "active_support/core_ext/array"
|
24
25
|
require "active_support/core_ext/hash/indifferent_access"
|
25
26
|
require "active_support/core_ext/module/delegation"
|
26
27
|
require "active_support/core_ext/string"
|
@@ -279,7 +280,7 @@ module Roast
|
|
279
280
|
output_path = generator.generate(options[:output])
|
280
281
|
|
281
282
|
puts ::CLI::UI.fmt("{{success:✓}} Diagram generated: #{output_path}")
|
282
|
-
rescue
|
283
|
+
rescue Roast::Error => e
|
283
284
|
raise Thor::Error, "Error generating diagram: #{e.message}"
|
284
285
|
end
|
285
286
|
|
@@ -327,7 +328,11 @@ module Roast
|
|
327
328
|
def copy_example(example_name)
|
328
329
|
examples_dir = File.join(Roast::ROOT, "examples")
|
329
330
|
source_path = File.join(examples_dir, example_name)
|
330
|
-
|
331
|
+
|
332
|
+
# Always place new workflows in roast/ so `roast list` can find them
|
333
|
+
roast_dir = File.join(Dir.pwd, "roast")
|
334
|
+
FileUtils.mkdir_p(roast_dir)
|
335
|
+
target_path = File.join(roast_dir, example_name)
|
331
336
|
|
332
337
|
unless File.directory?(source_path)
|
333
338
|
puts "Example '#{example_name}' not found!"
|
data/sorbet/config
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
**/*.rbi linguist-vendored=true
|