ralph.rb 1.2.435535439 → 2.0.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/gem-push.yml +2 -2
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +53 -0
  5. data/lib/ralph/cli.rb +67 -186
  6. data/lib/ralph/display.rb +105 -0
  7. data/lib/ralph/events.rb +117 -0
  8. data/lib/ralph/loop.rb +113 -170
  9. data/lib/ralph/metrics.rb +88 -0
  10. data/lib/ralph/opencode.rb +66 -0
  11. data/lib/ralph/version.rb +1 -1
  12. data/lib/ralph.rb +0 -3
  13. data/plans/00-complete-implementation.md +120 -0
  14. data/plans/01-cli-implementation.md +53 -0
  15. data/plans/02-loop-implementation.md +78 -0
  16. data/plans/03-agents-implementation.md +76 -0
  17. data/plans/04-metrics-implementation.md +98 -0
  18. data/plans/README.md +63 -0
  19. data/specs/README.md +4 -15
  20. data/specs/__templates__/API_TEMPLATE.md +0 -0
  21. data/specs/__templates__/AUTOMATION_ACTION_TEMPLATE.md +0 -0
  22. data/specs/__templates__/AUTOMATION_TRIGGER_TEMPLATE.md +0 -0
  23. data/specs/__templates__/CONTROLLER_TEMPLATE.md +32 -0
  24. data/specs/__templates__/INTEGRATION_TEMPLATE.md +0 -0
  25. data/specs/__templates__/MODEL_TEMPLATE.md +0 -0
  26. data/specs/agents.md +426 -120
  27. data/specs/cli.md +11 -218
  28. data/specs/lib/todo_item.rb +144 -0
  29. data/specs/log +15 -0
  30. data/specs/loop.md +42 -0
  31. data/specs/metrics.md +51 -0
  32. metadata +23 -39
  33. data/lib/ralph/agents/base.rb +0 -132
  34. data/lib/ralph/agents/claude_code.rb +0 -24
  35. data/lib/ralph/agents/codex.rb +0 -25
  36. data/lib/ralph/agents/open_code.rb +0 -30
  37. data/lib/ralph/agents.rb +0 -24
  38. data/lib/ralph/config.rb +0 -40
  39. data/lib/ralph/git/file_snapshot.rb +0 -60
  40. data/lib/ralph/helpers.rb +0 -76
  41. data/lib/ralph/iteration.rb +0 -220
  42. data/lib/ralph/output/active_loop_error.rb +0 -13
  43. data/lib/ralph/output/banner.rb +0 -29
  44. data/lib/ralph/output/completion_deferred.rb +0 -12
  45. data/lib/ralph/output/completion_detected.rb +0 -17
  46. data/lib/ralph/output/config_summary.rb +0 -31
  47. data/lib/ralph/output/context_consumed.rb +0 -11
  48. data/lib/ralph/output/iteration.rb +0 -45
  49. data/lib/ralph/output/max_iterations_reached.rb +0 -16
  50. data/lib/ralph/output/no_plugin_warning.rb +0 -14
  51. data/lib/ralph/output/nonzero_exit_warning.rb +0 -11
  52. data/lib/ralph/output/plugin_error.rb +0 -12
  53. data/lib/ralph/output/status.rb +0 -176
  54. data/lib/ralph/output/struggle_warning.rb +0 -18
  55. data/lib/ralph/output/task_completion.rb +0 -12
  56. data/lib/ralph/output/tasks_file_created.rb +0 -11
  57. data/lib/ralph/prompt_template.rb +0 -183
  58. data/lib/ralph/storage/context.rb +0 -58
  59. data/lib/ralph/storage/history.rb +0 -117
  60. data/lib/ralph/storage/state.rb +0 -178
  61. data/lib/ralph/storage/tasks.rb +0 -244
  62. data/lib/ralph/threads/heartbeat.rb +0 -44
  63. data/lib/ralph/threads/stream_reader.rb +0 -50
  64. data/original/bin/ralph.js +0 -13
  65. data/original/ralph.ts +0 -1706
  66. data/specs/iteration.md +0 -173
  67. data/specs/output.md +0 -104
  68. data/specs/storage/local-data-structure.md +0 -246
  69. data/specs/tasks.md +0 -295
data/lib/ralph/loop.rb CHANGED
@@ -1,196 +1,139 @@
1
- # _ _ _
2
- # _ __ __ _| |_ __ | |__ __ _(_) __ _ __ _ _ _ _ __ ___
3
- # | '__/ _` | | '_ \| '_ \ \ \ /\ / / |/ _` |/ _` | | | | '_ ` _ \
4
- # | | | (_| | | |_) | | | | \ V V /| | (_| | (_| | |_| | | | | | |
5
- # |_| \__,_|_| .__/|_| |_| \_/\_/ |_|\__, |\__, |\__,_|_| |_| |_|
6
- # |_| |___/ |___/
7
- #
8
1
  # frozen_string_literal: true
9
2
 
10
3
  module Ralph
4
+ # The core iteration engine. Runs opencode in a loop, restarting fresh
5
+ # iterations whenever context grows too large or time limits are hit.
6
+ # The loop ends when:
7
+ # - the agent emits the completion string
8
+ # - max iterations are reached
9
+ # - total duration is exceeded
11
10
  class Loop
12
- include ::Ralph::Helpers
13
-
14
- attr_reader :config, :agent, :state, :history, :context, :tasks, :prompt, :struggle_indicators
15
-
16
- def initialize(config, state, history, context, tasks)
17
- @config = config
18
- @agent = Agents.resolve(@config.chosen_agent)
19
- @state = state
20
- @history = history
21
- @context = context
22
- @tasks = tasks
23
-
24
- @struggle_indicators = {
25
- repeated_errors: {},
26
- no_progress_iterations: 0,
27
- short_iterations: 0
28
- }
29
-
30
- @prompt = @config.prompt
31
- @existing_state = Storage::State.load
32
- @state.save
11
+ SYSTEM_PROMPT = <<~PROMPT
12
+ You are working autonomously in a loop. IMPORTANT RULES:
13
+ 1. Do NOT ask the user any questions. Do NOT wait for user input.
14
+ If you need information, read the specs, code, or docs yourself.
15
+ 2. Work through the task methodically. Use the todo list to track progress.
16
+ 3. When you have FULLY completed the task, you MUST output the exact
17
+ completion string on its own line: %<completion>s
18
+ 4. Do not output the completion string until ALL work is truly done.
19
+ PROMPT
20
+
21
+ attr_reader :metrics, :iteration_number, :completed
22
+
23
+ def initialize(options)
24
+ @prompt = options[:prompt]
25
+ @model = options[:model]
26
+ @max_iterations = options[:max_iterations]
27
+ @duration_limit = options[:duration]
28
+ @max_context = options[:max_context]
29
+ @completion_string = options[:completion] || "<promise>COMPLETE</promise>"
30
+ @metrics = Metrics.new
31
+ @iteration_number = 0
32
+ @completed = false
33
+ @started_at = nil
34
+ @display = Display.new(self)
33
35
  end
34
36
 
35
- def existing_state = @existing_state
36
-
37
+ # Run the main loop until a termination condition is met.
37
38
  def run
38
- if existing_state&.active
39
- Output::ActiveLoopError.call(existing_state, path: Storage::State.path)
40
- exit 1
39
+ @started_at = now_seconds
40
+ @display.show_start(@prompt)
41
+
42
+ loop do
43
+ break if should_stop_loop?
44
+
45
+ @iteration_number += 1
46
+ @metrics.new_iteration
47
+ @display.show_iteration_start
48
+
49
+ run_iteration
50
+
51
+ @display.show_iteration_end
41
52
  end
42
53
 
43
- Output::Banner.call(self)
44
-
45
- # |..................................................|
46
- # |--------------------------------------------------|
47
- # |==================================================|
48
- # |**************************************************|
49
- # |##################################################|
50
- # | Main loop |
51
- # |##################################################|
52
- # |**************************************************|
53
- # |==================================================|
54
- # |--------------------------------------------------|
55
- # |..................................................|
56
- #
57
- # © 2026 Nathan K.
58
- # Honestly, I've no idea where this graphic
59
- # wonder came from. It's MIT lisenced now, thought.
60
-
61
- setup_signal_handler
54
+ @display.show_summary
55
+ @completed
56
+ end
62
57
 
63
- loop do
64
- if @config.stopping
65
- break
66
- elsif max_iterations_reached?
67
- Output::MaxIterationsReached.call(self)
68
- @state.clear
69
- break
70
- else
71
- Output::Iteration::Header.call(self)
72
-
73
- iteration = Iteration.new(self)
74
- result = iteration.run
75
- should_continue = process_result(result, iteration)
76
-
77
- if should_continue
78
- @state.iteration += 1
79
- @state.save
80
- sleep 1
81
- else
82
- break
83
- end
84
- end
58
+ # Total elapsed wall-clock seconds since the loop started
59
+ def elapsed_seconds
60
+ if @started_at
61
+ now_seconds - @started_at
62
+ else
63
+ 0.0
85
64
  end
86
65
  end
87
66
 
88
67
  private
89
68
 
90
- def process_result(result, iteration)
91
- unless result.error?
92
- Output::Iteration::Summary.call(self, result)
69
+ def should_stop_loop?
70
+ if @completed
71
+ true
72
+ elsif @max_iterations && @iteration_number >= @max_iterations
73
+ @display.show_termination("max iterations reached (#{@max_iterations})")
74
+ true
75
+ elsif @duration_limit && elapsed_seconds >= @duration_limit
76
+ @display.show_termination("duration limit reached (#{@duration_limit}s)")
77
+ true
78
+ else
79
+ false
80
+ end
81
+ end
82
+
83
+ def run_iteration
84
+ iteration_started_at = now_seconds
85
+ agent = Opencode.new(model: @model)
86
+ full_prompt = build_prompt
87
+
88
+ agent.run(full_prompt) do |event|
89
+ @metrics.process(event)
90
+ @display.show_event(event)
91
+
92
+ if event.is_a?(Events::Text)
93
+ check_completion(event.text)
93
94
  end
94
95
 
95
- case result.status
96
- when :fatal
97
- @history.record(
98
- state_iteration: @state.iteration,
99
- iteration_start: iteration.iteration_start,
100
- result: result,
101
- struggle_indicators: iteration.struggle_indicators
102
- )
103
- Output::PluginError.call
104
- @state.clear
105
- exit 1
106
-
107
- when :failed
108
- @history.record(
109
- state_iteration: @state.iteration,
110
- iteration_start: iteration.iteration_start,
111
- result: result,
112
- struggle_indicators: iteration.struggle_indicators
113
- )
114
- Output::NonzeroExitWarning.call(self, result)
115
-
116
- if @config.tasks_mode
117
- if check_completion(result.combined_output, @config.task_promise)
118
- Output::TaskCompletion.call(self)
119
- end
120
- end
121
-
122
- when :completed
123
- @history.record(
124
- state_iteration: @state.iteration,
125
- iteration_start: iteration.iteration_start,
126
- result: result,
127
- struggle_indicators: iteration.struggle_indicators
128
- )
129
-
130
- if @state.iteration >= @config.min_iterations
131
- Output::CompletionDetected.call(self)
132
- @state.clear
133
- Storage::History.clear_history
134
- @context.clear
135
- end
136
-
137
- when :continuing
138
- @history.record(
139
- state_iteration: @state.iteration,
140
- iteration_start: iteration.iteration_start,
141
- result: result,
142
- struggle_indicators: iteration.struggle_indicators
143
- )
144
-
145
- if @state.iteration > 2 && iteration.struggling?
146
- Output::StruggleWarning.call(self)
147
- end
148
-
149
- if @config.tasks_mode
150
- if check_completion(result.combined_output, @config.task_promise)
151
- Output::TaskCompletion.call(self)
152
- end
153
- end
154
-
155
- if iteration.context_at_start.present?
156
- Output::ContextConsumed.call
157
- iteration.context_at_start.clear
158
- end
159
-
160
- when :error
161
- @history.record_error(
162
- state_iteration: @state.iteration,
163
- iteration_start: iteration.iteration_start,
164
- error: result.errors.first || "Unknown error"
165
- )
96
+ if should_cancel_iteration?(iteration_started_at)
97
+ @display.show_iteration_cancelled(cancel_reason(iteration_started_at))
98
+ agent.cancel
99
+ break
166
100
  end
101
+ end
102
+ end
167
103
 
168
- result.status != :completed || @state.iteration < @config.min_iterations
104
+ def should_cancel_iteration?(iteration_started_at)
105
+ if @max_context && @metrics.current_context >= @max_context
106
+ true
107
+ elsif @duration_limit && elapsed_seconds >= @duration_limit
108
+ true
109
+ else
110
+ false
169
111
  end
112
+ end
170
113
 
171
- def max_iterations_reached?
172
- @config.max_iterations > 0 && @state.iteration > @config.max_iterations
114
+ def cancel_reason(iteration_started_at)
115
+ if @max_context && @metrics.current_context >= @max_context
116
+ "context limit reached (#{@metrics.current_context}/#{@max_context})"
117
+ elsif @duration_limit && elapsed_seconds >= @duration_limit
118
+ "duration limit reached"
119
+ else
120
+ "unknown"
173
121
  end
122
+ end
174
123
 
175
- def setup_signal_handler
176
- Signal.trap('INT') do
177
- if @config.stopping
178
- warn "\nForce stopping..."
179
- exit 1
180
- end
181
- @config.stopping = true
182
- warn "\nGracefully stopping Ralph loop..."
183
- if @config.current_pid
184
- begin
185
- Process.kill('TERM', @config.current_pid)
186
- rescue StandardError
187
- # process may have exited
188
- end
189
- end
190
- @state.clear
191
- warn 'Loop cancelled.'
192
- exit 0
193
- end
124
+ def check_completion(text)
125
+ if text && text.include?(@completion_string)
126
+ @completed = true
194
127
  end
128
+ end
129
+
130
+ def build_prompt
131
+ system_instructions = format(SYSTEM_PROMPT, completion: @completion_string)
132
+ "#{system_instructions}\n\n---\n\n#{@prompt}"
133
+ end
134
+
135
+ def now_seconds
136
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
137
+ end
195
138
  end
196
139
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ralph
4
+ # Tracks token usage and context size from opencode JSON stream events.
5
+ #
6
+ # Context formula per step: input + cache.read + cache.write
7
+ # Each step's cache.read ~= previous step's (cache.read + cache.write).
8
+ class Metrics
9
+ attr_reader :steps, :iteration_steps, :total_input_tokens, :total_output_tokens
10
+
11
+ def initialize
12
+ @steps = []
13
+ @iteration_steps = []
14
+ @total_input_tokens = 0
15
+ @total_output_tokens = 0
16
+ @iteration_count = 0
17
+ end
18
+
19
+ # Process a parsed event. Only StepFinish events carry token data.
20
+ def process(event)
21
+ if event.is_a?(Events::StepFinish)
22
+ record = {
23
+ input: event.input_tokens,
24
+ output: event.output_tokens,
25
+ reasoning: event.reasoning_tokens,
26
+ cache_read: event.cache_read,
27
+ cache_write: event.cache_write,
28
+ context: event.context_size,
29
+ timestamp: event.timestamp
30
+ }
31
+ @steps << record
32
+ @iteration_steps << record
33
+ @total_input_tokens += event.input_tokens
34
+ @total_output_tokens += event.output_tokens
35
+ end
36
+ end
37
+
38
+ # Current context size from the most recent step_finish
39
+ def current_context
40
+ if @steps.any?
41
+ @steps.last[:context]
42
+ else
43
+ 0
44
+ end
45
+ end
46
+
47
+ # Total tokens consumed across all steps (input + output)
48
+ def tokens_consumed
49
+ @steps.sum { |step| step[:input] + step[:output] + step[:cache_read] + step[:cache_write] }
50
+ end
51
+
52
+ # Tokens consumed in the current iteration only
53
+ def iteration_tokens
54
+ @iteration_steps.sum { |step| step[:input] + step[:output] + step[:cache_read] + step[:cache_write] }
55
+ end
56
+
57
+ # Number of LLM steps completed
58
+ def step_count
59
+ @steps.length
60
+ end
61
+
62
+ # Signal a new iteration -- resets per-iteration tracking
63
+ def new_iteration
64
+ @iteration_count += 1
65
+ @iteration_steps = []
66
+ end
67
+
68
+ # Context growth rate: average tokens added per step
69
+ def context_growth_rate
70
+ if @steps.length >= 2
71
+ first_context = @steps.first[:context]
72
+ last_context = @steps.last[:context]
73
+ (last_context - first_context).to_f / (@steps.length - 1)
74
+ else
75
+ 0.0
76
+ end
77
+ end
78
+
79
+ # Reset all metrics (used for full restart)
80
+ def reset
81
+ @steps = []
82
+ @iteration_steps = []
83
+ @total_input_tokens = 0
84
+ @total_output_tokens = 0
85
+ @iteration_count = 0
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ralph
4
+ # Wraps the opencode CLI to run headless agent sessions with JSON stream output.
5
+ #
6
+ # Spawns `opencode run` as a subprocess and yields parsed events line-by-line
7
+ # from the JSON stream. Supports cancellation via Process.kill.
8
+ class Opencode
9
+ attr_reader :model, :agent, :pid
10
+
11
+ def initialize(model: nil, agent: nil)
12
+ @model = model
13
+ @agent = agent
14
+ @pid = nil
15
+ @process_thread = nil
16
+ end
17
+
18
+ # Run opencode with the given prompt. Yields each parsed Event to the block.
19
+ # Returns the exit status of the opencode process.
20
+ def run(prompt, &block)
21
+ command = build_command(prompt)
22
+ @pid = nil
23
+
24
+ Open3.popen3(*command) do |stdin, stdout, stderr, wait_thread|
25
+ @pid = wait_thread.pid
26
+ stdin.close
27
+
28
+ stdout.each_line do |line|
29
+ line.strip!
30
+ if line.length > 0
31
+ Events.parse(line).then do |event|
32
+ block.call(event) if event
33
+ end
34
+ end
35
+ end
36
+
37
+ wait_thread.value
38
+ end
39
+ end
40
+
41
+ # Send SIGTERM to the running opencode process.
42
+ def cancel
43
+ if @pid
44
+ Process.kill("TERM", @pid)
45
+ end
46
+ rescue Errno::ESRCH
47
+ # Process already exited -- nothing to do
48
+ end
49
+
50
+ # Check whether opencode is available on the system PATH.
51
+ def self.available?
52
+ system("which opencode > /dev/null 2>&1")
53
+ end
54
+
55
+ private
56
+
57
+ def build_command(prompt)
58
+ command = ["opencode", "run"]
59
+ command.push("--model", @model) if @model
60
+ command.push("--agent", @agent) if @agent
61
+ command.push("--format", "json")
62
+ command.push(prompt)
63
+ command
64
+ end
65
+ end
66
+ end
data/lib/ralph/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ralph
4
- VERSION = "1.2.435535439"
4
+ VERSION = "2.0.0"
5
5
  end
data/lib/ralph.rb CHANGED
@@ -60,8 +60,5 @@ module Ralph
60
60
  # ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣤⣄⣀⣀⣈⣉⠉⠉⠉⠉⠙⠛⠛⠛⠛⠛⠉⠉⣀⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
61
61
  end
62
62
 
63
- # Helpers must load first since other modules extend it at parse time
64
- require_relative "ralph/helpers"
65
-
66
63
  # Require everything else by globbing, because I'm too lazy to do anything else
67
64
  Dir[File.join(__dir__, "ralph", "**", "*.rb")].sort.each { |f| require f }
@@ -0,0 +1,120 @@
1
+ # Ralph.rb Complete Implementation Plan
2
+
3
+ This is the master plan that coordinates all individual component implementations into a cohesive system.
4
+
5
+ ## Phase 1: Foundation Setup
6
+ - [ ] Verify Ruby development environment and dependencies
7
+ - [ ] Set up project structure following Ruby conventions
8
+ - [ ] Create basic gemspec and package configuration
9
+ - [ ] Set up testing framework (RSpec or Minitest)
10
+ - [ ] Configure RuboCop style checking
11
+ - [ ] Create basic CI/CD configuration files
12
+
13
+ ## Phase 2: Core Infrastructure (Parallel Development)
14
+ - [ ] **CLI Implementation** (Plan 01)
15
+ - [ ] Basic executable and argument parsing
16
+ - [ ] Option validation and error handling
17
+ - [ ] Unix pipe support implementation
18
+ - [ ] **Metrics Foundation** (Plan 04)
19
+ - [ ] Event parsing infrastructure
20
+ - [ ] JSON stream processing
21
+ - [ ] Basic token calculation logic
22
+ - [ ] **Agent Integration Foundation** (Plan 03)
23
+ - [ ] Opencode wrapper class
24
+ - [ ] Process management basics
25
+ - [ ] JSON stream consumption
26
+
27
+ ## Phase 3: Loop Integration Core
28
+ - [ ] **Loop Implementation** (Plan 02)
29
+ - [ ] Basic loop architecture
30
+ - [ ] Iteration management system
31
+ - [ ] Context guard implementation
32
+ - [ ] **Component Integration**
33
+ - [ ] Metrics → Loop interface for context monitoring
34
+ - [ ] Loop → Agents interface for iteration control
35
+ - [ ] CLI → Loop interface for parameter passing
36
+
37
+ ## Phase 4: Advanced Features Integration
38
+ - [ ] **Complete Agent Integration**
39
+ - [ ] Full opencode command execution
40
+ - [ ] Event forwarding to Metrics
41
+ - [ ] Process lifecycle management
42
+ - [ ] **Advanced Metrics**
43
+ - [ ] Real-time monitoring interface
44
+ - [ ] Threshold-based alerting
45
+ - [ ] Historical data tracking
46
+ - [ ] **Loop Termination Logic**
47
+ - [ ] Completion string detection
48
+ - [ ] Time and iteration limits
49
+ - [ ] Context-based cancellation
50
+
51
+ ## Phase 5: Display and User Experience
52
+ - [ ] Real-time progress display implementation
53
+ - [ ] Token usage visualization
54
+ - [ ] Error message formatting and display
55
+ - [ ] Help system and documentation
56
+ - [ ] Debug and verbose output modes
57
+
58
+ ## Phase 6: Testing and Quality Assurance
59
+ - [ ] **Unit Testing**
60
+ - [ ] CLI argument parsing tests
61
+ - [ ] Metrics calculation tests
62
+ - [ ] Loop logic tests
63
+ - [ ] Agent integration tests
64
+ - [ ] **Integration Testing**
65
+ - [ ] End-to-end workflow tests
66
+ - [ ] Error scenario testing
67
+ - [ ] Performance testing
68
+ - [ ] **Style and Quality**
69
+ - [ ] RuboCop compliance verification
70
+ - [ ] Code coverage requirements
71
+ - [ ] Documentation completeness
72
+
73
+ ## Phase 7: Documentation and Deployment
74
+ - [ ] Complete README with usage examples
75
+ - [ ] API documentation for all components
76
+ - [ ] Installation and setup guides
77
+ - [ ] Troubleshooting and FAQ sections
78
+ - [ ] Release notes and changelog
79
+
80
+ ## Phase 8: Performance Optimization
81
+ - [ ] Profile and optimize hot paths
82
+ - [ ] Memory usage optimization
83
+ - [ ] JSON stream parsing performance
84
+ - [ ] Concurrent processing optimizations
85
+ - [ ] Resource cleanup and garbage collection
86
+
87
+ ## Critical Success Criteria
88
+ - [ ] All four component plans completed successfully
89
+ - [ ] Integration between components works seamlessly
90
+ - [ ] Real-time monitoring meets performance requirements
91
+ - [ ] Unix philosophy and CLI design principles followed
92
+ - [ ] Error handling is comprehensive and user-friendly
93
+ - [ ] Code quality meets Ruby style standards
94
+ - [ ] Test coverage exceeds 90%
95
+ - [ ] Documentation is complete and accurate
96
+
97
+ ## Risk Mitigation
98
+ - [ ] Component integration points identified and tested early
99
+ - [ ] Fallback mechanisms for opencode CLI failures
100
+ - [ ] Graceful degradation for partial system failures
101
+ - [ ] Comprehensive logging for troubleshooting
102
+ - [ ] Modular design allows component replacement/upgrades
103
+
104
+ ## Dependencies and External Requirements
105
+ - [ ] Opencode CLI installed and accessible
106
+ - [ ] Ruby 3.0+ with standard library
107
+ - [ ] Unix-like environment for pipe support
108
+ - [ ] Sufficient memory for JSON stream processing
109
+ - [ ] Network access for opencode API communication
110
+
111
+ ## Final Verification Checklist
112
+ - [ ] CLI accepts all specified options correctly
113
+ - [ ] Loop manages iterations and context properly
114
+ - [ ] Agents communicate with opencode reliably
115
+ - [ ] Metrics provide accurate real-time data
116
+ - [ ] Complete workflow functions end-to-end
117
+ - [ ] All error scenarios handled gracefully
118
+ - [ ] Performance meets real-time requirements
119
+ - [ ] Documentation enables easy adoption
120
+ - [ ] Code quality and tests pass all checks
@@ -0,0 +1,53 @@
1
+ # Ralph.rb CLI Implementation Plan
2
+
3
+ This plan outlines the phased implementation of the Ralph.rb command-line interface based on the CLI specification in `specs/cli.md`.
4
+
5
+ ## Phase 1: Core CLI Infrastructure
6
+ - [ ] Create `exe/ralph` executable with proper shebang and permissions
7
+ - [ ] Set up basic argument parsing using Ruby's OptionParser or similar
8
+ - [ ] Implement standard Unix-style pipe support for stdin input
9
+ - [ ] Add basic help/version output functionality
10
+ - [ ] Set up project structure with lib/ directory and main.rb entry point
11
+
12
+ ## Phase 2: Command Line Options Implementation
13
+ - [ ] Implement `--model` option for model selection validation
14
+ - [ ] Add `--max-iterations` parameter with integer validation
15
+ - [ ] Implement `--duration` option with time parsing (seconds/minutes)
16
+ - [ ] Add `--max-context` option with integer validation
17
+ - [ ] Implement `--completion` option for completion string detection
18
+ - [ ] Add validation for required vs optional parameters
19
+
20
+ ## Phase 3: Input Processing and Validation
21
+ - [ ] Implement stdin reading with proper encoding handling
22
+ - [ ] Create input validation for prompt files vs inline prompts
23
+ - [ ] Add error handling for malformed input or missing files
24
+ - [ ] Implement argument combination logic (stdin + args)
25
+ - [ ] Add debug mode for troubleshooting input processing
26
+
27
+ ## Phase 4: Integration Preparation
28
+ - [ ] Create interface stubs for Loop, Agent, and Metrics components
29
+ - [ ] Implement parameter passing to core loop system
30
+ - [ ] Add configuration management and defaults
31
+ - [ ] Create error handling and user-friendly error messages
32
+ - [ ] Implement logging/verbosity levels if needed
33
+
34
+ ## Phase 5: Testing and Validation
35
+ - [ ] Write unit tests for CLI argument parsing
36
+ - [ ] Test pipe functionality with various input sources
37
+ - [ ] Validate error handling for invalid inputs
38
+ - [ ] Test integration with other components (mock versions)
39
+ - [ ] Add integration tests for complete CLI workflow
40
+
41
+ ## Verification Criteria
42
+ - [ ] CLI accepts all specified options with proper validation
43
+ - [ ] Unix pipe functionality works correctly (`cat file | ralph`)
44
+ - [ ] Error messages are clear and helpful
45
+ - [ ] All options pass parameters correctly to core system
46
+ - [ ] Help documentation is comprehensive and accurate
47
+ - [ ] No Ruby style violations (run `bin/rubocop`)
48
+ - [ ] All tests pass (run `bin/test`)
49
+
50
+ ## Dependencies
51
+ - Requires Loop, Agents, and Metrics components to be implemented
52
+ - Needs Ruby OptionParser or equivalent for argument handling
53
+ - Integration with opencil CLI for agent execution