roast-ai 0.1.7 → 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 +40 -1
- data/CLAUDE.md +20 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +9 -6
- data/README.md +81 -14
- data/bin/roast +27 -0
- 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.rb +11 -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 +2 -1
- data/schema/workflow.json +85 -0
- metadata +97 -4
@@ -0,0 +1,29 @@
|
|
1
|
+
# Update Method Count Report
|
2
|
+
|
3
|
+
You are a data updater responsible for adding analysis results to a report.
|
4
|
+
|
5
|
+
## Input
|
6
|
+
- File path: {{ file_path }}
|
7
|
+
- Method count: {{ method_count }}
|
8
|
+
- Current report data: {{ current_report }}
|
9
|
+
|
10
|
+
## Task
|
11
|
+
1. Parse the current report data as JSON
|
12
|
+
2. Add the new file analysis results to the report's "results" array
|
13
|
+
3. Increment the "files_analyzed" counter by 1
|
14
|
+
4. Add the method count to the "total_methods" counter
|
15
|
+
5. Return the updated JSON report
|
16
|
+
|
17
|
+
## Response Format
|
18
|
+
Return a JSON object with the updated report structure:
|
19
|
+
```json
|
20
|
+
{
|
21
|
+
"files_analyzed": 10,
|
22
|
+
"total_methods": 45,
|
23
|
+
"results": [
|
24
|
+
{"file_path": "file1.rb", "method_count": 5, "method_names": ["method1", "method2", ...]},
|
25
|
+
{"file_path": "file2.rb", "method_count": 3, "method_names": ["methodA", "methodB", ...]},
|
26
|
+
...
|
27
|
+
]
|
28
|
+
}
|
29
|
+
```
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Write Method Analysis Report
|
2
|
+
|
3
|
+
You are responsible for creating a formatted report file based on the analysis results.
|
4
|
+
|
5
|
+
## Input
|
6
|
+
- Report data: {{ report_data }}
|
7
|
+
- Summary: {{ summary }}
|
8
|
+
|
9
|
+
## Task
|
10
|
+
1. Generate a Markdown report that includes:
|
11
|
+
- The summary information
|
12
|
+
- A detailed table of all files analyzed, with their method counts and method names
|
13
|
+
2. Format the report in a clean, readable manner
|
14
|
+
|
15
|
+
## Response Format
|
16
|
+
Return a JSON object with the following structure:
|
17
|
+
```json
|
18
|
+
{
|
19
|
+
"report_content": "# Ruby Method Analysis Report\n\n{{ summary }}\n\n## Detailed Results\n\n| File | Method Count | Methods |\n|------|--------------|--------|\n| file1.rb | 5 | method1, method2, ... |\n| file2.rb | 3 | methodA, methodB, ... |\n...",
|
20
|
+
"report_file_path": "method_analysis_report.md"
|
21
|
+
}
|
22
|
+
```
|
@@ -0,0 +1,25 @@
|
|
1
|
+
I'll select the next highest priority issue to fix from our prioritized list.
|
2
|
+
|
3
|
+
Here is the current prioritized list of issues:
|
4
|
+
```json
|
5
|
+
{{output.prioritize_issues}}
|
6
|
+
```
|
7
|
+
|
8
|
+
And here is the count of fixes we've already applied:
|
9
|
+
```
|
10
|
+
{{output.update_fix_count || '0'}}
|
11
|
+
```
|
12
|
+
|
13
|
+
I'll select the highest priority issue that hasn't yet been addressed. I'll consider:
|
14
|
+
|
15
|
+
1. The priority score from our previous analysis
|
16
|
+
2. Dependencies between issues (ensuring prerequisites are addressed first)
|
17
|
+
3. Logical grouping (addressing related issues in the same file together)
|
18
|
+
|
19
|
+
If there are no issues left to fix, I'll indicate this with `{"no_issues_left": true}`.
|
20
|
+
|
21
|
+
For the selected issue, I'll return:
|
22
|
+
1. The issue details
|
23
|
+
2. The file path to modify
|
24
|
+
3. A clear description of the changes needed
|
25
|
+
4. Any context needed for implementation
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Simple Iteration Workflow Example
|
2
|
+
|
3
|
+
This example demonstrates how to use both the `each` and `repeat` iteration constructs in Roast workflows.
|
4
|
+
|
5
|
+
## Workflow Description
|
6
|
+
|
7
|
+
The workflow analyzes Ruby files in the `lib/roast/workflow` directory and counts the number of methods defined in each file. The process follows these steps:
|
8
|
+
|
9
|
+
1. Find all Ruby files in the specified directory
|
10
|
+
2. Initialize a report object to store our results
|
11
|
+
3. Process each file found:
|
12
|
+
- Read the file content
|
13
|
+
- Count the methods defined
|
14
|
+
- Update the report with the analysis result
|
15
|
+
4. Generate a summary report
|
16
|
+
5. Write the report to a file
|
17
|
+
|
18
|
+
## Key Components
|
19
|
+
|
20
|
+
- `each` construct: Processes every Ruby file found in the directory
|
21
|
+
- `repeat` construct: Used to generate the final summary (demonstrates a simple case of the repeat construct)
|
22
|
+
- Both block-level and prompt-based steps
|
23
|
+
|
24
|
+
## Running the Workflow
|
25
|
+
|
26
|
+
To run this workflow, use the following command:
|
27
|
+
|
28
|
+
```bash
|
29
|
+
shadowenv exec -- bundle exec roast examples/iteration/simple_workflow.yml
|
30
|
+
```
|
31
|
+
|
32
|
+
The final report will be saved to a markdown file as specified in the output of the `write_report` step.
|
33
|
+
|
34
|
+
## Learning Objectives
|
35
|
+
|
36
|
+
- Understand how to use the `each` construct to iterate over a collection
|
37
|
+
- Understand how to use the `repeat` construct for conditional repetition
|
38
|
+
- Learn how to build and update data structures across workflow steps
|
39
|
+
- See how to pass data between steps in an iteration workflow
|
@@ -0,0 +1,58 @@
|
|
1
|
+
name: Ruby Method Counter
|
2
|
+
tools:
|
3
|
+
- Roast::Tools::ReadFile
|
4
|
+
- Roast::Tools::Grep
|
5
|
+
- Roast::Tools::WriteFile
|
6
|
+
- Roast::Tools::CodingAgent
|
7
|
+
|
8
|
+
steps:
|
9
|
+
# Find all Ruby files in the specified directory
|
10
|
+
- find_ruby_files:
|
11
|
+
$(find lib/roast/workflow -type f -name "*.rb")
|
12
|
+
|
13
|
+
# Initialize a report object to store our results
|
14
|
+
- initialize_report:
|
15
|
+
$(echo '{"files_analyzed": 0, "total_methods": 0, "results": []}')
|
16
|
+
|
17
|
+
# Process each file found
|
18
|
+
- each: "{{output['find_ruby_files'].split('\n')}}"
|
19
|
+
as: "current_file"
|
20
|
+
steps:
|
21
|
+
# Read the file content
|
22
|
+
- read_file:
|
23
|
+
prompt: examples/iteration/prompts/analyze_file
|
24
|
+
model: claude-3-haiku-20240307
|
25
|
+
vars:
|
26
|
+
file_path: "{{ current_file }}"
|
27
|
+
|
28
|
+
# Update the report with the analysis result
|
29
|
+
- update_report:
|
30
|
+
prompt: examples/iteration/prompts/update_report
|
31
|
+
model: claude-3-haiku-20240307
|
32
|
+
vars:
|
33
|
+
file_path: "{{ current_file }}"
|
34
|
+
method_count: "{{ output['read_file']['method_count'] }}"
|
35
|
+
current_report: "{{ output['initialize_report'] }}"
|
36
|
+
|
37
|
+
# Generate the summary report after processing all files
|
38
|
+
- report_count:
|
39
|
+
$(echo "{{ output.initialize_report.files_analyzed }}")
|
40
|
+
|
41
|
+
# Repeat to generate the final summary - demonstrates the repeat construct
|
42
|
+
- repeat:
|
43
|
+
steps:
|
44
|
+
- generate_summary:
|
45
|
+
prompt: examples/iteration/prompts/generate_summary
|
46
|
+
model: claude-3-haiku-20240307
|
47
|
+
vars:
|
48
|
+
report_data: "{{ output['initialize_report'] }}"
|
49
|
+
until: "{{true}}"
|
50
|
+
max_iterations: 1
|
51
|
+
|
52
|
+
# Write the report to a file
|
53
|
+
- write_report:
|
54
|
+
prompt: examples/iteration/prompts/write_report
|
55
|
+
model: claude-3-haiku-20240307
|
56
|
+
vars:
|
57
|
+
report_data: "{{ output['initialize_report'] }}"
|
58
|
+
summary: "{{ output['generate_summary']['summary'] }}"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
I'll update the count of fixes that have been successfully applied.
|
2
|
+
|
3
|
+
Current fix count:
|
4
|
+
```
|
5
|
+
{{output.update_fix_count || 0}}
|
6
|
+
```
|
7
|
+
|
8
|
+
Verification result from the previous step:
|
9
|
+
```json
|
10
|
+
{{output.verify_fix}}
|
11
|
+
```
|
12
|
+
|
13
|
+
I'll increment the fix count if the verification was successful or partial, but not if it failed.
|
14
|
+
|
15
|
+
```javascript
|
16
|
+
let currentCount = parseInt({{output.update_fix_count || 0}});
|
17
|
+
let verificationStatus = "{{output.verify_fix.status}}";
|
18
|
+
|
19
|
+
if (verificationStatus === "success" || verificationStatus === "partial") {
|
20
|
+
currentCount += 1;
|
21
|
+
}
|
22
|
+
|
23
|
+
return { fixes_applied: currentCount };
|
24
|
+
```
|
25
|
+
|
26
|
+
This updated count will be used to determine whether we've met our target for the number of fixes to implement.
|
@@ -0,0 +1,29 @@
|
|
1
|
+
I'll verify that the fix implemented in the previous step correctly addresses the identified issue.
|
2
|
+
|
3
|
+
Here are the details of the issue that was fixed:
|
4
|
+
```json
|
5
|
+
{{output.select_next_issue}}
|
6
|
+
```
|
7
|
+
|
8
|
+
And here is the implementation of the fix:
|
9
|
+
```json
|
10
|
+
{{output.implement_fix}}
|
11
|
+
```
|
12
|
+
|
13
|
+
Now I'll read the updated file to verify the changes:
|
14
|
+
```ruby
|
15
|
+
{{read_file(output.select_next_issue.file_path)}}
|
16
|
+
```
|
17
|
+
|
18
|
+
I'll evaluate the fix based on these criteria:
|
19
|
+
1. Does it fully address the identified issue?
|
20
|
+
2. Did it introduce any new issues or regressions?
|
21
|
+
3. Does it maintain the original functionality?
|
22
|
+
4. Does it follow Ruby best practices and style conventions?
|
23
|
+
5. Is it minimal and focused (changing only what was necessary)?
|
24
|
+
|
25
|
+
Based on this evaluation, I'll provide:
|
26
|
+
1. A verification status (success, partial, failure)
|
27
|
+
2. Detailed reasoning for the status
|
28
|
+
3. Any recommendations for further improvements or adjustments
|
29
|
+
4. An overall assessment of the code quality improvement
|
@@ -0,0 +1,42 @@
|
|
1
|
+
name: Code Quality Analyzer
|
2
|
+
tools:
|
3
|
+
- Roast::Tools::ReadFile
|
4
|
+
- Roast::Tools::Grep
|
5
|
+
- Roast::Tools::WriteFile
|
6
|
+
- Roast::Tools::UpdateFiles
|
7
|
+
- Roast::Tools::CodingAgent
|
8
|
+
- Roast::Tools::Cmd
|
9
|
+
|
10
|
+
steps:
|
11
|
+
# Get all Ruby files from the target directory
|
12
|
+
- get_files_to_analyze:
|
13
|
+
$(find {{resource.target}} -name "*.rb" -not -path "*/vendor/*" | grep -v "test")
|
14
|
+
|
15
|
+
# Analyze each file and generate improvement recommendations
|
16
|
+
- each: "{{output['get_files_to_analyze'].split('\n')}}"
|
17
|
+
as: "current_file"
|
18
|
+
steps:
|
19
|
+
- read_file:
|
20
|
+
$(cat {{current_file}})
|
21
|
+
- analyze_complexity
|
22
|
+
- generate_recommendations
|
23
|
+
|
24
|
+
# After analyzing all files, sort issues by priority
|
25
|
+
- prioritize_issues
|
26
|
+
|
27
|
+
# Process the highest priority issues first, until we've addressed a sufficient number
|
28
|
+
# or reached our iteration limit
|
29
|
+
- initialize_fixes:
|
30
|
+
$(echo "0")
|
31
|
+
|
32
|
+
- repeat:
|
33
|
+
steps:
|
34
|
+
- select_next_issue
|
35
|
+
- implement_fix
|
36
|
+
- verify_fix
|
37
|
+
- update_fix_count
|
38
|
+
until: "{{output['update_fix_count']['fixes_applied'] >= 5 || output['select_next_issue']['no_issues_left'] == true}}"
|
39
|
+
max_iterations: 10
|
40
|
+
|
41
|
+
# Generate a summary report of all changes made
|
42
|
+
- generate_report
|
@@ -1,7 +1,7 @@
|
|
1
1
|
name: OpenRouter Example
|
2
2
|
api_provider: openrouter
|
3
3
|
api_token: $(echo $OPENROUTER_API_KEY)
|
4
|
-
model:
|
4
|
+
model: google/gemini-2.0-flash-001
|
5
5
|
|
6
6
|
tools:
|
7
7
|
- Roast::Tools::ReadFile
|
@@ -9,4 +9,4 @@ tools:
|
|
9
9
|
|
10
10
|
steps:
|
11
11
|
- analyze_input
|
12
|
-
- generate_response
|
12
|
+
- generate_response
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Workflow Generator
|
2
|
+
|
3
|
+
This workflow generates new Roast workflows based on user descriptions. It's the engine behind the "New from prompt" option in `roast init`.
|
4
|
+
|
5
|
+
## How It Works
|
6
|
+
|
7
|
+
The workflow generator takes a user description and workflow name, then:
|
8
|
+
|
9
|
+
1. **Analyzes the request** - Understands what type of workflow is needed, what steps are required, and what tools should be used
|
10
|
+
2. **Generates structure** - Creates a complete workflow configuration including YAML and step prompts
|
11
|
+
3. **Creates files** - Writes all the necessary files and directories to disk
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
This workflow is typically invoked automatically by the `roast init` command, but can also be run directly:
|
16
|
+
|
17
|
+
```bash
|
18
|
+
# Run the generator workflow
|
19
|
+
roast execute examples/workflow_generator/workflow.yml
|
20
|
+
```
|
21
|
+
|
22
|
+
## Generated Output
|
23
|
+
|
24
|
+
The workflow creates a new directory with:
|
25
|
+
- `workflow.yml` - Main workflow configuration
|
26
|
+
- `step_name/prompt.md` - Individual step prompts
|
27
|
+
- `README.md` - Documentation for the generated workflow
|
@@ -0,0 +1,34 @@
|
|
1
|
+
You are an assistant that analyzes user requests to understand what kind of workflow they want to create.
|
2
|
+
|
3
|
+
Based on the user input from the previous step:
|
4
|
+
<%= workflow.output["get_user_input"] %>
|
5
|
+
|
6
|
+
Roast information from previous step:
|
7
|
+
<%= workflow.output["info_from_roast"] %>
|
8
|
+
|
9
|
+
First, explore existing workflow examples in the examples directory to understand common patterns and structures. Look for ones that may be related to the user's intention.
|
10
|
+
|
11
|
+
Then analyze the user's request and determine:
|
12
|
+
|
13
|
+
1. **Required Steps**: Break down the workflow into logical steps. Each step should be a discrete task that can be accomplished with an AI prompt.
|
14
|
+
|
15
|
+
2. **Tools Needed**: What Roast tools will be needed? Base this on the actual tools you read from info provided above.
|
16
|
+
|
17
|
+
3. **Target Strategy**: Will this workflow:
|
18
|
+
- Process specific files (needs target configuration)
|
19
|
+
- Be targetless (works without specific input files)
|
20
|
+
- Use shell commands to find targets dynamically
|
21
|
+
|
22
|
+
4. **Model Requirements**: Should this use a specific model, or is the default (gpt-4o-mini) sufficient?
|
23
|
+
|
24
|
+
Respond with a structured analysis in this format:
|
25
|
+
|
26
|
+
```
|
27
|
+
STEPS: [list of 3-5 logical steps]
|
28
|
+
TOOLS: [list of required tools]
|
29
|
+
TARGET_STRATEGY: [targetless/files/dynamic]
|
30
|
+
MODEL: [model recommendation]
|
31
|
+
COMPLEXITY: [simple/moderate/complex]
|
32
|
+
```
|
33
|
+
|
34
|
+
Be specific and actionable in your analysis.
|
@@ -0,0 +1,32 @@
|
|
1
|
+
You are an assistant that creates the actual workflow files in the filesystem.
|
2
|
+
|
3
|
+
Based on the user input:
|
4
|
+
<%= workflow.output["get_user_input"] %>
|
5
|
+
|
6
|
+
And the generated workflow structure from the previous step:
|
7
|
+
<%= workflow.output["analyze_user_request"] %>
|
8
|
+
|
9
|
+
And info from roast:
|
10
|
+
<%= workflow.output["info_from_roast"] %>
|
11
|
+
|
12
|
+
Your task is to create all the necessary files and directories for the workflow.
|
13
|
+
|
14
|
+
Extract the workflow name from the user input JSON and create the workflow in the current directory under that folder name.
|
15
|
+
|
16
|
+
Steps to complete:
|
17
|
+
|
18
|
+
1. **Create the main directory**: Use Cmd to create the "{{ workflow_name }}" directory
|
19
|
+
2. **Create step directories**: Create subdirectories for each workflow step
|
20
|
+
3. **Create workflow.yml**: Write the main workflow configuration file
|
21
|
+
4. **Create step prompt files**: Write each step's prompt.md file
|
22
|
+
5. **Create README.md**: Generate a helpful README explaining the workflow
|
23
|
+
|
24
|
+
When writing files, extract the content from the structured response and write each file separately.
|
25
|
+
|
26
|
+
Important notes:
|
27
|
+
- Make sure all directories exist before writing files to them
|
28
|
+
- Follow the exact structure specified in the previous step
|
29
|
+
- Include helpful comments in the workflow.yml file
|
30
|
+
- Make the README.md informative and include usage instructions
|
31
|
+
|
32
|
+
At the end, confirm that all files have been created by listing the directory structure.
|
@@ -0,0 +1,14 @@
|
|
1
|
+
You need to collect user input for generating a new workflow.
|
2
|
+
|
3
|
+
Step 1: Ask for the workflow description using ask_user tool with prompt: "What should your workflow do?"
|
4
|
+
|
5
|
+
Step 2: Ask for the workflow name using ask_user tool with prompt: "Enter workflow directory name:"
|
6
|
+
|
7
|
+
Step 3: Return the result in JSON format:
|
8
|
+
|
9
|
+
<json>
|
10
|
+
{
|
11
|
+
"user_description": "what the user wants the workflow to do",
|
12
|
+
"workflow_name": "directory_name_provided_by_user"
|
13
|
+
}
|
14
|
+
</json>
|
@@ -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
|