roast-ai 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yaml +1 -1
- data/CHANGELOG.md +48 -0
- data/CLAUDE.md +20 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +11 -6
- data/README.md +225 -13
- data/bin/roast +27 -0
- data/docs/INSTRUMENTATION.md +42 -1
- data/docs/ITERATION_SYNTAX.md +119 -0
- data/examples/conditional/README.md +161 -0
- data/examples/conditional/check_condition/prompt.md +1 -0
- data/examples/conditional/simple_workflow.yml +15 -0
- data/examples/conditional/workflow.yml +23 -0
- data/examples/dot_notation/README.md +37 -0
- data/examples/dot_notation/workflow.yml +44 -0
- data/examples/exit_on_error/README.md +50 -0
- data/examples/exit_on_error/analyze_lint_output/prompt.md +9 -0
- data/examples/exit_on_error/apply_fixes/prompt.md +2 -0
- data/examples/exit_on_error/workflow.yml +19 -0
- data/examples/grading/workflow.yml +5 -1
- data/examples/iteration/IMPLEMENTATION.md +88 -0
- data/examples/iteration/README.md +68 -0
- data/examples/iteration/analyze_complexity/prompt.md +22 -0
- data/examples/iteration/generate_recommendations/prompt.md +21 -0
- data/examples/iteration/generate_report/prompt.md +129 -0
- data/examples/iteration/implement_fix/prompt.md +25 -0
- data/examples/iteration/prioritize_issues/prompt.md +24 -0
- data/examples/iteration/prompts/analyze_file.md +28 -0
- data/examples/iteration/prompts/generate_summary.md +24 -0
- data/examples/iteration/prompts/update_report.md +29 -0
- data/examples/iteration/prompts/write_report.md +22 -0
- data/examples/iteration/read_file/prompt.md +9 -0
- data/examples/iteration/select_next_issue/prompt.md +25 -0
- data/examples/iteration/simple_workflow.md +39 -0
- data/examples/iteration/simple_workflow.yml +58 -0
- data/examples/iteration/update_fix_count/prompt.md +26 -0
- data/examples/iteration/verify_fix/prompt.md +29 -0
- data/examples/iteration/workflow.yml +42 -0
- data/examples/openrouter_example/workflow.yml +2 -2
- data/examples/workflow_generator/README.md +27 -0
- data/examples/workflow_generator/analyze_user_request/prompt.md +34 -0
- data/examples/workflow_generator/create_workflow_files/prompt.md +32 -0
- data/examples/workflow_generator/get_user_input/prompt.md +14 -0
- data/examples/workflow_generator/info_from_roast.rb +22 -0
- data/examples/workflow_generator/workflow.yml +35 -0
- data/lib/roast/errors.rb +9 -0
- data/lib/roast/factories/api_provider_factory.rb +61 -0
- data/lib/roast/helpers/function_caching_interceptor.rb +1 -1
- data/lib/roast/helpers/minitest_coverage_runner.rb +1 -1
- data/lib/roast/helpers/prompt_loader.rb +50 -1
- data/lib/roast/resources/base_resource.rb +7 -0
- data/lib/roast/resources.rb +6 -6
- data/lib/roast/tools/ask_user.rb +40 -0
- data/lib/roast/tools/cmd.rb +1 -1
- data/lib/roast/tools/search_file.rb +1 -1
- data/lib/roast/tools/update_files.rb +413 -0
- data/lib/roast/tools.rb +12 -1
- data/lib/roast/value_objects/api_token.rb +49 -0
- data/lib/roast/value_objects/step_name.rb +39 -0
- data/lib/roast/value_objects/workflow_path.rb +77 -0
- data/lib/roast/value_objects.rb +5 -0
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/api_configuration.rb +61 -0
- data/lib/roast/workflow/base_iteration_step.rb +165 -0
- data/lib/roast/workflow/base_step.rb +4 -24
- data/lib/roast/workflow/base_workflow.rb +76 -73
- data/lib/roast/workflow/command_executor.rb +88 -0
- data/lib/roast/workflow/conditional_executor.rb +50 -0
- data/lib/roast/workflow/conditional_step.rb +96 -0
- data/lib/roast/workflow/configuration.rb +35 -158
- data/lib/roast/workflow/configuration_loader.rb +78 -0
- data/lib/roast/workflow/configuration_parser.rb +13 -248
- data/lib/roast/workflow/context_path_resolver.rb +43 -0
- data/lib/roast/workflow/dot_access_hash.rb +198 -0
- data/lib/roast/workflow/each_step.rb +86 -0
- data/lib/roast/workflow/error_handler.rb +97 -0
- data/lib/roast/workflow/expression_utils.rb +36 -0
- data/lib/roast/workflow/file_state_repository.rb +3 -2
- data/lib/roast/workflow/interpolator.rb +34 -0
- data/lib/roast/workflow/iteration_executor.rb +85 -0
- data/lib/roast/workflow/llm_boolean_coercer.rb +55 -0
- data/lib/roast/workflow/output_handler.rb +35 -0
- data/lib/roast/workflow/output_manager.rb +77 -0
- data/lib/roast/workflow/parallel_executor.rb +49 -0
- data/lib/roast/workflow/repeat_step.rb +75 -0
- data/lib/roast/workflow/replay_handler.rb +123 -0
- data/lib/roast/workflow/resource_resolver.rb +77 -0
- data/lib/roast/workflow/session_manager.rb +6 -2
- data/lib/roast/workflow/state_manager.rb +97 -0
- data/lib/roast/workflow/step_executor_coordinator.rb +205 -0
- data/lib/roast/workflow/step_executor_factory.rb +47 -0
- data/lib/roast/workflow/step_executor_registry.rb +79 -0
- data/lib/roast/workflow/step_executors/base_step_executor.rb +23 -0
- data/lib/roast/workflow/step_executors/hash_step_executor.rb +43 -0
- data/lib/roast/workflow/step_executors/parallel_step_executor.rb +54 -0
- data/lib/roast/workflow/step_executors/string_step_executor.rb +29 -0
- data/lib/roast/workflow/step_finder.rb +97 -0
- data/lib/roast/workflow/step_loader.rb +154 -0
- data/lib/roast/workflow/step_orchestrator.rb +45 -0
- data/lib/roast/workflow/step_runner.rb +23 -0
- data/lib/roast/workflow/step_type_resolver.rb +117 -0
- data/lib/roast/workflow/workflow_context.rb +60 -0
- data/lib/roast/workflow/workflow_executor.rb +90 -209
- data/lib/roast/workflow/workflow_initializer.rb +112 -0
- data/lib/roast/workflow/workflow_runner.rb +87 -0
- data/lib/roast/workflow.rb +3 -0
- data/lib/roast.rb +96 -3
- data/roast.gemspec +3 -1
- data/schema/workflow.json +85 -0
- metadata +112 -4
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class InfoFromRoast < Roast::Workflow::BaseStep
|
4
|
+
def call
|
5
|
+
examples_path = File.join(Roast::ROOT, "examples")
|
6
|
+
tools_path = File.join(Roast::ROOT, "lib", "roast", "tools")
|
7
|
+
|
8
|
+
# Get list of available tools
|
9
|
+
available_tools = Dir.entries(tools_path)
|
10
|
+
.select { |file| file.end_with?(".rb") }
|
11
|
+
.map { |file| file.gsub(".rb", "") }
|
12
|
+
.reject { |tool| tool == "." || tool == ".." }
|
13
|
+
.sort
|
14
|
+
|
15
|
+
{
|
16
|
+
examples_directory: examples_path,
|
17
|
+
tools_directory: tools_path,
|
18
|
+
available_tools: available_tools,
|
19
|
+
message: "Examples directory and available tools provided for analysis step",
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Workflow Generator
|
2
|
+
#
|
3
|
+
# This workflow generates new Roast workflows based on user descriptions.
|
4
|
+
# It gets user input, analyzes the request, generates an appropriate workflow structure,
|
5
|
+
# and creates all necessary files in a new directory.
|
6
|
+
|
7
|
+
name: Workflow Generator
|
8
|
+
model: gpt-4o-mini
|
9
|
+
|
10
|
+
tools:
|
11
|
+
- Roast::Tools::WriteFile
|
12
|
+
- Roast::Tools::ReadFile
|
13
|
+
- Roast::Tools::Cmd
|
14
|
+
- Roast::Tools::AskUser
|
15
|
+
|
16
|
+
steps:
|
17
|
+
- get_user_input
|
18
|
+
- info_from_roast
|
19
|
+
- analyze_user_request
|
20
|
+
- create_workflow_files
|
21
|
+
|
22
|
+
# Step configurations
|
23
|
+
get_user_input:
|
24
|
+
print_response: false
|
25
|
+
json: true
|
26
|
+
auto_loop: false
|
27
|
+
|
28
|
+
analyze_user_request:
|
29
|
+
print_response: true
|
30
|
+
|
31
|
+
generate_workflow_structure:
|
32
|
+
print_response: true
|
33
|
+
|
34
|
+
create_workflow_files:
|
35
|
+
print_response: false
|
data/lib/roast/errors.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Roast
|
4
|
+
# Custom error for API resource not found (404) responses
|
5
|
+
class ResourceNotFoundError < StandardError; end
|
6
|
+
|
7
|
+
# Custom error for when API authentication fails
|
8
|
+
class AuthenticationError < StandardError; end
|
9
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Roast
|
4
|
+
module Factories
|
5
|
+
# Factory for determining and creating API provider configurations
|
6
|
+
class ApiProviderFactory
|
7
|
+
SUPPORTED_PROVIDERS = {
|
8
|
+
"openai" => :openai,
|
9
|
+
"openrouter" => :openrouter,
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
DEFAULT_PROVIDER = :openai
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Determines the API provider from configuration
|
16
|
+
# @param config [Hash] The configuration hash
|
17
|
+
# @return [Symbol] The API provider symbol (:openai or :openrouter)
|
18
|
+
def from_config(config)
|
19
|
+
return DEFAULT_PROVIDER unless config["api_provider"]
|
20
|
+
|
21
|
+
provider_name = config["api_provider"].to_s.downcase
|
22
|
+
provider = SUPPORTED_PROVIDERS[provider_name]
|
23
|
+
|
24
|
+
unless provider
|
25
|
+
Roast::Helpers::Logger.warn("Unknown API provider '#{provider_name}', defaulting to #{DEFAULT_PROVIDER}")
|
26
|
+
return DEFAULT_PROVIDER
|
27
|
+
end
|
28
|
+
|
29
|
+
provider
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns true if the provider is OpenRouter
|
33
|
+
# @param provider [Symbol] The provider symbol
|
34
|
+
# @return [Boolean]
|
35
|
+
def openrouter?(provider)
|
36
|
+
provider == :openrouter
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns true if the provider is OpenAI
|
40
|
+
# @param provider [Symbol] The provider symbol
|
41
|
+
# @return [Boolean]
|
42
|
+
def openai?(provider)
|
43
|
+
provider == :openai
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the list of supported provider names
|
47
|
+
# @return [Array<String>]
|
48
|
+
def supported_provider_names
|
49
|
+
SUPPORTED_PROVIDERS.keys
|
50
|
+
end
|
51
|
+
|
52
|
+
# Validates a provider symbol
|
53
|
+
# @param provider [Symbol] The provider to validate
|
54
|
+
# @return [Boolean]
|
55
|
+
def valid_provider?(provider)
|
56
|
+
SUPPORTED_PROVIDERS.values.include?(provider)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -89,11 +89,60 @@ module Roast
|
|
89
89
|
|
90
90
|
def process_erb_if_needed(content)
|
91
91
|
if content.include?("<%")
|
92
|
-
|
92
|
+
begin
|
93
|
+
ERB.new(content, trim_mode: "-").result(context.instance_eval { binding })
|
94
|
+
rescue TypeError => e
|
95
|
+
if e.message.include?("no implicit conversion of nil into String")
|
96
|
+
# Try to find which variable is causing the issue
|
97
|
+
variable_hint = detect_nil_variable(content)
|
98
|
+
|
99
|
+
error_message = <<~ERROR
|
100
|
+
This workflow requires a file or target to be specified.
|
101
|
+
#{variable_hint}
|
102
|
+
|
103
|
+
Usage: roast execute <workflow.yml> <file_or_pattern>
|
104
|
+
|
105
|
+
Examples:
|
106
|
+
roast execute #{context.respond_to?(:configuration) && context.configuration&.workflow_path || "workflow.yml"} test/my_test.rb
|
107
|
+
roast execute #{context.respond_to?(:configuration) && context.configuration&.workflow_path || "workflow.yml"} "test/**/*_test.rb"
|
108
|
+
ERROR
|
109
|
+
raise error_message
|
110
|
+
else
|
111
|
+
raise e
|
112
|
+
end
|
113
|
+
rescue NoMethodError => e
|
114
|
+
if e.message.include?("undefined method") && e.message.include?("for nil")
|
115
|
+
variable_hint = detect_nil_variable(content)
|
116
|
+
|
117
|
+
error_message = <<~ERROR
|
118
|
+
Error processing prompt template: #{e.message}
|
119
|
+
#{variable_hint}
|
120
|
+
|
121
|
+
This may indicate that the workflow requires a file or target to be specified.
|
122
|
+
|
123
|
+
Usage: roast execute <workflow.yml> <file_or_pattern>
|
124
|
+
ERROR
|
125
|
+
raise error_message
|
126
|
+
else
|
127
|
+
raise e
|
128
|
+
end
|
129
|
+
end
|
93
130
|
else
|
94
131
|
content
|
95
132
|
end
|
96
133
|
end
|
134
|
+
|
135
|
+
def detect_nil_variable(content)
|
136
|
+
if content.include?("workflow.file")
|
137
|
+
"The prompt template references 'workflow.file' but no file was provided."
|
138
|
+
elsif content.include?("<%= file %>")
|
139
|
+
"The prompt template references 'file' but no file was provided."
|
140
|
+
elsif content.match(/<%= .*?\.(\w+) %>/)
|
141
|
+
"The prompt template is trying to access a property that doesn't exist."
|
142
|
+
else
|
143
|
+
"The prompt template contains an ERB expression that references a nil value."
|
144
|
+
end
|
145
|
+
end
|
97
146
|
end
|
98
147
|
end
|
99
148
|
end
|
@@ -19,6 +19,13 @@ module Roast
|
|
19
19
|
target
|
20
20
|
end
|
21
21
|
|
22
|
+
# Get the value of the resource (alias for target)
|
23
|
+
# Used for backward compatibility
|
24
|
+
# @return [String] The resource target value
|
25
|
+
def value
|
26
|
+
target
|
27
|
+
end
|
28
|
+
|
22
29
|
# Check if the resource exists
|
23
30
|
# @return [Boolean] true if the resource exists
|
24
31
|
def exists?
|
data/lib/roast/resources.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
require "roast/resources/base_resource"
|
4
|
+
require "roast/resources/file_resource"
|
5
|
+
require "roast/resources/directory_resource"
|
6
|
+
require "roast/resources/url_resource"
|
7
|
+
require "roast/resources/api_resource"
|
8
|
+
require "roast/resources/none_resource"
|
9
9
|
require "uri"
|
10
10
|
|
11
11
|
module Roast
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "roast/helpers/logger"
|
4
|
+
|
5
|
+
module Roast
|
6
|
+
module Tools
|
7
|
+
module AskUser
|
8
|
+
extend self
|
9
|
+
|
10
|
+
class << self
|
11
|
+
# Add this method to be included in other classes
|
12
|
+
def included(base)
|
13
|
+
base.class_eval do
|
14
|
+
function(
|
15
|
+
:ask_user,
|
16
|
+
"Ask the user for input with a specific prompt. Returns the user's response.",
|
17
|
+
prompt: { type: "string", description: "The prompt to show the user" },
|
18
|
+
) do |params|
|
19
|
+
Roast::Tools::AskUser.call(params[:prompt])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(prompt)
|
26
|
+
Roast::Helpers::Logger.info("💬 Asking user: #{prompt}\n")
|
27
|
+
|
28
|
+
response = ::CLI::UI::Prompt.ask(prompt)
|
29
|
+
|
30
|
+
Roast::Helpers::Logger.info("User responded: #{response}\n")
|
31
|
+
response
|
32
|
+
rescue StandardError => e
|
33
|
+
"Error getting user input: #{e.message}".tap do |error_message|
|
34
|
+
Roast::Helpers::Logger.error(error_message + "\n")
|
35
|
+
Roast::Helpers::Logger.debug(e.backtrace.join("\n") + "\n") if ENV["DEBUG"]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/roast/tools/cmd.rb
CHANGED
@@ -28,7 +28,7 @@ module Roast
|
|
28
28
|
Roast::Helpers::Logger.info("🔧 Running command: #{command}\n")
|
29
29
|
|
30
30
|
# Validate the command starts with one of the allowed prefixes
|
31
|
-
allowed_prefixes = ["pwd", "find", "ls", "rake", "ruby", "dev"]
|
31
|
+
allowed_prefixes = ["pwd", "find", "ls", "rake", "ruby", "dev", "mkdir"]
|
32
32
|
command_prefix = command.split(" ").first
|
33
33
|
|
34
34
|
err = "Error: Command not allowed. Only commands starting with #{allowed_prefixes.join(", ")} are permitted."
|
@@ -26,7 +26,7 @@ module Roast
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def call(glob_pattern, path = ".")
|
29
|
-
Roast::Helpers::Logger.info("🔍 Searching for: '#{glob_pattern}' in '#{path}'\n")
|
29
|
+
Roast::Helpers::Logger.info("🔍 Searching for: '#{glob_pattern}' in '#{File.expand_path(path)}'\n")
|
30
30
|
search_for(glob_pattern, path).then do |results|
|
31
31
|
return "No results found for #{glob_pattern} in #{path}" if results.empty?
|
32
32
|
return read_contents(results.first) if results.size == 1
|