ralph.rb 1.2.4355354345 → 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/specs/iteration.md DELETED
@@ -1,173 +0,0 @@
1
- # Iteration Class Specification
2
-
3
- ## Purpose
4
-
5
- The `Iteration` class is responsible for executing a single AI agent iteration and collecting metrics. It handles the low-level execution details while delegating business logic decisions to the caller.
6
-
7
- ## Location
8
-
9
- `lib/ralph/iteration.rb`
10
-
11
- ## Dependencies
12
-
13
- ```ruby
14
- require_relative "types"
15
- require_relative "helpers"
16
- require_relative "state"
17
- ```
18
-
19
- ## Class Interface
20
-
21
- ### Constructor
22
-
23
- ```ruby
24
- def initialize(loop)
25
- ```
26
-
27
- **Parameters:**
28
- - `loop` - `Ralph::Loop` instance. Provides access to:
29
- - `loop.config` - `Ralph::Config` instance with all execution options (model, stream_output, verbose_tools, allow_all_permissions, disable_plugins, completion_promise, etc.)
30
- - `loop.agent_config` - `Ralph::Agents::Base` instance providing command, build_args, build_env, etc. (see [agents.md](./agents.md))
31
-
32
- ### Main Method
33
-
34
- ```ruby
35
- def call(prompt, iteration_start:)
36
- ```
37
-
38
- **Parameters:**
39
- - `prompt` - String containing the prompt to send to the agent
40
- - `iteration_start` - Integer timestamp (ms) when the iteration started
41
-
42
- **Returns:**
43
- - `IterationResult` struct with execution data
44
-
45
- ## Result Type
46
-
47
- ```ruby
48
- IterationResult = Struct.new(
49
- :duration_ms, # Integer - execution duration in milliseconds
50
- :exit_code, # Integer - agent process exit code
51
- :stdout_text, # String - agent stdout output
52
- :stderr_text, # String - agent stderr output
53
- :tool_counts, # Hash<String, Integer> - tool usage counts
54
- :files_modified, # Array<String> - list of files changed during iteration
55
- :completion_detected, # Boolean - whether completion promise was found
56
- :errors, # Array<String> - extracted errors from output
57
- :success # Boolean - overall success (no crashes, exit_code == 0)
58
- )
59
- ```
60
-
61
- ## Internal Responsibilities
62
-
63
- ### 1. Agent Execution
64
- - Build agent command using `agent_config.build_args` and `agent_config.build_env`
65
- - Execute agent (streaming or buffered based on options)
66
- - Capture stdout, stderr, exit code, and tool usage data
67
- - Handle agent process lifecycle
68
-
69
- ### 2. Metrics Collection
70
- - Calculate execution duration from `iteration_start`
71
- - Extract tool counts from agent output using `agent_config.parse_tool_output`
72
- - Capture file snapshots before/after execution using `State.capture_file_snapshot`
73
- - Detect files modified via `State.modified_files_since_snapshot`
74
-
75
- ### 3. Data Extraction
76
- - Combine stdout/stderr for analysis
77
- - Detect completion promise using `::Ralph::Helpers.check_completion`
78
- - Extract errors from combined output using `::Ralph::Helpers.extract_errors`
79
- - Determine overall success status
80
-
81
- ### 4. Result Packaging
82
- - Assemble all collected data into `IterationResult`
83
- - Return structured result to caller
84
-
85
- ## External Dependencies (Provided by Caller)
86
-
87
- The class expects the caller to handle:
88
- - **Prompt Building** - Creating the prompt text using `PromptBuilder`
89
- - **Output Display** - Showing iteration summaries, warnings, etc.
90
- - **History Management** - Recording iteration data in `RalphHistory`
91
- - **State Updates** - Managing loop state, iteration counters
92
- - **Completion Decisions** - Determining whether to continue or break loop
93
- - **Error Handling** - Deciding how to respond to iteration errors
94
- - **Post-Processing** - Auto-committing changes, consuming context
95
-
96
- ## Error Handling
97
-
98
- The `Iteration` class handles:
99
- - Process execution errors (captures in result)
100
- - Stream processing errors (captures in result)
101
- - File operation errors for snapshots (gracefully degrades)
102
- - Agent process cleanup if errors occur
103
-
104
- It does NOT:
105
- - Display error messages (caller responsibility)
106
- - Modify loop state (caller responsibility)
107
- - Make retry decisions (caller responsibility)
108
-
109
- ## Usage Example
110
-
111
- ```ruby
112
- # In Loop class (Loop exposes attr_reader :config, :agent_config)
113
- iteration = Iteration.new(self)
114
-
115
- result = iteration.call(full_prompt, iteration_start: start_time)
116
-
117
- # Caller processes result
118
- Output::IterationSummary.call(
119
- iteration: @state.iteration,
120
- elapsed_ms: result.duration_ms,
121
- tool_counts: result.tool_counts,
122
- exit_code: result.exit_code,
123
- completion_detected: result.completion_detected
124
- )
125
-
126
- # Caller updates history
127
- record_iteration_in_history(result)
128
-
129
- # Caller decides what to do next
130
- if result.completion_detected && @state.iteration >= @min_iterations
131
- # Handle completion
132
- end
133
- ```
134
-
135
- ## Implementation Notes
136
-
137
- ### Thread Safety
138
- The class holds a reference to the parent `Loop` instance and mutable streaming state. It is not intended for concurrent use across multiple loops.
139
-
140
- ### Performance Considerations
141
- - File snapshot operations should be efficient (git-based)
142
- - Stream processing should handle large outputs without memory issues
143
- - Tool counting should be done efficiently via line-by-line processing
144
-
145
- ### Extensibility
146
- New config values are automatically available to `Iteration` via `@loop.config` without constructor changes.
147
- The `IterationResult` struct can be extended with additional fields as needed.
148
-
149
- ## Testing Strategy
150
-
151
- ### Unit Tests
152
- - Mock `Open3` to test agent execution logic
153
- - Mock `State` methods to test file snapshot logic
154
- - Test completion detection with various output formats
155
- - Test error extraction from different types of output
156
- - Test tool counting with different agent output formats
157
-
158
- ### Integration Tests
159
- - Test with actual agent binaries (if available in test environment)
160
- - Test file snapshot operations with git repositories
161
- - Test streaming vs buffered output modes
162
- - Test error scenarios (agent crashes, git operations fail)
163
-
164
- ## Migration Path
165
-
166
- 1. Create the new `Iteration` class with this interface
167
- 2. Update `Loop` class to use `Iteration` for agent execution
168
- 3. Move iteration-related private methods from `Loop` to `Iteration`
169
- 4. Update `Loop` to process `IterationResult` instead of handling execution directly
170
- 5. Remove redundant code from `Loop` class
171
- 6. Add tests for the new `Iteration` class
172
-
173
- This refactoring will improve separation of concerns, testability, and maintainability of the iteration execution logic.
data/specs/output.md DELETED
@@ -1,104 +0,0 @@
1
- # Output Specification
2
-
3
- All terminal output in Ralph is structured as callable output classes under the `Ralph::Output` namespace. Each class encapsulates a single output concern — a banner, a warning, a summary — and exposes a `self.call` class method as its only public interface.
4
-
5
- ## Namespace & File Layout
6
-
7
- ```
8
- lib/ralph/output/
9
- no_plugin_warning.rb # Ralph::Output::NoPluginWarning
10
- banner.rb # Ralph::Output::Banner
11
- iteration.rb # Ralph::Output::Iteration::Header
12
- # Ralph::Output::Iteration::Summary
13
- # Ralph::Output::Iteration::Error
14
- ...
15
- ```
16
-
17
- Each file defines one or more classes inside `module Ralph; module Output; ... end; end`.
18
-
19
- When several output classes share a common concern (e.g. iteration lifecycle), they are grouped under a nested module in a single file. The submodule acts as a namespace — it contains only classes, no logic of its own.
20
-
21
- ## Interface: The Callable Object Pattern
22
-
23
- Every output class follows the same shape:
24
-
25
- ```ruby
26
- module Ralph
27
- module Output
28
- class ExampleWarning
29
- def self.call(thing:, count:)
30
- warn "Warning: #{thing} happened #{count} times"
31
- end
32
- end
33
- end
34
- end
35
- ```
36
-
37
- ### Rules
38
-
39
- | Rule | Detail |
40
- |------|--------|
41
- | **Entry point** | `self.call` — a class method, never instantiated |
42
- | **Arguments** | All inputs passed as **keyword arguments** for clarity at the call site |
43
- | **Return value** | None expected. These are fire-and-forget side-effect methods |
44
- | **Single responsibility** | One output concern per class. If a method prints a banner, that's one class. If it prints an iteration summary, that's another |
45
- | **Grouping** | Related output classes may be grouped under a nested module in a single file (e.g. `Output::Iteration::Header`, `Output::Iteration::Summary`, `Output::Iteration::Error` all live in `iteration.rb`) |
46
- | **No base class** | Plain `Object` inheritance. No shared superclass or included module required |
47
-
48
- ## Output Channels
49
-
50
- Use the appropriate channel based on the nature of the output:
51
-
52
- | Channel | Ruby method | When to use |
53
- |---------|-------------|-------------|
54
- | **stdout** | `puts` | Informational output: banners, summaries, progress, status |
55
- | **stderr** | `warn` or `$stderr.puts` | Warnings, errors, and diagnostics that should not pollute stdout |
56
-
57
- `warn` is preferred over `$stderr.puts` for single-line warnings because it respects Ruby's `-W` flag and is idiomatic.
58
-
59
- ## Requiring & Usage
60
-
61
- Output classes are required where they are used, not auto-loaded:
62
-
63
- ```ruby
64
- require_relative "output/no_plugin_warning"
65
-
66
- # Later, at the call site:
67
- Output::NoPluginWarning.call(chosen_agent: @agent_config.type)
68
- ```
69
-
70
- There is no `lib/ralph/output.rb` barrel file. Each class is required individually by the code that needs it.
71
-
72
- ## Reference Implementation
73
-
74
- `lib/ralph/output/no_plugin_warning.rb` is the canonical example:
75
-
76
- ```ruby
77
- module Ralph
78
- module Output
79
- class NoPluginWarning
80
- def self.call(chosen_agent:)
81
- case chosen_agent
82
- when :claude_code
83
- warn "Warning: --no-plugins has no effect with Claude Code agent"
84
- when :codex
85
- warn "Warning: --no-plugins has no effect with Codex agent"
86
- end
87
- end
88
- end
89
- end
90
- end
91
- ```
92
-
93
- Key things to note:
94
-
95
- - The class decides **what** to print based on its arguments (the `case` on `chosen_agent`).
96
- - The caller does not need to know the message text — it passes data, and the output class formats it.
97
- - The caller (`Loop#call` in `lib/ralph/loop.rb`) reads as `Output::NoPluginWarning.call(chosen_agent: ...)` — intent is clear at the call site.
98
-
99
- ## Formatting Conventions
100
-
101
- - **Box-drawing characters** (`═`, `║`, `╔`, `╗`, `╚`, `╝`, `─`) are used for prominent banners and completion boxes.
102
- - **Unicode symbols** (checkmarks, warning signs, emoji) are used sparingly for status indicators.
103
- - **Line width** is 68 characters for horizontal rules and box borders.
104
- - Formatting logic lives inside the output class, not at the call site.
@@ -1,246 +0,0 @@
1
- # Ralph Storage Architecture Specification
2
-
3
- ## Overview
4
-
5
- The Ralph autonomous agent maintains persistent state across sessions through a modular storage system. All state is stored in the `.ralph/` directory within the project root, providing persistence for autonomous coding sessions, iteration history, context management, and task tracking.
6
-
7
- ## File Structure
8
-
9
- ### Root Directory Layout
10
- ```
11
- .ralph/
12
- ├── ralph-loop.state.json # Main loop state
13
- ├── ralph-history.json # Complete iteration history
14
- ├── ralph-context.md # Persistent AI context
15
- ├── ralph-tasks.md # Task management
16
- └── ralph-opencode.config.json # OpenCode plugin configuration
17
- ```
18
-
19
- ## Storage Module Architecture
20
-
21
- ### Directory Structure
22
- ```
23
- lib/ralph/storage/
24
- ├── history.rb # Ralph::Storage::History class
25
- ├── state.rb # Ralph::Storage::State class
26
- ├── context.rb # Ralph::Storage::Context class
27
- ├── tasks.rb # Ralph::Storage::Tasks class + Task/Tasks models
28
- └── . # Main storage module file
29
- ```
30
-
31
- ### Module Organization
32
-
33
- #### Ralph::Storage::History
34
- **Purpose**: Manages complete iteration history across Ralph sessions.
35
-
36
- **Responsibilities**:
37
- - Save/load iteration history to `.ralph/ralph-history.json`
38
- - Track struggle indicators and performance metrics
39
- - Maintain chronology of all iterations
40
- - Provide history analytics
41
-
42
- **Data Structure**:
43
- ```ruby
44
- RalphHistory = Struct.new(
45
- :iterations, # Array<IterationHistory>
46
- :total_duration_ms, # Integer
47
- :struggle_indicators, # StruggleIndicators
48
- keyword_init: true
49
- )
50
-
51
- IterationHistory = Struct.new(
52
- :iteration, # Integer
53
- :started_at, # String (ISO 8601)
54
- :ended_at, # String (ISO 8601)
55
- :duration_ms, # Integer
56
- :tools_used, # Hash<String, Integer>
57
- :files_modified, # Array<String>
58
- :exit_code, # Integer
59
- :completion_detected, # Boolean
60
- :errors # Array<String>
61
- )
62
- ```
63
-
64
- **File Format**: JSON with iteration data, total duration, and struggle indicators.
65
-
66
- #### Ralph::Storage::State
67
- **Purpose**: Manages the active loop state for current Ralph session.
68
-
69
- **Responsibilities**:
70
- - Save/load loop state to `.ralph/ralph-loop.state.json`
71
- - Track session metadata (iteration count, timing, configuration)
72
- - Handle session lifecycle (start, stop, resume)
73
- - File change detection via git snapshots
74
- - OpenCode configuration management
75
-
76
- **Data Structure**:
77
- ```ruby
78
- RalphState = Struct.new(
79
- :active, # Boolean
80
- :iteration, # Integer
81
- :min_iterations, # Integer
82
- :max_iterations, # Integer
83
- :completion_promise, # String
84
- :tasks_mode, # Boolean
85
- :task_promise, # String
86
- :prompt, # String
87
- :started_at, # String (ISO 8601)
88
- :model, # String
89
- :agent, # String
90
- keyword_init: true
91
- )
92
- ```
93
-
94
- **File Format**: JSON with loop configuration and session state.
95
-
96
- **Additional Features**:
97
- - `capture_file_snapshot()` - Git-based file state tracking
98
- - `modified_files_since_snapshot()` - Change detection
99
- - `ensure_ralph_config()` - OpenCode plugin configuration
100
- - `load_plugins_from_config()` - Plugin discovery from config files
101
-
102
- #### Ralph::Storage::Context
103
- **Purpose**: Manages persistent AI context for maintaining conversational continuity.
104
-
105
- **Responsibilities**:
106
- - Load context from `.ralph/ralph-context.md`
107
- - Clear context when sessions end
108
- - Provide context for AI prompt building
109
-
110
- **File Format**: Markdown file with structured context sections.
111
-
112
- #### Ralph::Storage::Tasks
113
- **Purpose**: Manages task tracking and workflow coordination.
114
-
115
- **Responsibilities**:
116
- - Save/load tasks to `.ralph/ralph-tasks.md`
117
- - Parse and render task lists in markdown format
118
- - Track task status (todo, in_progress, complete)
119
- - Handle subtasks and task hierarchy
120
- - Provide task analytics and display methods
121
-
122
- **Data Structures**:
123
- ```ruby
124
- Task = Struct.new(
125
- :text, # String
126
- :status, # Symbol (:todo, :in_progress, :complete)
127
- :subtasks, # Array<Task>
128
- :original_line, # String (for round-trip preservation)
129
- keyword_init: true
130
- )
131
-
132
- Tasks = Enumerable collection of Task objects
133
- ```
134
-
135
- **File Format**: Markdown with checkbox syntax:
136
- ```markdown
137
- # Ralph Tasks
138
-
139
- - [ ] Task 1 description
140
- - [/] Task 2 in progress
141
- - [ ] Subtask 2.1
142
- - [x] Subtask 2.2
143
- - [x] Task 3 complete
144
- ```
145
-
146
- ## State Persistence Patterns
147
-
148
- ### Atomic Operations
149
- All write operations use atomic file writing to prevent corruption:
150
- - Write to temporary file first
151
- - Rename/replace original file atomically
152
- - Handle filesystem errors gracefully
153
-
154
- ### Error Handling
155
- - All load operations handle missing files gracefully
156
- - JSON parsing errors return empty/default states
157
- - Filesystem errors are caught and logged
158
- - State corruption recovery with fallback to empty state
159
-
160
- ### Directory Management
161
- - `.ralph/` directory created automatically when needed
162
- - Directory permissions set appropriately
163
- - Cleanup operations preserve important state files
164
-
165
- ## Configuration Integration
166
-
167
- ### OpenCode Configuration
168
- Storage::State manages OpenCode plugin configuration through:
169
- - `.ralph/ralph-opencode.config.json` - Generated config file
170
- - User config discovery: `~/.config/opencode/opencode.json`
171
- - Project config discovery: `.ralph/opencode.json` or `.opencode/opencode.json`
172
- - Plugin filtering and permission management
173
-
174
- ### Config File Format
175
- ```json
176
- {
177
- "$schema": "https://opencode.ai/config.json",
178
- "plugin": ["auth-plugin"],
179
- "permission": {
180
- "read": "allow",
181
- "edit": "allow",
182
- // ... all tool permissions
183
- }
184
- }
185
- ```
186
-
187
- ## Migration Strategy
188
-
189
- ### Backward Compatibility
190
- The original `State` module in `lib/ralph/state.rb` remains as a thin wrapper:
191
- - Requires the new Storage module
192
- - Delegates all existing methods to appropriate Storage classes
193
- - Maintains identical public interface
194
- - No functional changes to existing code
195
-
196
- ### Update Dependencies
197
- Files requiring state management will need minimal updates:
198
- - Add `require_relative "storage"` where needed
199
- - All existing `State.method` calls continue to work unchanged
200
- - Gradual migration to use Storage classes directly is optional
201
-
202
- ## Data Lifecycle
203
-
204
- ### Session Start
205
- 1. Load existing state from `.ralph/ralph-loop.state.json`
206
- 2. Check for active sessions (prevent concurrent loops)
207
- 3. Load history and context
208
- 4. Initialize new session state if needed
209
- 5. Create task file if tasks mode enabled
210
-
211
- ### During Session
212
- 1. Update state after each iteration
213
- 2. Append to history with iteration results
214
- 3. Track file changes via git snapshots
215
- 4. Update context as needed
216
-
217
- ### Session End
218
- 1. Clear active state flag
219
- 2. Save final history and state
220
- 3. Optionally clear context (based on configuration)
221
- 4. Cleanup temporary files
222
-
223
- ### Error Recovery
224
- 1. Automatic state clearing on crashes/interrupts
225
- 2. History preservation through graceful degradation
226
- 3. Context recovery from last good state
227
- 4. Task file preservation across all scenarios
228
-
229
- ## Security Considerations
230
-
231
- ### File Permissions
232
- - `.ralph/` directory permissions restricted to owner
233
- - Sensitive configuration files protected appropriately
234
- - No world-readable state files
235
-
236
- ### Data Sanitization
237
- - JSON validation on all loaded data
238
- - Markdown sanitization for context/tasks
239
- - Plugin configuration validation
240
- - Safe handling of file paths and content
241
-
242
- ### Integrity Checks
243
- - JSON schema validation for structured data
244
- - File content checksums where appropriate
245
- - Consistency checks between related files
246
- - Recovery procedures for corrupted state