roast-ai 0.1.0 → 0.1.2
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/cla.yml +1 -1
- data/.gitignore +1 -0
- data/CHANGELOG.md +28 -0
- data/CLAUDE.md +3 -1
- data/Gemfile +0 -1
- data/Gemfile.lock +3 -4
- data/README.md +419 -5
- data/Rakefile +1 -6
- data/docs/INSTRUMENTATION.md +202 -0
- data/examples/api_workflow/README.md +85 -0
- data/examples/api_workflow/fetch_api_data/prompt.md +10 -0
- data/examples/api_workflow/generate_report/prompt.md +10 -0
- data/examples/api_workflow/prompt.md +10 -0
- data/examples/api_workflow/transform_data/prompt.md +10 -0
- data/examples/api_workflow/workflow.yml +30 -0
- data/examples/grading/format_result.rb +25 -9
- data/examples/grading/js_test_runner +31 -0
- data/examples/grading/rb_test_runner +19 -0
- data/examples/grading/read_dependencies/prompt.md +14 -0
- data/examples/grading/run_coverage.rb +2 -2
- data/examples/grading/workflow.yml +3 -12
- data/examples/instrumentation.rb +76 -0
- data/examples/rspec_to_minitest/README.md +68 -0
- data/examples/rspec_to_minitest/analyze_spec/prompt.md +30 -0
- data/examples/rspec_to_minitest/create_minitest/prompt.md +33 -0
- data/examples/rspec_to_minitest/run_and_improve/prompt.md +35 -0
- data/examples/rspec_to_minitest/workflow.md +10 -0
- data/examples/rspec_to_minitest/workflow.yml +40 -0
- data/lib/roast/helpers/function_caching_interceptor.rb +72 -8
- data/lib/roast/helpers/prompt_loader.rb +2 -0
- data/lib/roast/resources/api_resource.rb +137 -0
- data/lib/roast/resources/base_resource.rb +47 -0
- data/lib/roast/resources/directory_resource.rb +40 -0
- data/lib/roast/resources/file_resource.rb +33 -0
- data/lib/roast/resources/none_resource.rb +29 -0
- data/lib/roast/resources/url_resource.rb +63 -0
- data/lib/roast/resources.rb +100 -0
- data/lib/roast/tools/coding_agent.rb +69 -0
- data/lib/roast/tools.rb +1 -0
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/base_step.rb +21 -17
- data/lib/roast/workflow/base_workflow.rb +69 -17
- data/lib/roast/workflow/configuration.rb +83 -8
- data/lib/roast/workflow/configuration_parser.rb +218 -3
- data/lib/roast/workflow/file_state_repository.rb +156 -0
- data/lib/roast/workflow/prompt_step.rb +16 -0
- data/lib/roast/workflow/session_manager.rb +82 -0
- data/lib/roast/workflow/state_repository.rb +21 -0
- data/lib/roast/workflow/workflow_executor.rb +99 -9
- data/lib/roast/workflow.rb +4 -0
- data/lib/roast.rb +2 -5
- data/roast.gemspec +1 -1
- data/schema/workflow.json +12 -0
- metadata +34 -6
- data/.rspec +0 -1
@@ -0,0 +1,202 @@
|
|
1
|
+
# Instrumentation Hooks in Roast
|
2
|
+
|
3
|
+
Roast provides built-in instrumentation hooks using ActiveSupport::Notifications, allowing you to track workflow execution, monitor performance, and integrate with your own monitoring systems.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The instrumentation system emits events at key points during workflow execution:
|
8
|
+
|
9
|
+
- Workflow lifecycle (start, complete)
|
10
|
+
- Step execution (start, complete, error)
|
11
|
+
- Chat completion/AI calls (start, complete, error)
|
12
|
+
- Tool function execution
|
13
|
+
|
14
|
+
## Configuration
|
15
|
+
|
16
|
+
To add custom instrumentation, create Ruby files in your project's `.roast/initializers/` directory. These files will be automatically loaded during workflow startup.
|
17
|
+
|
18
|
+
Example structure:
|
19
|
+
```
|
20
|
+
your-project/
|
21
|
+
├── .roast/
|
22
|
+
│ └── initializers/
|
23
|
+
│ ├── logging.rb
|
24
|
+
│ ├── metrics.rb
|
25
|
+
│ └── monitoring.rb
|
26
|
+
└── ...
|
27
|
+
```
|
28
|
+
|
29
|
+
## Available Events
|
30
|
+
|
31
|
+
### Workflow Events
|
32
|
+
|
33
|
+
- `roast.workflow.start` - Emitted when a workflow begins
|
34
|
+
- Payload: `{ workflow_path:, options:, name: }`
|
35
|
+
|
36
|
+
- `roast.workflow.complete` - Emitted when a workflow completes
|
37
|
+
- Payload: `{ workflow_path:, success:, execution_time: }`
|
38
|
+
|
39
|
+
### Step Events
|
40
|
+
|
41
|
+
- `roast.step.start` - Emitted when a step begins execution
|
42
|
+
- Payload: `{ step_name: }`
|
43
|
+
|
44
|
+
- `roast.step.complete` - Emitted when a step completes successfully
|
45
|
+
- Payload: `{ step_name:, success: true, execution_time:, result_size: }`
|
46
|
+
|
47
|
+
- `roast.step.error` - Emitted when a step encounters an error
|
48
|
+
- Payload: `{ step_name:, error:, message:, execution_time: }`
|
49
|
+
|
50
|
+
### AI/Chat Completion Events
|
51
|
+
|
52
|
+
- `roast.chat_completion.start` - Emitted before an AI API call
|
53
|
+
- Payload: `{ model:, parameters: }`
|
54
|
+
|
55
|
+
- `roast.chat_completion.complete` - Emitted after successful AI API call
|
56
|
+
- Payload: `{ success: true, model:, parameters:, execution_time:, response_size: }`
|
57
|
+
|
58
|
+
- `roast.chat_completion.error` - Emitted when AI API call fails
|
59
|
+
- Payload: `{ error:, message:, model:, parameters:, execution_time: }`
|
60
|
+
|
61
|
+
### Tool Execution Events
|
62
|
+
|
63
|
+
- `roast.tool.execute` - Emitted when a tool function is called
|
64
|
+
- Payload: `{ function_name:, params: }`
|
65
|
+
|
66
|
+
- `roast.tool.complete` - Emitted when a tool function completes
|
67
|
+
- Payload: `{ function_name:, execution_time:, cache_enabled: }`
|
68
|
+
|
69
|
+
- `roast.tool.error` - Emitted when a tool execution fails
|
70
|
+
- Payload: `{ function_name:, error:, message:, execution_time: }`
|
71
|
+
|
72
|
+
## Example Usage
|
73
|
+
|
74
|
+
### Basic Logging
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
# .roast/initializers/logging.rb
|
78
|
+
ActiveSupport::Notifications.subscribe(/roast\./) do |name, start, finish, id, payload|
|
79
|
+
duration = finish - start
|
80
|
+
puts "[#{name}] completed in #{duration.round(3)}s"
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
### Performance Monitoring
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
# .roast/initializers/performance.rb
|
88
|
+
ActiveSupport::Notifications.subscribe("roast.step.complete") do |name, start, finish, id, payload|
|
89
|
+
duration = finish - start
|
90
|
+
if duration > 10.0
|
91
|
+
puts "WARNING: Step '#{payload[:step_name]}' took #{duration.round(1)}s"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
### Integration with External Services
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
# .roast/initializers/metrics.rb
|
100
|
+
ActiveSupport::Notifications.subscribe("roast.workflow.complete") do |name, start, finish, id, payload|
|
101
|
+
duration = finish - start
|
102
|
+
|
103
|
+
# Send to your metrics service
|
104
|
+
MyMetricsService.track_event("workflow_execution", {
|
105
|
+
workflow_path: payload[:workflow_path],
|
106
|
+
duration: duration,
|
107
|
+
success: payload[:success]
|
108
|
+
})
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
### Internal Shopify Example
|
113
|
+
|
114
|
+
For the internal Shopify version, you can use these instrumentation hooks to track metrics with Monorail:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
# .roast/initializers/monorail.rb
|
118
|
+
|
119
|
+
# Track workflow execution
|
120
|
+
ActiveSupport::Notifications.subscribe("roast.workflow.start") do |name, start, finish, id, payload|
|
121
|
+
Roast::Support::Monorail.track_command("run", {
|
122
|
+
"workflow_path" => payload[:workflow_path],
|
123
|
+
"options" => payload[:options],
|
124
|
+
"name" => payload[:name]
|
125
|
+
})
|
126
|
+
end
|
127
|
+
|
128
|
+
ActiveSupport::Notifications.subscribe("roast.workflow.complete") do |name, start, finish, id, payload|
|
129
|
+
Roast::Support::Monorail.track_command("run_complete", {
|
130
|
+
"workflow_path" => payload[:workflow_path],
|
131
|
+
"success" => payload[:success],
|
132
|
+
"execution_time" => payload[:execution_time]
|
133
|
+
})
|
134
|
+
end
|
135
|
+
|
136
|
+
# Track AI model usage and performance
|
137
|
+
ActiveSupport::Notifications.subscribe("roast.chat_completion.complete") do |name, start, finish, id, payload|
|
138
|
+
Roast::Support::Monorail.track_command("ai_usage", {
|
139
|
+
"model" => payload[:model],
|
140
|
+
"execution_time" => payload[:execution_time],
|
141
|
+
"response_size" => payload[:response_size],
|
142
|
+
"has_json" => payload[:parameters][:json] || false,
|
143
|
+
"has_loop" => payload[:parameters][:loop] || false
|
144
|
+
})
|
145
|
+
end
|
146
|
+
|
147
|
+
# Track tool execution and caching
|
148
|
+
ActiveSupport::Notifications.subscribe("roast.tool.complete") do |name, start, finish, id, payload|
|
149
|
+
Roast::Support::Monorail.track_command("tool_usage", {
|
150
|
+
"function_name" => payload[:function_name],
|
151
|
+
"execution_time" => payload[:execution_time],
|
152
|
+
"cache_enabled" => payload[:cache_enabled]
|
153
|
+
})
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
See `examples/monorail_initializer.rb` for a complete example of Monorail integration.
|
158
|
+
|
159
|
+
## Best Practices
|
160
|
+
|
161
|
+
1. **Keep initializers focused**: Each initializer should handle a specific concern (logging, metrics, error reporting, etc.)
|
162
|
+
|
163
|
+
2. **Handle errors gracefully**: Wrap your subscriber code in error handling to prevent crashes:
|
164
|
+
```ruby
|
165
|
+
ActiveSupport::Notifications.subscribe("roast.workflow.start") do |name, start, finish, id, payload|
|
166
|
+
begin
|
167
|
+
# Your instrumentation code here
|
168
|
+
rescue => e
|
169
|
+
$stderr.puts "Instrumentation error: #{e.message}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
174
|
+
3. **Avoid blocking operations**: Instrumentation should be fast and non-blocking. For heavy operations, consider using async processing.
|
175
|
+
|
176
|
+
4. **Use pattern matching**: Subscribe to specific event patterns to reduce overhead:
|
177
|
+
```ruby
|
178
|
+
# Subscribe only to workflow events
|
179
|
+
ActiveSupport::Notifications.subscribe(/roast\.workflow\./) do |name, start, finish, id, payload|
|
180
|
+
# Handle only workflow events
|
181
|
+
end
|
182
|
+
```
|
183
|
+
|
184
|
+
5. **Consider performance impact**: While instrumentation is valuable, too many subscribers can impact performance. Be selective about what you instrument.
|
185
|
+
|
186
|
+
## Testing Your Instrumentation
|
187
|
+
|
188
|
+
You can test your instrumentation by creating a simple workflow and observing the events:
|
189
|
+
|
190
|
+
```yaml
|
191
|
+
# test_instrumentation.yml
|
192
|
+
name: instrumentation_test
|
193
|
+
steps:
|
194
|
+
- test_step
|
195
|
+
```
|
196
|
+
|
197
|
+
Then run:
|
198
|
+
```bash
|
199
|
+
roast execute test_instrumentation.yml some_file.rb
|
200
|
+
```
|
201
|
+
|
202
|
+
Your instrumentation should capture the workflow start, step execution, and workflow completion events.
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# API Workflow Example
|
2
|
+
|
3
|
+
This example demonstrates a targetless workflow that interacts with APIs rather than operating on specific files.
|
4
|
+
|
5
|
+
## Structure
|
6
|
+
|
7
|
+
The workflow consists of three steps that work together to create a complete API integration process:
|
8
|
+
|
9
|
+
1. `fetch_api_data` - Simulates fetching data from a weather API and returns a structured JSON response
|
10
|
+
2. `transform_data` - Processes the JSON data into a human-readable markdown format
|
11
|
+
3. `generate_report` - Creates a polished report with recommendations based on the weather data
|
12
|
+
|
13
|
+
## Running the Example
|
14
|
+
|
15
|
+
To run this example, you need to have a valid API token. The example is configured to fetch a token using a shell command:
|
16
|
+
|
17
|
+
```yaml
|
18
|
+
# Dynamic API token using shell command
|
19
|
+
api_token: $(print-token --key)
|
20
|
+
```
|
21
|
+
|
22
|
+
You can modify this to use your own token source, such as:
|
23
|
+
|
24
|
+
```yaml
|
25
|
+
# Using an environment variable
|
26
|
+
api_token: $(echo $OPENAI_API_KEY)
|
27
|
+
|
28
|
+
# Or a direct value (not recommended for production)
|
29
|
+
api_token: $(echo "sk-your-actual-token")
|
30
|
+
```
|
31
|
+
|
32
|
+
Then run the workflow:
|
33
|
+
|
34
|
+
```bash
|
35
|
+
# Run the targetless workflow
|
36
|
+
roast execute examples/api_workflow/workflow.yml
|
37
|
+
|
38
|
+
# Save the output to a file
|
39
|
+
roast execute examples/api_workflow/workflow.yml -o weather_report.md
|
40
|
+
```
|
41
|
+
|
42
|
+
## How Targetless Workflows Work
|
43
|
+
|
44
|
+
Targetless workflows operate without a specific target file. This is useful for:
|
45
|
+
|
46
|
+
- API integrations
|
47
|
+
- Content generation
|
48
|
+
- Data analysis
|
49
|
+
- Report creation
|
50
|
+
- Interactive tools
|
51
|
+
|
52
|
+
Unlike file-based workflows that process each target separately, targetless workflows run once and can retrieve their own data sources (like API calls) or generate content from scratch.
|
53
|
+
|
54
|
+
## Workflow Definition
|
55
|
+
|
56
|
+
```yaml
|
57
|
+
name: API Integration Workflow
|
58
|
+
# Default model for all steps
|
59
|
+
model: gpt-4o-mini
|
60
|
+
|
61
|
+
tools:
|
62
|
+
- Roast::Tools::ReadFile
|
63
|
+
- Roast::Tools::Grep
|
64
|
+
- Roast::Tools::WriteFile
|
65
|
+
|
66
|
+
steps:
|
67
|
+
- fetch_api_data
|
68
|
+
- transform_data
|
69
|
+
- generate_report
|
70
|
+
|
71
|
+
# Tool configurations for API calls (no need to specify model here since it uses global model)
|
72
|
+
fetch_api_data:
|
73
|
+
print_response: true
|
74
|
+
```
|
75
|
+
|
76
|
+
## Creating Your Own Targetless Workflows
|
77
|
+
|
78
|
+
To create your own targetless workflow:
|
79
|
+
|
80
|
+
1. Create a workflow YAML file without a `target` parameter
|
81
|
+
2. Define the steps your workflow will execute
|
82
|
+
3. Create prompt files for each step
|
83
|
+
4. Run the workflow with `roast execute your_workflow.yml`
|
84
|
+
|
85
|
+
Your steps can use the workflow's `output` hash to pass data between them, just like in file-based workflows.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
You are an assistant helping with retrieving data from an API source.
|
2
|
+
|
3
|
+
Your task is to simulate fetching data from a weather API using mock data.
|
4
|
+
|
5
|
+
1. Imagine you are retrieving current weather conditions for various cities
|
6
|
+
2. Create a structured JSON response that represents typical weather API data
|
7
|
+
3. The response should include temperature, conditions, wind, and other relevant weather metrics
|
8
|
+
4. Format the response as valid JSON that could be parsed programmatically
|
9
|
+
|
10
|
+
Return only the JSON data without any additional explanation.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
You are an assistant helping to generate a final weather report.
|
2
|
+
|
3
|
+
Based on the transformed data from the previous step, create a polished report that could be sent to stakeholders.
|
4
|
+
|
5
|
+
1. Take the transformed weather data and enhance it with recommendations and insights
|
6
|
+
2. Add relevant suggestions based on the weather conditions (like what to wear, activities that would be appropriate, etc.)
|
7
|
+
3. Include a "forecast overview" section that summarizes the key points
|
8
|
+
4. Format the output as a professional-looking report with proper headings and structure
|
9
|
+
|
10
|
+
The report should be comprehensive yet concise, easy to scan, and provide actionable insights based on the weather data.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# API Integration Workflow
|
2
|
+
|
3
|
+
This workflow demonstrates how to create an API integration that:
|
4
|
+
1. Fetches data from an external source
|
5
|
+
2. Transforms the data into a usable format
|
6
|
+
3. Generates a report based on the processed data
|
7
|
+
|
8
|
+
The workflow doesn't require a target file because it's designed to work with external APIs and data sources rather than processing specific files.
|
9
|
+
|
10
|
+
You'll be working through a weather data processing example. This is a simulation to demonstrate the workflow pattern - no actual API calls are made.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
You are an assistant helping to transform API data.
|
2
|
+
|
3
|
+
Your task is to process weather data from a JSON format into a more readable summary.
|
4
|
+
|
5
|
+
1. Review the weather data provided in the previous step
|
6
|
+
2. Transform the technical JSON data into a human-readable summary
|
7
|
+
3. Format the output as markdown with appropriate sections and highlights
|
8
|
+
4. Focus on the most relevant information that would be useful to a typical user
|
9
|
+
|
10
|
+
Return a well-formatted markdown summary of the weather data.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# API Integration Workflow
|
2
|
+
|
3
|
+
# This workflow demonstrates how to create an API integration that:
|
4
|
+
# 1. Fetches data from an external source
|
5
|
+
# 2. Transforms the data into a usable format
|
6
|
+
# 3. Generates a report based on the processed data
|
7
|
+
|
8
|
+
# The workflow doesn't require a target file because it's designed to work with external APIs and data sources rather than processing specific files.
|
9
|
+
|
10
|
+
# You'll be working through a weather data processing example. This is a simulation to demonstrate the workflow pattern - no actual API calls are made.
|
11
|
+
|
12
|
+
name: API Integration Workflow
|
13
|
+
model: gpt-4o-mini
|
14
|
+
|
15
|
+
tools:
|
16
|
+
- Roast::Tools::ReadFile
|
17
|
+
- Roast::Tools::Grep
|
18
|
+
- Roast::Tools::WriteFile
|
19
|
+
|
20
|
+
# For demonstration purposes only - in production you would use a real token command
|
21
|
+
# api_token: $(echo "demo_token_123456")
|
22
|
+
|
23
|
+
steps:
|
24
|
+
- fetch_api_data
|
25
|
+
- transform_data
|
26
|
+
- generate_report
|
27
|
+
|
28
|
+
# Tool configurations for API calls
|
29
|
+
fetch_api_data:
|
30
|
+
print_response: true
|
@@ -16,7 +16,6 @@ class FormatResult < Roast::Workflow::BaseStep
|
|
16
16
|
append_to_final_output(<<~OUTPUT)
|
17
17
|
========== TEST GRADE REPORT ==========
|
18
18
|
Test file: #{workflow.file}
|
19
|
-
Source file: #{workflow.subject_file}
|
20
19
|
OUTPUT
|
21
20
|
|
22
21
|
format_results
|
@@ -26,22 +25,39 @@ class FormatResult < Roast::Workflow::BaseStep
|
|
26
25
|
private
|
27
26
|
|
28
27
|
def format_results
|
29
|
-
|
28
|
+
# With HashWithIndifferentAccess, we can simply access with either syntax
|
29
|
+
grade_data = workflow.output["calculate_final_grade"]
|
30
|
+
|
31
|
+
unless grade_data
|
32
|
+
return append_to_final_output("Error: Grading data not available. This may be because you're replaying the workflow from this step, but the previous step data is missing or not found in the selected session.")
|
33
|
+
end
|
34
|
+
|
35
|
+
format_grade(grade_data)
|
36
|
+
|
37
|
+
# Make sure rubric_scores exists before trying to iterate over it
|
38
|
+
unless grade_data[:rubric_scores]
|
39
|
+
return append_to_final_output("Error: Rubric scores data not available in the workflow output.")
|
40
|
+
end
|
30
41
|
|
31
42
|
append_to_final_output("RUBRIC SCORES:")
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
43
|
+
grade_data[:rubric_scores].each do |category, data|
|
44
|
+
# Safely access RUBRIC with a fallback for potentially missing categories
|
45
|
+
rubric_item = RUBRIC[category.to_sym] || { description: "Unknown Category", weight: 0 }
|
46
|
+
|
47
|
+
append_to_final_output(" #{rubric_item[:description]} (#{(rubric_item[:weight] * 100).round}% of grade):")
|
48
|
+
append_to_final_output(" Value: #{data[:raw_value] || "N/A"}")
|
49
|
+
append_to_final_output(" Score: #{data[:score] ? (data[:score] * 10).round : "N/A"}/10 - \"#{data[:description] || "No description available"}\"")
|
36
50
|
end
|
37
51
|
end
|
38
52
|
|
39
|
-
def format_grade
|
40
|
-
|
53
|
+
def format_grade(grade_data)
|
54
|
+
return append_to_final_output("\nError: Final grade data not available.") unless grade_data && grade_data[:final_score]
|
55
|
+
|
56
|
+
letter_grade = grade_data[:final_score][:letter_grade]
|
41
57
|
celebration_emoji = letter_grade == "A" ? "🎉" : ""
|
42
58
|
append_to_final_output(<<~OUTPUT)
|
43
59
|
\nFINAL GRADE:
|
44
|
-
Score: #{(
|
60
|
+
Score: #{(grade_data[:final_score][:weighted_score] * 100).round}/100
|
45
61
|
Letter Grade: #{letter_grade} #{celebration_emoji}
|
46
62
|
OUTPUT
|
47
63
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
if ARGV.length != 2
|
5
|
+
puts "Usage: #{File.basename($PROGRAM_NAME)} SUBJECT_FILE TEST_FILE"
|
6
|
+
exit 1
|
7
|
+
end
|
8
|
+
|
9
|
+
subject_file, test_file = ARGV
|
10
|
+
|
11
|
+
def detect_package_manager
|
12
|
+
return "pnpm" if File.exist?(File.join(Dir.pwd, "pnpm-lock.yaml"))
|
13
|
+
return "yarn" if File.exist?(File.join(Dir.pwd, "yarn.lock"))
|
14
|
+
|
15
|
+
"npm"
|
16
|
+
end
|
17
|
+
|
18
|
+
jest_options = [
|
19
|
+
"--verbose",
|
20
|
+
"--no-colors",
|
21
|
+
"--ci",
|
22
|
+
"--coverageReporters=text-summary",
|
23
|
+
"--collectCoverageFrom=#{subject_file}",
|
24
|
+
]
|
25
|
+
|
26
|
+
# Assumes the test command is `test:coverage`
|
27
|
+
# Both admin-web and checkout-web use this command
|
28
|
+
command = "#{detect_package_manager} run test:coverage -- #{test_file} #{jest_options.join(" ")}"
|
29
|
+
|
30
|
+
$stderr.puts "Running: #{command}"
|
31
|
+
puts system(command)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "rubygems"
|
5
|
+
require "bundler/setup"
|
6
|
+
|
7
|
+
require_relative "../../lib/roast/helpers/minitest_coverage_runner"
|
8
|
+
|
9
|
+
# Suppress fancy minitest reporting
|
10
|
+
ENV["RM_INFO"] = "true"
|
11
|
+
|
12
|
+
if ARGV.length != 2
|
13
|
+
puts "Usage: #{File.basename($PROGRAM_NAME)} SUBJECT_FILE TEST_FILE"
|
14
|
+
exit 1
|
15
|
+
end
|
16
|
+
|
17
|
+
test_file, subject_file = ARGV
|
18
|
+
|
19
|
+
Roast::Helpers::MinitestCoverageRunner.new(test_file, subject_file).run
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Use the provided functions to find and read important dependencies of the provided test file named <%= workflow.file %>.
|
2
|
+
|
3
|
+
The first dependency you should always look for is the source file for the prime subject of the test (whatever class this test file is claiming to test). Use `read_file` to read the subject's source code into your conversation transcript, but only if it's not already there from a previous chat.
|
4
|
+
|
5
|
+
If you can identify other important application-level dependencies then read them too.
|
6
|
+
How many extra dependencies to research is left to your discretion, but ALWAYS make sure you have the subject under test (SUT) in your context before responding.
|
7
|
+
|
8
|
+
Once you are finished using tool functions, respond with the relative path to the source file of the SUT inside <sut> tags.
|
9
|
+
|
10
|
+
Example:
|
11
|
+
|
12
|
+
If you are told to find the dependencies of `test/services/country_db_interface_test.rb`,
|
13
|
+
then you would use the functions as explained above and ultimately respond with `<sut>./app/services/country_db_interface.rb</sut>`
|
14
|
+
|
@@ -20,7 +20,7 @@ class RunCoverage < Roast::Workflow::BaseStep
|
|
20
20
|
extension = "js" if ["js", "jsx", "ts", "tsx"].include?(extension)
|
21
21
|
|
22
22
|
# Get the absolute path to the test_runner executable
|
23
|
-
test_runner_path = File.expand_path("
|
23
|
+
test_runner_path = File.expand_path("#{extension}_test_runner", __dir__)
|
24
24
|
|
25
25
|
# Make sure the test_runner executable exists
|
26
26
|
unless File.exist?(test_runner_path)
|
@@ -33,7 +33,7 @@ class RunCoverage < Roast::Workflow::BaseStep
|
|
33
33
|
resolved_test_file = Roast::Helpers::PathResolver.resolve(test_file)
|
34
34
|
|
35
35
|
# Run the test_runner using shadowenv for environment consistency
|
36
|
-
command = "shadowenv exec --dir . -- #{test_runner_path} #{
|
36
|
+
command = "shadowenv exec --dir . -- #{test_runner_path} #{resolved_test_file} #{resolved_subject_file}"
|
37
37
|
output, status = Open3.capture2(command)
|
38
38
|
|
39
39
|
unless status.success?
|
@@ -5,7 +5,7 @@ tools:
|
|
5
5
|
- Roast::Tools::ReadFile
|
6
6
|
- Roast::Tools::SearchFile
|
7
7
|
|
8
|
-
each: '% cd $(git rev-parse --show-toplevel) && git status --porcelain | grep "_test\.rb" | cut -c4- | xargs realpath'
|
8
|
+
# each: '% cd $(git rev-parse --show-toplevel) && git status --porcelain | grep "_test\.rb" | cut -c4- | xargs realpath'
|
9
9
|
|
10
10
|
steps:
|
11
11
|
- read_dependencies
|
@@ -18,12 +18,11 @@ steps:
|
|
18
18
|
- calculate_final_grade
|
19
19
|
- format_result
|
20
20
|
- generate_recommendations
|
21
|
-
- annotate_pr_with_comments
|
22
21
|
|
23
22
|
# set non-default attributes for steps below
|
24
23
|
analyze_coverage:
|
25
24
|
model: gpt-4.1-mini
|
26
|
-
|
25
|
+
auto_loop: false
|
27
26
|
json: true
|
28
27
|
|
29
28
|
generate_grades:
|
@@ -32,15 +31,7 @@ generate_grades:
|
|
32
31
|
|
33
32
|
generate_recommendations:
|
34
33
|
model: o3
|
35
|
-
|
34
|
+
auto_loop: false
|
36
35
|
json: true
|
37
36
|
params:
|
38
37
|
max_completion_tokens: 5_000
|
39
|
-
|
40
|
-
annotate_pr_with_comments:
|
41
|
-
tools:
|
42
|
-
- Roast::Tools::Github::Annotator
|
43
|
-
model: o3
|
44
|
-
params:
|
45
|
-
max_completion_tokens: 5_000
|
46
|
-
if: "workflow.pr? && output.recommendations.any?"
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Demonstration of how to use the Roast instrumentation hooks
|
4
|
+
# This file would typically be placed in PROJECT_ROOT/.roast/initializers/
|
5
|
+
# for automatic loading during workflow execution
|
6
|
+
|
7
|
+
# Example: Log all workflow and step events
|
8
|
+
ActiveSupport::Notifications.subscribe(/roast\.workflow\./) do |name, start, finish, _id, payload|
|
9
|
+
duration = finish - start
|
10
|
+
|
11
|
+
case name
|
12
|
+
when "roast.workflow.start"
|
13
|
+
puts "\n🚀 Workflow starting: #{payload[:name]}"
|
14
|
+
puts " Path: #{payload[:workflow_path]}"
|
15
|
+
puts " Options: #{payload[:options]}"
|
16
|
+
when "roast.workflow.complete"
|
17
|
+
status = payload[:success] ? "✅ Successfully" : "❌ With errors"
|
18
|
+
puts "\n#{status} completed workflow in #{duration.round(2)} seconds"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Example: Track step execution times
|
23
|
+
ActiveSupport::Notifications.subscribe(/roast\.step\./) do |name, start, finish, _id, payload|
|
24
|
+
duration = finish - start
|
25
|
+
|
26
|
+
case name
|
27
|
+
when "roast.step.start"
|
28
|
+
puts "\n ▶️ Step starting: #{payload[:step_name]}"
|
29
|
+
when "roast.step.complete"
|
30
|
+
puts " ✅ Step completed: #{payload[:step_name]} (#{duration.round(3)}s)"
|
31
|
+
when "roast.step.error"
|
32
|
+
puts " ❌ Step failed: #{payload[:step_name]}"
|
33
|
+
puts " Error: #{payload[:error]} - #{payload[:message]}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Example: Monitor AI interactions
|
38
|
+
ActiveSupport::Notifications.subscribe(/roast\.chat_completion\./) do |name, start, finish, _id, payload|
|
39
|
+
case name
|
40
|
+
when "roast.chat_completion.start"
|
41
|
+
puts "\n 🤖 AI request starting (model: #{payload[:model]})"
|
42
|
+
puts " Parameters: #{payload[:parameters].inspect}" if payload[:parameters].any?
|
43
|
+
when "roast.chat_completion.complete"
|
44
|
+
duration = finish - start
|
45
|
+
puts " 🤖 AI request completed in #{duration.round(2)}s (execution time: #{payload[:execution_time].round(2)}s)"
|
46
|
+
puts " Response size: #{payload[:response_size]} characters"
|
47
|
+
when "roast.chat_completion.error"
|
48
|
+
puts " 🤖 AI request failed: #{payload[:error]} - #{payload[:message]}"
|
49
|
+
puts " Execution time: #{payload[:execution_time].round(2)}s"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Example: Track tool executions
|
54
|
+
ActiveSupport::Notifications.subscribe(/roast\.tool\./) do |name, _start, _finish, _id, payload|
|
55
|
+
case name
|
56
|
+
when "roast.tool.execute"
|
57
|
+
puts "\n 🔧 Executing tool: #{payload[:function_name]}"
|
58
|
+
when "roast.tool.complete"
|
59
|
+
puts " 🔧 Tool completed: #{payload[:function_name]} (#{payload[:execution_time].round(3)}s)"
|
60
|
+
puts " Cache enabled: #{payload[:cache_enabled]}"
|
61
|
+
when "roast.tool.error"
|
62
|
+
puts " 🔧 Tool failed: #{payload[:function_name]}"
|
63
|
+
puts " Error: #{payload[:error]} - #{payload[:message]}"
|
64
|
+
puts " Execution time: #{payload[:execution_time].round(3)}s"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# In a Shopify-specific initializer (e.g., .roast/initializers/monorail.rb),
|
69
|
+
# you could replace these logging examples with actual Monorail tracking:
|
70
|
+
#
|
71
|
+
# ActiveSupport::Notifications.subscribe("roast.workflow.start") do |name, start, finish, id, payload|
|
72
|
+
# Roast::Support::Monorail.track_command("run", {
|
73
|
+
# "workflow_path" => payload[:workflow_path],
|
74
|
+
# "name" => payload[:name]
|
75
|
+
# })
|
76
|
+
# end
|