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.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +1 -1
  3. data/CHANGELOG.md +40 -1
  4. data/CLAUDE.md +20 -0
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +9 -6
  7. data/README.md +81 -14
  8. data/bin/roast +27 -0
  9. data/docs/ITERATION_SYNTAX.md +119 -0
  10. data/examples/conditional/README.md +161 -0
  11. data/examples/conditional/check_condition/prompt.md +1 -0
  12. data/examples/conditional/simple_workflow.yml +15 -0
  13. data/examples/conditional/workflow.yml +23 -0
  14. data/examples/dot_notation/README.md +37 -0
  15. data/examples/dot_notation/workflow.yml +44 -0
  16. data/examples/exit_on_error/README.md +50 -0
  17. data/examples/exit_on_error/analyze_lint_output/prompt.md +9 -0
  18. data/examples/exit_on_error/apply_fixes/prompt.md +2 -0
  19. data/examples/exit_on_error/workflow.yml +19 -0
  20. data/examples/grading/workflow.yml +5 -1
  21. data/examples/iteration/IMPLEMENTATION.md +88 -0
  22. data/examples/iteration/README.md +68 -0
  23. data/examples/iteration/analyze_complexity/prompt.md +22 -0
  24. data/examples/iteration/generate_recommendations/prompt.md +21 -0
  25. data/examples/iteration/generate_report/prompt.md +129 -0
  26. data/examples/iteration/implement_fix/prompt.md +25 -0
  27. data/examples/iteration/prioritize_issues/prompt.md +24 -0
  28. data/examples/iteration/prompts/analyze_file.md +28 -0
  29. data/examples/iteration/prompts/generate_summary.md +24 -0
  30. data/examples/iteration/prompts/update_report.md +29 -0
  31. data/examples/iteration/prompts/write_report.md +22 -0
  32. data/examples/iteration/read_file/prompt.md +9 -0
  33. data/examples/iteration/select_next_issue/prompt.md +25 -0
  34. data/examples/iteration/simple_workflow.md +39 -0
  35. data/examples/iteration/simple_workflow.yml +58 -0
  36. data/examples/iteration/update_fix_count/prompt.md +26 -0
  37. data/examples/iteration/verify_fix/prompt.md +29 -0
  38. data/examples/iteration/workflow.yml +42 -0
  39. data/examples/openrouter_example/workflow.yml +2 -2
  40. data/examples/workflow_generator/README.md +27 -0
  41. data/examples/workflow_generator/analyze_user_request/prompt.md +34 -0
  42. data/examples/workflow_generator/create_workflow_files/prompt.md +32 -0
  43. data/examples/workflow_generator/get_user_input/prompt.md +14 -0
  44. data/examples/workflow_generator/info_from_roast.rb +22 -0
  45. data/examples/workflow_generator/workflow.yml +35 -0
  46. data/lib/roast/errors.rb +9 -0
  47. data/lib/roast/factories/api_provider_factory.rb +61 -0
  48. data/lib/roast/helpers/function_caching_interceptor.rb +1 -1
  49. data/lib/roast/helpers/minitest_coverage_runner.rb +1 -1
  50. data/lib/roast/helpers/prompt_loader.rb +50 -1
  51. data/lib/roast/resources/base_resource.rb +7 -0
  52. data/lib/roast/resources.rb +6 -6
  53. data/lib/roast/tools/ask_user.rb +40 -0
  54. data/lib/roast/tools/cmd.rb +1 -1
  55. data/lib/roast/tools/search_file.rb +1 -1
  56. data/lib/roast/tools.rb +11 -1
  57. data/lib/roast/value_objects/api_token.rb +49 -0
  58. data/lib/roast/value_objects/step_name.rb +39 -0
  59. data/lib/roast/value_objects/workflow_path.rb +77 -0
  60. data/lib/roast/value_objects.rb +5 -0
  61. data/lib/roast/version.rb +1 -1
  62. data/lib/roast/workflow/api_configuration.rb +61 -0
  63. data/lib/roast/workflow/base_iteration_step.rb +165 -0
  64. data/lib/roast/workflow/base_step.rb +4 -24
  65. data/lib/roast/workflow/base_workflow.rb +76 -73
  66. data/lib/roast/workflow/command_executor.rb +88 -0
  67. data/lib/roast/workflow/conditional_executor.rb +50 -0
  68. data/lib/roast/workflow/conditional_step.rb +96 -0
  69. data/lib/roast/workflow/configuration.rb +35 -158
  70. data/lib/roast/workflow/configuration_loader.rb +78 -0
  71. data/lib/roast/workflow/configuration_parser.rb +13 -248
  72. data/lib/roast/workflow/context_path_resolver.rb +43 -0
  73. data/lib/roast/workflow/dot_access_hash.rb +198 -0
  74. data/lib/roast/workflow/each_step.rb +86 -0
  75. data/lib/roast/workflow/error_handler.rb +97 -0
  76. data/lib/roast/workflow/expression_utils.rb +36 -0
  77. data/lib/roast/workflow/file_state_repository.rb +3 -2
  78. data/lib/roast/workflow/interpolator.rb +34 -0
  79. data/lib/roast/workflow/iteration_executor.rb +85 -0
  80. data/lib/roast/workflow/llm_boolean_coercer.rb +55 -0
  81. data/lib/roast/workflow/output_handler.rb +35 -0
  82. data/lib/roast/workflow/output_manager.rb +77 -0
  83. data/lib/roast/workflow/parallel_executor.rb +49 -0
  84. data/lib/roast/workflow/repeat_step.rb +75 -0
  85. data/lib/roast/workflow/replay_handler.rb +123 -0
  86. data/lib/roast/workflow/resource_resolver.rb +77 -0
  87. data/lib/roast/workflow/session_manager.rb +6 -2
  88. data/lib/roast/workflow/state_manager.rb +97 -0
  89. data/lib/roast/workflow/step_executor_coordinator.rb +205 -0
  90. data/lib/roast/workflow/step_executor_factory.rb +47 -0
  91. data/lib/roast/workflow/step_executor_registry.rb +79 -0
  92. data/lib/roast/workflow/step_executors/base_step_executor.rb +23 -0
  93. data/lib/roast/workflow/step_executors/hash_step_executor.rb +43 -0
  94. data/lib/roast/workflow/step_executors/parallel_step_executor.rb +54 -0
  95. data/lib/roast/workflow/step_executors/string_step_executor.rb +29 -0
  96. data/lib/roast/workflow/step_finder.rb +97 -0
  97. data/lib/roast/workflow/step_loader.rb +154 -0
  98. data/lib/roast/workflow/step_orchestrator.rb +45 -0
  99. data/lib/roast/workflow/step_runner.rb +23 -0
  100. data/lib/roast/workflow/step_type_resolver.rb +117 -0
  101. data/lib/roast/workflow/workflow_context.rb +60 -0
  102. data/lib/roast/workflow/workflow_executor.rb +90 -209
  103. data/lib/roast/workflow/workflow_initializer.rb +112 -0
  104. data/lib/roast/workflow/workflow_runner.rb +87 -0
  105. data/lib/roast/workflow.rb +3 -0
  106. data/lib/roast.rb +96 -3
  107. data/roast.gemspec +2 -1
  108. data/schema/workflow.json +85 -0
  109. metadata +97 -4
@@ -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
@@ -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
data/lib/roast/tools.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "active_support/cache"
4
4
  require "English"
5
+ require "fileutils"
5
6
 
6
7
  require "roast/tools/grep"
7
8
  require "roast/tools/read_file"
@@ -10,12 +11,21 @@ require "roast/tools/write_file"
10
11
  require "roast/tools/update_files"
11
12
  require "roast/tools/cmd"
12
13
  require "roast/tools/coding_agent"
14
+ require "roast/tools/ask_user"
13
15
 
14
16
  module Roast
15
17
  module Tools
16
18
  extend self
17
19
 
18
- CACHE = ActiveSupport::Cache::FileStore.new(File.join(Dir.pwd, ".roast", "cache"))
20
+ # Initialize cache and ensure .gitignore exists
21
+ cache_dir = File.join(Dir.pwd, ".roast", "cache")
22
+ FileUtils.mkdir_p(cache_dir) unless File.directory?(cache_dir)
23
+
24
+ # Add .gitignore to cache directory
25
+ gitignore_path = File.join(cache_dir, ".gitignore")
26
+ File.write(gitignore_path, "*") unless File.exist?(gitignore_path)
27
+
28
+ CACHE = ActiveSupport::Cache::FileStore.new(cache_dir)
19
29
 
20
30
  def file_to_prompt(file)
21
31
  <<~PROMPT
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roast
4
+ module ValueObjects
5
+ # Value object representing an API token with validation
6
+ class ApiToken
7
+ class InvalidTokenError < StandardError; end
8
+
9
+ attr_reader :value
10
+
11
+ def initialize(value)
12
+ @value = value&.to_s
13
+ validate!
14
+ freeze
15
+ end
16
+
17
+ def present?
18
+ !blank?
19
+ end
20
+
21
+ def blank?
22
+ @value.nil? || @value.strip.empty?
23
+ end
24
+
25
+ def to_s
26
+ @value
27
+ end
28
+
29
+ def ==(other)
30
+ return false unless other.is_a?(ApiToken)
31
+
32
+ value == other.value
33
+ end
34
+ alias_method :eql?, :==
35
+
36
+ def hash
37
+ [self.class, @value].hash
38
+ end
39
+
40
+ private
41
+
42
+ def validate!
43
+ return if @value.nil? # Allow nil tokens, just not empty strings
44
+
45
+ raise InvalidTokenError, "API token cannot be an empty string" if @value.strip.empty?
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roast
4
+ module ValueObjects
5
+ # Value object representing a step name, which can be either a plain text prompt
6
+ # or a reference to a step file
7
+ class StepName
8
+ attr_reader :value
9
+
10
+ def initialize(name)
11
+ @value = name.to_s.strip
12
+ freeze
13
+ end
14
+
15
+ def plain_text?
16
+ @value.include?(" ")
17
+ end
18
+
19
+ def file_reference?
20
+ !plain_text?
21
+ end
22
+
23
+ def to_s
24
+ @value
25
+ end
26
+
27
+ def ==(other)
28
+ return false unless other.is_a?(StepName)
29
+
30
+ value == other.value
31
+ end
32
+ alias_method :eql?, :==
33
+
34
+ def hash
35
+ [self.class, @value].hash
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module Roast
6
+ module ValueObjects
7
+ # Value object representing a workflow file path with validation and resolution
8
+ class WorkflowPath
9
+ class InvalidPathError < StandardError; end
10
+
11
+ attr_reader :value
12
+
13
+ def initialize(path)
14
+ @value = normalize_path(path)
15
+ @pathname = Pathname.new(@value)
16
+ validate!
17
+ freeze
18
+ end
19
+
20
+ def exist?
21
+ pathname.exist?
22
+ end
23
+
24
+ def absolute?
25
+ pathname.absolute?
26
+ end
27
+
28
+ def relative?
29
+ pathname.relative?
30
+ end
31
+
32
+ def dirname
33
+ pathname.dirname.to_s
34
+ end
35
+
36
+ def basename
37
+ pathname.basename.to_s
38
+ end
39
+
40
+ def to_s
41
+ @value
42
+ end
43
+
44
+ def to_path
45
+ @value
46
+ end
47
+
48
+ def ==(other)
49
+ return false unless other.is_a?(WorkflowPath)
50
+
51
+ value == other.value
52
+ end
53
+ alias_method :eql?, :==
54
+
55
+ def hash
56
+ [self.class, @value].hash
57
+ end
58
+
59
+ private
60
+
61
+ attr_reader :pathname
62
+
63
+ def normalize_path(path)
64
+ path.to_s.strip
65
+ end
66
+
67
+ def validate!
68
+ raise InvalidPathError, "Workflow path cannot be empty" if @value.empty?
69
+ raise InvalidPathError, "Workflow path must have .yml or .yaml extension" unless valid_extension?
70
+ end
71
+
72
+ def valid_extension?
73
+ @value.end_with?(".yml") || @value.end_with?(".yaml")
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roast/value_objects/api_token"
4
+ require "roast/value_objects/step_name"
5
+ require "roast/value_objects/workflow_path"
data/lib/roast/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Roast
4
- VERSION = "0.1.7"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roast/factories/api_provider_factory"
4
+ require "roast/workflow/resource_resolver"
5
+
6
+ module Roast
7
+ module Workflow
8
+ # Handles API-related configuration including tokens and providers
9
+ class ApiConfiguration
10
+ attr_reader :api_token, :api_provider
11
+
12
+ def initialize(config_hash)
13
+ @config_hash = config_hash
14
+ process_api_configuration
15
+ end
16
+
17
+ # Check if using OpenRouter
18
+ # @return [Boolean] true if using OpenRouter
19
+ def openrouter?
20
+ Roast::Factories::ApiProviderFactory.openrouter?(@api_provider)
21
+ end
22
+
23
+ # Check if using OpenAI
24
+ # @return [Boolean] true if using OpenAI
25
+ def openai?
26
+ Roast::Factories::ApiProviderFactory.openai?(@api_provider)
27
+ end
28
+
29
+ # Get the effective API token including environment variables
30
+ # @return [String, nil] The API token
31
+ def effective_token
32
+ @api_token || environment_token
33
+ end
34
+
35
+ private
36
+
37
+ def process_api_configuration
38
+ extract_api_token
39
+ extract_api_provider
40
+ end
41
+
42
+ def extract_api_token
43
+ if @config_hash["api_token"]
44
+ @api_token = ResourceResolver.process_shell_command(@config_hash["api_token"])
45
+ end
46
+ end
47
+
48
+ def extract_api_provider
49
+ @api_provider = Roast::Factories::ApiProviderFactory.from_config(@config_hash)
50
+ end
51
+
52
+ def environment_token
53
+ if openai?
54
+ ENV["OPENAI_API_KEY"]
55
+ elsif openrouter?
56
+ ENV["OPENROUTER_API_KEY"]
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roast/workflow/expression_utils"
4
+ require "roast/workflow/llm_boolean_coercer"
5
+ require "roast/workflow/workflow_executor"
6
+
7
+ module Roast
8
+ module Workflow
9
+ # Base class for iteration steps (RepeatStep and EachStep)
10
+ class BaseIterationStep < BaseStep
11
+ include ExpressionUtils
12
+
13
+ DEFAULT_MAX_ITERATIONS = 100
14
+
15
+ attr_reader :steps
16
+
17
+ def initialize(workflow, steps:, **kwargs)
18
+ super(workflow, **kwargs)
19
+ @steps = steps
20
+ # Don't initialize cmd_tool here - we'll do it lazily when needed
21
+ end
22
+
23
+ protected
24
+
25
+ # Process various types of inputs and convert to appropriate types for iteration
26
+ def process_iteration_input(input, context, coerce_to: nil)
27
+ if input.is_a?(String)
28
+ if ruby_expression?(input)
29
+ process_ruby_expression(input, context, coerce_to)
30
+ elsif bash_command?(input)
31
+ process_bash_command(input, coerce_to)
32
+ else
33
+ process_step_or_prompt(input, context, coerce_to)
34
+ end
35
+ else
36
+ # Non-string inputs are coerced as-is
37
+ coerce_result(input, coerce_to)
38
+ end
39
+ end
40
+
41
+ # Interpolates {{expression}} in a string with values from the workflow context
42
+ def interpolate_expression(text, context)
43
+ return text unless text.is_a?(String) && text.include?("{{") && text.include?("}}")
44
+
45
+ # Replace all {{expression}} with their evaluated values
46
+ text.gsub(/\{\{([^}]+)\}\}/) do |match|
47
+ expression = extract_expression(match)
48
+ begin
49
+ # Evaluate the expression in the workflow's context
50
+ result = context.instance_eval(expression)
51
+ result.inspect # Convert to string representation
52
+ rescue => e
53
+ warn_interpolation_error(expression, e)
54
+ match # Return the original match to preserve it in the string
55
+ end
56
+ end
57
+ end
58
+
59
+ # Execute nested steps
60
+ def execute_nested_steps(steps, context, executor = nil)
61
+ executor ||= WorkflowExecutor.new(context, {}, context_path)
62
+ results = []
63
+
64
+ steps.each do |step|
65
+ result = case step
66
+ when String
67
+ executor.execute_step(step)
68
+ when Hash, Array
69
+ executor.execute_steps([step])
70
+ end
71
+ results << result
72
+ end
73
+
74
+ results
75
+ end
76
+
77
+ private
78
+
79
+ # Process a Ruby expression
80
+ def process_ruby_expression(input, context, coerce_to)
81
+ expression = extract_expression(input)
82
+ result = evaluate_ruby_expression(expression, context)
83
+ coerce_result(result, coerce_to)
84
+ end
85
+
86
+ # Process a Bash command
87
+ def process_bash_command(input, coerce_to)
88
+ command = extract_command(input)
89
+ execute_command(command, coerce_to)
90
+ end
91
+
92
+ # Process a step name or prompt
93
+ def process_step_or_prompt(input, context, coerce_to)
94
+ step_result = execute_step_by_name(input, context)
95
+ coerce_result(step_result, coerce_to)
96
+ end
97
+
98
+ # Execute a Ruby expression in the workflow context
99
+ def evaluate_ruby_expression(expression, context)
100
+ context.instance_eval(expression)
101
+ rescue => e
102
+ warn_expression_error(expression, e)
103
+ nil
104
+ end
105
+
106
+ # Execute a bash command and return its result
107
+ def execute_command(command, coerce_to)
108
+ # Use the Cmd module to execute the command
109
+ result = Roast::Tools::Cmd.call(command)
110
+
111
+ if coerce_to == :boolean
112
+ # For boolean coercion, use exit status (assume success unless error message)
113
+ !result.to_s.start_with?("Error")
114
+ else
115
+ # For other uses, return the output
116
+ result
117
+ end
118
+ end
119
+
120
+ # Execute a step by name and return its result
121
+ def execute_step_by_name(step_name, context)
122
+ # Reuse existing step execution logic
123
+ executor = WorkflowExecutor.new(context, {}, context_path)
124
+ executor.execute_step(step_name)
125
+ end
126
+
127
+ # Coerce results to the appropriate type
128
+ def coerce_result(result, coerce_to)
129
+ return coerce_to_boolean(result) if coerce_to == :boolean
130
+ return coerce_to_iterable(result) if coerce_to == :iterable
131
+ return coerce_to_llm_boolean(result) if coerce_to == :llm_boolean
132
+
133
+ # Default - return as is
134
+ result
135
+ end
136
+
137
+ # Force a value to boolean
138
+ def coerce_to_boolean(result)
139
+ !!result
140
+ end
141
+
142
+ # Ensure a value is iterable
143
+ def coerce_to_iterable(result)
144
+ return result if result.respond_to?(:each)
145
+
146
+ result.to_s.split("\n")
147
+ end
148
+
149
+ # Convert LLM response to boolean
150
+ def coerce_to_llm_boolean(result)
151
+ LlmBooleanCoercer.coerce(result)
152
+ end
153
+
154
+ # Log a warning for expression evaluation errors
155
+ def warn_expression_error(expression, error)
156
+ $stderr.puts "Warning: Error evaluating expression '#{expression}': #{error.message}"
157
+ end
158
+
159
+ # Log a warning for interpolation errors
160
+ def warn_interpolation_error(expression, error)
161
+ $stderr.puts "Warning: Error interpolating {{#{expression}}}: #{error.message}"
162
+ end
163
+ end
164
+ end
165
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "erb"
4
4
  require "forwardable"
5
+ require "roast/workflow/context_path_resolver"
5
6
 
6
7
  module Roast
7
8
  module Workflow
@@ -15,11 +16,11 @@ module Roast
15
16
  def_delegator :workflow, :chat_completion
16
17
  def_delegator :workflow, :transcript
17
18
 
18
- def initialize(workflow, model: "anthropic:claude-3-7-sonnet", name: nil, context_path: nil, auto_loop: true)
19
+ def initialize(workflow, model: "anthropic:claude-opus-4", name: nil, context_path: nil, auto_loop: true)
19
20
  @workflow = workflow
20
21
  @model = model
21
22
  @name = name || self.class.name.underscore.split("/").last
22
- @context_path = context_path || determine_context_path
23
+ @context_path = context_path || ContextPathResolver.resolve(self.class)
23
24
  @print_response = false
24
25
  @auto_loop = auto_loop
25
26
  @json = false
@@ -35,7 +36,7 @@ module Roast
35
36
  protected
36
37
 
37
38
  def chat_completion(print_response: false, auto_loop: true, json: false, params: {})
38
- workflow.chat_completion(openai: model, loop: auto_loop, json:, params:).then do |response|
39
+ workflow.chat_completion(openai: workflow.openai? && model, loop: auto_loop, model: model, json:, params:).then do |response|
39
40
  case response
40
41
  in Array
41
42
  response.map(&:presence).compact.join("\n")
@@ -47,27 +48,6 @@ module Roast
47
48
  end
48
49
  end
49
50
 
50
- # Determine the directory where the actual class is defined, not BaseWorkflow
51
- def determine_context_path
52
- # Get the actual class's source file
53
- klass = self.class
54
-
55
- # Try to get the file path where the class is defined
56
- path = if klass.name.include?("::")
57
- # For namespaced classes like Roast::Workflow::Grading::Workflow
58
- # Convert the class name to a relative path
59
- class_path = klass.name.underscore + ".rb"
60
- # Look through load path to find the actual file
61
- $LOAD_PATH.map { |p| File.join(p, class_path) }.find { |f| File.exist?(f) }
62
- else
63
- # Fall back to the current file if we can't find it
64
- __FILE__
65
- end
66
-
67
- # Return directory containing the class definition
68
- File.dirname(path || __FILE__)
69
- end
70
-
71
51
  def prompt(text)
72
52
  transcript << { user: text }
73
53
  end