roast-ai 1.1.0 → 1.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/.claude/commands/docs/write-comments.md +1 -1
- data/.rubocop.yml +10 -1
- data/Gemfile.lock +123 -18
- data/README.md +56 -3
- data/examples/custom_logging.rb +4 -2
- data/examples/demo/Gemfile.lock +17 -15
- data/examples/plugin-gem-example/Gemfile.lock +15 -15
- data/examples/simple_chat.rb +1 -1
- data/internal/rubocop/cop/roast/no_test_class_nesting.rb +126 -0
- data/internal/rubocop/rubocop-roast.yml +6 -0
- data/internal/workflows/maintenance/branch_docs_impact.rb +97 -0
- data/internal/workflows/maintenance/deprecated_models_docs_updater.rb +78 -0
- data/lib/roast/cog/config.rb +1 -1
- data/lib/roast/cog_input_manager.rb +28 -7
- data/lib/roast/cogs/agent/config.rb +1 -1
- data/lib/roast/cogs/agent/providers/claude/claude_invocation.rb +2 -2
- data/lib/roast/cogs/agent/providers/claude/messages/result_message.rb +1 -1
- data/lib/roast/cogs/agent/providers/claude/tool_result.rb +344 -4
- data/lib/roast/cogs/agent/providers/claude/tool_use.rb +356 -1
- data/lib/roast/cogs/agent/providers/pi/pi_invocation.rb +2 -2
- data/lib/roast/cogs/agent.rb +3 -2
- data/lib/roast/cogs/chat/config.rb +28 -2
- data/lib/roast/cogs/chat.rb +82 -10
- data/lib/roast/event.rb +1 -0
- data/lib/roast/event_monitor.rb +35 -3
- data/lib/roast/log.rb +21 -0
- data/lib/roast/log_formatter.rb +9 -7
- data/lib/roast/version.rb +1 -1
- data/roast-ai.gemspec +1 -1
- data/tutorial/01_your_first_workflow/README.md +9 -5
- data/tutorial/01_your_first_workflow/configured_chat.rb +1 -1
- data/tutorial/02_chaining_cogs/README.md +2 -2
- data/tutorial/02_chaining_cogs/code_review.rb +1 -1
- data/tutorial/02_chaining_cogs/session_resumption.rb +1 -1
- data/tutorial/03_targets_and_params/README.md +1 -1
- data/tutorial/04_configuration_options/README.md +2 -2
- data/tutorial/08_iterative_workflows/README.md +1 -1
- data/tutorial/README.md +1 -1
- metadata +12 -8
- /data/internal/documentation/{architectural-notes.md → comments/architectural-notes.md} +0 -0
- /data/internal/documentation/{doc-comments-external.md → comments/doc-comments-external.md} +0 -0
- /data/internal/documentation/{doc-comments-internal.md → comments/doc-comments-internal.md} +0 -0
- /data/internal/documentation/{doc-comments.md → comments/doc-comments.md} +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# typed: false
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RuboCop
|
|
5
|
+
module Cop
|
|
6
|
+
module Roast
|
|
7
|
+
# Prevents nesting classes or modules inside reopened (non-test) class
|
|
8
|
+
# definitions in test files.
|
|
9
|
+
#
|
|
10
|
+
# When a class is reopened in a test file (e.g., `class Agent < Cog`) and
|
|
11
|
+
# contains nested class or module definitions, IDE test runners like
|
|
12
|
+
# RubyMine fail to discover test suites. Use `::` scoping instead.
|
|
13
|
+
#
|
|
14
|
+
# The cop walks the entire subtree of the offending class, so deeply
|
|
15
|
+
# nested structures (e.g., `class Agent < Cog; module Providers; ...`)
|
|
16
|
+
# are caught even when the nested definitions are not direct children.
|
|
17
|
+
#
|
|
18
|
+
# Classes nested inside test classes are exempt — helper stubs and
|
|
19
|
+
# fixtures defined inside a test suite are perfectly fine.
|
|
20
|
+
#
|
|
21
|
+
# @example Bad — reopened class with nested module
|
|
22
|
+
# class Agent < Cog
|
|
23
|
+
# module Providers
|
|
24
|
+
# class Claude::MessageTest < ActiveSupport::TestCase
|
|
25
|
+
# # ...
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# @example Bad — reopened class with nested test class
|
|
31
|
+
# class Agent < Cog
|
|
32
|
+
# class ConfigTest < ActiveSupport::TestCase
|
|
33
|
+
# # ...
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# @example Good — :: scoping, no class reopening
|
|
38
|
+
# module Agent::Providers
|
|
39
|
+
# class Claude::MessageTest < ActiveSupport::TestCase
|
|
40
|
+
# # ...
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# @example Good — :: scoped test class
|
|
45
|
+
# class Agent::ConfigTest < ActiveSupport::TestCase
|
|
46
|
+
# # ...
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# @example Good — helper class inside a test class
|
|
50
|
+
# class Agent::OutputTest < ActiveSupport::TestCase
|
|
51
|
+
# class FakeAdapter
|
|
52
|
+
# def call; end
|
|
53
|
+
# end
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
class NoTestClassNesting < Base
|
|
57
|
+
MSG = "Do not nest classes or modules inside reopened class `%<parent>s` in test files. " \
|
|
58
|
+
"Use `::` scoping instead (e.g., `class %<parent>s::Nested` or `module %<parent>s::Nested`)."
|
|
59
|
+
|
|
60
|
+
# @!method test_base_class?(node)
|
|
61
|
+
def_node_matcher :test_base_class?, <<~PATTERN
|
|
62
|
+
{
|
|
63
|
+
(const (const {nil? cbase} :ActiveSupport) :TestCase)
|
|
64
|
+
(const (const {nil? cbase} :Minitest) :Test)
|
|
65
|
+
(const {nil? cbase} :Minitest)
|
|
66
|
+
}
|
|
67
|
+
PATTERN
|
|
68
|
+
|
|
69
|
+
def on_class(node)
|
|
70
|
+
# Test classes are allowed to contain nested definitions (helpers, stubs)
|
|
71
|
+
return if test_class?(node)
|
|
72
|
+
|
|
73
|
+
# Classes nested inside a test class are helpers — leave them alone
|
|
74
|
+
return if inside_test_class?(node)
|
|
75
|
+
|
|
76
|
+
# Flag if this non-test class contains any nested class or module
|
|
77
|
+
return unless contains_nested_definitions?(node)
|
|
78
|
+
|
|
79
|
+
message = format(MSG, parent: node.identifier.const_name)
|
|
80
|
+
add_offense(node.loc.keyword.join(node.identifier.source_range), message: message)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def test_class?(node)
|
|
86
|
+
node.parent_class && test_base_class?(node.parent_class)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns true if any ancestor of +node+ is a test class.
|
|
90
|
+
def inside_test_class?(node)
|
|
91
|
+
current = node.parent
|
|
92
|
+
while current
|
|
93
|
+
return true if current.class_type? && test_class?(current)
|
|
94
|
+
|
|
95
|
+
current = current.parent
|
|
96
|
+
end
|
|
97
|
+
false
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns true if +node+ contains any nested class or module definition
|
|
101
|
+
# at any depth, excluding those sheltered inside an intermediate test class.
|
|
102
|
+
def contains_nested_definitions?(node)
|
|
103
|
+
node.each_descendant(:class, :module) do |descendant|
|
|
104
|
+
next if sheltered_by_test_class?(descendant, node)
|
|
105
|
+
|
|
106
|
+
return true
|
|
107
|
+
end
|
|
108
|
+
false
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Returns true if there is a test class between +descendant+ and +stop_at+
|
|
112
|
+
# in the ancestor chain — meaning the descendant is a helper inside a test
|
|
113
|
+
# class and should not count as a problematic nested definition.
|
|
114
|
+
def sheltered_by_test_class?(descendant, stop_at)
|
|
115
|
+
current = descendant.parent
|
|
116
|
+
while current && current != stop_at
|
|
117
|
+
return true if current.class_type? && test_class?(current)
|
|
118
|
+
|
|
119
|
+
current = current.parent
|
|
120
|
+
end
|
|
121
|
+
false
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::Workflow
|
|
5
|
+
|
|
6
|
+
# Gets the committed diff of the current branch vs origin/main, then analyzes it for potential
|
|
7
|
+
# documentation impacts, and optionally applies fixes. This is meant to be run as a pre-merge check, to
|
|
8
|
+
# catch any potential documentation issues before they get merged into main.
|
|
9
|
+
#
|
|
10
|
+
# Accepts a `--fix` flag to apply any suggested fixes in place. Otherwise, it just reports the analysis
|
|
11
|
+
# and recommended fixes without applying them.
|
|
12
|
+
|
|
13
|
+
config do
|
|
14
|
+
agent do
|
|
15
|
+
provider :claude
|
|
16
|
+
model "claude-opus-4-7"
|
|
17
|
+
quiet!
|
|
18
|
+
end
|
|
19
|
+
chat do
|
|
20
|
+
provider :openai
|
|
21
|
+
model "gpt-5"
|
|
22
|
+
quiet!
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
execute do
|
|
27
|
+
cmd(:diff) do
|
|
28
|
+
merge_base = %x(git merge-base origin/main HEAD).strip
|
|
29
|
+
fail!("could not determine merge-base with origin/main — run `git fetch origin main` first") if merge_base.empty?
|
|
30
|
+
"git diff #{merge_base} HEAD"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
agent(:analyzer) do
|
|
34
|
+
skip! if cmd!(:diff).text.strip.empty?
|
|
35
|
+
fail!("diff too large (#{cmd!(:diff).text.bytesize} bytes) to analyze — narrow the branch or exclude generated files") if cmd!(:diff).text.bytesize > 500_000
|
|
36
|
+
<<~PROMPT
|
|
37
|
+
You are checking whether a git diff makes any existing documentation stale.
|
|
38
|
+
|
|
39
|
+
Rules:
|
|
40
|
+
- Use ONLY the diff below. Do not run any commands.
|
|
41
|
+
- Be thorough about finding real issues — do not be conservative.
|
|
42
|
+
- But do NOT speculate about docs you cannot see in the diff.
|
|
43
|
+
- No markdown headers, no preamble, no caveats, no "limitations" sections.
|
|
44
|
+
|
|
45
|
+
Output format — pick exactly one:
|
|
46
|
+
|
|
47
|
+
If nothing in the diff affects existing docs, output a single line:
|
|
48
|
+
No documentation impact.
|
|
49
|
+
|
|
50
|
+
Otherwise, output one block per affected doc, separated by blank lines:
|
|
51
|
+
<doc/path.md>
|
|
52
|
+
Stale because: <one sentence>
|
|
53
|
+
Fix: <one sentence>
|
|
54
|
+
|
|
55
|
+
--- DIFF START ---
|
|
56
|
+
#{cmd!(:diff).text}
|
|
57
|
+
--- DIFF END ---
|
|
58
|
+
PROMPT
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
chat(:report) do
|
|
62
|
+
skip! if cmd!(:diff).text.strip.empty?
|
|
63
|
+
<<~PROMPT
|
|
64
|
+
Based on the following analysis, summarize the impact of the changes in this branch on the project's documentation.
|
|
65
|
+
Highlight any significant improvements or regressions, and provide recommendations for any additional documentation updates that may be necessary.
|
|
66
|
+
Do not suggest updates that are not needed.
|
|
67
|
+
Do not be verbose. Be super concise.
|
|
68
|
+
|
|
69
|
+
Analysis:
|
|
70
|
+
#{agent!(:analyzer).response}
|
|
71
|
+
PROMPT
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
agent(:fixer) do
|
|
75
|
+
skip! unless arg?(:fix)
|
|
76
|
+
skip! if cmd!(:diff).text.strip.empty?
|
|
77
|
+
skip! if agent!(:analyzer).response.strip == "No documentation impact."
|
|
78
|
+
<<~PROMPT
|
|
79
|
+
Apply the documentation fixes suggested in the analysis below. Edit the
|
|
80
|
+
affected files in place. Do not modify any code files; docs only.
|
|
81
|
+
|
|
82
|
+
#{agent!(:analyzer).response}
|
|
83
|
+
PROMPT
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
ruby(:output) do
|
|
87
|
+
if cmd!(:diff).text.strip.empty?
|
|
88
|
+
puts "No changes vs origin/main — nothing to analyze."
|
|
89
|
+
else
|
|
90
|
+
files = cmd!(:diff).out.scan(%r{^diff --git a/.+ b/(.+)$}).flatten
|
|
91
|
+
puts "Files considered (#{files.size}):"
|
|
92
|
+
files.each { |f| puts " #{f}" }
|
|
93
|
+
puts "ANALYSIS:\n#{chat!(:report).response}"
|
|
94
|
+
puts(agent?(:fixer) ? "Fixes applied." : "(Next time, run with `-- fix` to auto-apply suggested edits)")
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::Workflow
|
|
5
|
+
|
|
6
|
+
# Looks at the Anthropic (https://platform.claude.com/docs/en/about-claude/model-deprecations) and
|
|
7
|
+
# OpenAI (https://developers.openai.com/api/docs/deprecations) model-deprecation pages and updates all
|
|
8
|
+
# docs, doc comments and code references to deprecated/retired models to reflect those changes. This is
|
|
9
|
+
# meant to be run periodically to keep all the references to models up to date.
|
|
10
|
+
|
|
11
|
+
config do
|
|
12
|
+
agent do
|
|
13
|
+
provider :claude
|
|
14
|
+
model "claude-opus-4-8"
|
|
15
|
+
quiet!
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
execute do
|
|
20
|
+
agent(:model_verifier) do
|
|
21
|
+
<<~PROMPT
|
|
22
|
+
Check both of these pages for deprecated/retired models and their suggested replacements:
|
|
23
|
+
- Anthropic (Claude): https://platform.claude.com/docs/en/about-claude/model-deprecations
|
|
24
|
+
- OpenAI: https://developers.openai.com/api/docs/deprecations
|
|
25
|
+
|
|
26
|
+
Include every deprecated or retired model that has a recommended replacement, from both pages.
|
|
27
|
+
Only include models — ignore deprecated API endpoints, tools, or other non-model features.
|
|
28
|
+
|
|
29
|
+
Output ONLY a JSON object of exactly this shape, with no surrounding prose:
|
|
30
|
+
{"outdated": [{"model": "<outdated_model>", "replacement": "<replacement_model>"}]}
|
|
31
|
+
|
|
32
|
+
If there are none, output {"outdated": []}.
|
|
33
|
+
PROMPT
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
agent(:model_finder) do
|
|
37
|
+
<<~PROMPT
|
|
38
|
+
You are a code search engine. Search through the codebase and documentation for any references to
|
|
39
|
+
the outdated models below, including test files, and list every place where they are mentioned.
|
|
40
|
+
|
|
41
|
+
The input is a JSON object of the form:
|
|
42
|
+
{"outdated": [{"model": "<outdated_model>", "replacement": "<replacement_model>"}]}
|
|
43
|
+
|
|
44
|
+
INPUT:
|
|
45
|
+
#{agent!(:model_verifier).response}
|
|
46
|
+
|
|
47
|
+
Output ONLY a JSON object of exactly this shape, with no surrounding prose, carrying the
|
|
48
|
+
replacement through for each model you find a reference to:
|
|
49
|
+
{"references": [{"model": "<outdated_model>", "replacement": "<replacement_model>", "locations": ["<file_path>:<line_number>"]}]}
|
|
50
|
+
|
|
51
|
+
If you find no references, output {"references": []}.
|
|
52
|
+
PROMPT
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
agent(:updater) do |my|
|
|
56
|
+
finder = agent!(:model_finder)
|
|
57
|
+
skip! if finder.json![:references].blank?
|
|
58
|
+
my.session = finder.session
|
|
59
|
+
<<~PROMPT
|
|
60
|
+
For each reference you found of an outdated model, update the reference to use the suggested replacement model instead. Make sure to update all types of references, including documentation, doc comments and code references. Output the list of updated references in the following format:
|
|
61
|
+
<outdated_model_1> -> <replacement_model_1>:
|
|
62
|
+
- <file_path>:<line_number>
|
|
63
|
+
<outdated_model_2> -> <replacement_model_2>:
|
|
64
|
+
- <file_path>:<line_number>
|
|
65
|
+
...
|
|
66
|
+
PROMPT
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
ruby(:output) do
|
|
70
|
+
if agent?(:updater)
|
|
71
|
+
puts "[OUTDATED MODELS & REPLACEMENTS]\n #{agent!(:model_verifier).response}"
|
|
72
|
+
puts "[REFERENCES IN CODEBASE]\n #{agent!(:model_finder).response}"
|
|
73
|
+
puts "[UPDATED REFERENCES]\n #{agent!(:updater).response}"
|
|
74
|
+
else
|
|
75
|
+
puts "No references to outdated models found — nothing to update."
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/roast/cog/config.rb
CHANGED
|
@@ -159,7 +159,7 @@ module Roast
|
|
|
159
159
|
# Supports both relative shorthand paths like "greeting" and full absolute paths.
|
|
160
160
|
#
|
|
161
161
|
# @param path [String, Pathname] The template path to resolve. Can be:
|
|
162
|
-
# - Shorthand name: "greeting" -> searches for prompts/greeting.md.erb
|
|
162
|
+
# - Shorthand name: "greeting" -> searches for prompts/greeting.md.erb or templates/greeting.md.erb
|
|
163
163
|
# - With extension: "template.erb" -> searches for template.erb
|
|
164
164
|
# - Absolute path: "/full/path/to/template.erb" -> uses as-is
|
|
165
165
|
# @param args [Hash] Template variables for ERB interpolation
|
|
@@ -175,13 +175,14 @@ module Roast
|
|
|
175
175
|
# 1. Absolute path as-is (if absolute)
|
|
176
176
|
# 2-4. Workflow directory: path, path.erb, path.md.erb
|
|
177
177
|
# 5-7. Workflow directory prompts/: prompts/path, prompts/path.erb, prompts/path.md.erb
|
|
178
|
-
# 8-10.
|
|
179
|
-
# 11-13. Current directory
|
|
178
|
+
# 8-10. Workflow directory templates/: templates/path, templates/path.erb, templates/path.md.erb
|
|
179
|
+
# 11-13. Current directory: path, path.erb, path.md.erb
|
|
180
|
+
# 14-16. Current directory prompts/: prompts/path, prompts/path.erb, prompts/path.md.erb
|
|
181
|
+
# 17-19. Current directory templates/: templates/path, templates/path.erb, templates/path.md.erb
|
|
182
|
+
# 20-22. Tilde-expanded path: path, path.erb, path.md.erb
|
|
180
183
|
#
|
|
181
184
|
#: (String | Pathname, ?Hash) -> String
|
|
182
185
|
def template(path, args = {})
|
|
183
|
-
# NOTE: Pathname does not expand ~ for home directory automatically.
|
|
184
|
-
# This is tracked in issue https://github.com/Shopify/roast/issues/663.
|
|
185
186
|
path = Pathname.new(path) unless path.is_a?(Pathname)
|
|
186
187
|
|
|
187
188
|
# Priority stack of places to look for a matching file
|
|
@@ -201,17 +202,37 @@ module Roast
|
|
|
201
202
|
candidate_paths << workflow_dir / "prompts" / "#{path}.erb"
|
|
202
203
|
candidate_paths << workflow_dir / "prompts" / "#{path}.md.erb"
|
|
203
204
|
|
|
204
|
-
# 8-10. Relative to
|
|
205
|
+
# 8-10. Relative to workflow directory templates folder
|
|
206
|
+
candidate_paths << workflow_dir / "templates" / path
|
|
207
|
+
candidate_paths << workflow_dir / "templates" / "#{path}.erb"
|
|
208
|
+
candidate_paths << workflow_dir / "templates" / "#{path}.md.erb"
|
|
209
|
+
|
|
210
|
+
# 11-13. Relative to current working directory
|
|
205
211
|
pwd = Pathname.pwd
|
|
206
212
|
candidate_paths << pwd / path
|
|
207
213
|
candidate_paths << pwd / "#{path}.erb"
|
|
208
214
|
candidate_paths << pwd / "#{path}.md.erb"
|
|
209
215
|
|
|
210
|
-
#
|
|
216
|
+
# 14-16. Relative to current working directory prompts folder
|
|
211
217
|
candidate_paths << pwd / "prompts" / path
|
|
212
218
|
candidate_paths << pwd / "prompts" / "#{path}.erb"
|
|
213
219
|
candidate_paths << pwd / "prompts" / "#{path}.md.erb"
|
|
214
220
|
|
|
221
|
+
# 17-19. Relative to current working directory templates folder
|
|
222
|
+
candidate_paths << pwd / "templates" / path
|
|
223
|
+
candidate_paths << pwd / "templates" / "#{path}.erb"
|
|
224
|
+
candidate_paths << pwd / "templates" / "#{path}.md.erb"
|
|
225
|
+
|
|
226
|
+
# 20-22. Tilde expanded path
|
|
227
|
+
begin
|
|
228
|
+
expanded_path = Pathname.new(File.expand_path(path))
|
|
229
|
+
candidate_paths << expanded_path
|
|
230
|
+
candidate_paths << Pathname.new(File.expand_path("#{expanded_path}.erb"))
|
|
231
|
+
candidate_paths << Pathname.new(File.expand_path("#{expanded_path}.md.erb"))
|
|
232
|
+
rescue ArgumentError
|
|
233
|
+
# File.expand_path raises when expanding ~something/foo (assuming "something" is not a real user).
|
|
234
|
+
# Nothing to do here, falls back to other candidate paths without tilde expansion.
|
|
235
|
+
end
|
|
215
236
|
# Use the first path that exists
|
|
216
237
|
resolved_path = candidate_paths.find(&:exist?)
|
|
217
238
|
|
|
@@ -51,7 +51,7 @@ module Roast
|
|
|
51
51
|
def valid_provider!
|
|
52
52
|
provider = @values[:provider] || VALID_PROVIDERS.first
|
|
53
53
|
unless VALID_PROVIDERS.include?(provider)
|
|
54
|
-
raise
|
|
54
|
+
raise InvalidConfigError, "'#{provider}' is not a valid provider. Available providers include: #{VALID_PROVIDERS.join(", ")}"
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
provider
|
|
@@ -77,7 +77,7 @@ module Roast
|
|
|
77
77
|
raise ClaudeAlreadyStartedError if started?
|
|
78
78
|
|
|
79
79
|
@started = true
|
|
80
|
-
|
|
80
|
+
Event << { block: { header: "USER PROMPT", content: @prompt } } if @show_prompt
|
|
81
81
|
_stdout, stderr, status = CommandRunner.execute(
|
|
82
82
|
command_line,
|
|
83
83
|
working_directory: @working_directory,
|
|
@@ -87,7 +87,7 @@ module Roast
|
|
|
87
87
|
|
|
88
88
|
if status.success?
|
|
89
89
|
@completed = true
|
|
90
|
-
|
|
90
|
+
Event << { block: { header: "AGENT RESPONSE", content: @result.response } } if @show_response
|
|
91
91
|
else
|
|
92
92
|
@failed = true
|
|
93
93
|
@result.success = false
|
|
@@ -31,7 +31,7 @@ module Roast
|
|
|
31
31
|
@content = hash.delete(:result) || ""
|
|
32
32
|
@success = hash.delete(:success) || subtype == "success"
|
|
33
33
|
if hash.delete(:is_error) || subtype == "error"
|
|
34
|
-
@content = @content || hash.dig(:error, :message) || "Unknown error"
|
|
34
|
+
@content = @content.presence || hash.dig(:error, :message) || "Unknown error"
|
|
35
35
|
hash.delete(:error)
|
|
36
36
|
end
|
|
37
37
|
|