ralph.rb 1.2.435535439

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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/gem-push.yml +47 -0
  3. data/.gitignore +79 -0
  4. data/.rubocop.yml +6018 -0
  5. data/.ruby-version +1 -0
  6. data/AGENTS.md +113 -0
  7. data/Gemfile +11 -0
  8. data/LICENSE +21 -0
  9. data/README.md +656 -0
  10. data/bin/rubocop +8 -0
  11. data/bin/test +5 -0
  12. data/exe/ralph +8 -0
  13. data/lib/ralph/agents/base.rb +132 -0
  14. data/lib/ralph/agents/claude_code.rb +24 -0
  15. data/lib/ralph/agents/codex.rb +25 -0
  16. data/lib/ralph/agents/open_code.rb +30 -0
  17. data/lib/ralph/agents.rb +24 -0
  18. data/lib/ralph/cli.rb +222 -0
  19. data/lib/ralph/config.rb +40 -0
  20. data/lib/ralph/git/file_snapshot.rb +60 -0
  21. data/lib/ralph/helpers.rb +76 -0
  22. data/lib/ralph/iteration.rb +220 -0
  23. data/lib/ralph/loop.rb +196 -0
  24. data/lib/ralph/output/active_loop_error.rb +13 -0
  25. data/lib/ralph/output/banner.rb +29 -0
  26. data/lib/ralph/output/completion_deferred.rb +12 -0
  27. data/lib/ralph/output/completion_detected.rb +17 -0
  28. data/lib/ralph/output/config_summary.rb +31 -0
  29. data/lib/ralph/output/context_consumed.rb +11 -0
  30. data/lib/ralph/output/iteration.rb +45 -0
  31. data/lib/ralph/output/max_iterations_reached.rb +16 -0
  32. data/lib/ralph/output/no_plugin_warning.rb +14 -0
  33. data/lib/ralph/output/nonzero_exit_warning.rb +11 -0
  34. data/lib/ralph/output/plugin_error.rb +12 -0
  35. data/lib/ralph/output/status.rb +176 -0
  36. data/lib/ralph/output/struggle_warning.rb +18 -0
  37. data/lib/ralph/output/task_completion.rb +12 -0
  38. data/lib/ralph/output/tasks_file_created.rb +11 -0
  39. data/lib/ralph/prompt_template.rb +183 -0
  40. data/lib/ralph/storage/context.rb +58 -0
  41. data/lib/ralph/storage/history.rb +117 -0
  42. data/lib/ralph/storage/state.rb +178 -0
  43. data/lib/ralph/storage/tasks.rb +244 -0
  44. data/lib/ralph/threads/heartbeat.rb +44 -0
  45. data/lib/ralph/threads/stream_reader.rb +50 -0
  46. data/lib/ralph/version.rb +5 -0
  47. data/lib/ralph.rb +67 -0
  48. data/original/bin/ralph.js +13 -0
  49. data/original/ralph.ts +1706 -0
  50. data/ralph.gemspec +35 -0
  51. data/ralph2.gemspec +35 -0
  52. data/screenshot.webp +0 -0
  53. data/specs/README.md +46 -0
  54. data/specs/agents.md +172 -0
  55. data/specs/cli.md +223 -0
  56. data/specs/iteration.md +173 -0
  57. data/specs/output.md +104 -0
  58. data/specs/storage/local-data-structure.md +246 -0
  59. data/specs/tasks.md +295 -0
  60. metadata +150 -0
data/specs/output.md ADDED
@@ -0,0 +1,104 @@
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.
@@ -0,0 +1,246 @@
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
data/specs/tasks.md ADDED
@@ -0,0 +1,295 @@
1
+ # Task Management Specification
2
+
3
+ Task management lets Ralph break large projects into discrete units of work tracked in a markdown file. Instead of sending the entire project goal every iteration, Ralph focuses the agent on one task at a time, advancing through the list as each task is completed. This reduces per-iteration context size, improves agent focus, and lowers cost.
4
+
5
+ ## File Format
6
+
7
+ Tasks are stored in `.ralph/ralph-tasks.md`. The file uses markdown checkbox syntax with three status characters.
8
+
9
+ ### Status Characters
10
+
11
+ | Character | Symbol | Status | Meaning |
12
+ |-----------|--------|--------|---------|
13
+ | ` ` (space) | `[ ]` | `:todo` | Not started |
14
+ | `/` | `[/]` | `:in_progress` | Currently being worked on |
15
+ | `x` | `[x]` | `:complete` | Finished |
16
+
17
+ ### Structure
18
+
19
+ ```markdown
20
+ # Ralph Tasks
21
+
22
+ - [ ] First task description
23
+ - [/] Second task (in progress)
24
+ - [ ] Subtask A
25
+ - [x] Subtask B
26
+ - [x] Third task (complete)
27
+ ```
28
+
29
+ **Rules:**
30
+
31
+ - Top-level tasks start at column 0 with `- [<status>] <text>`.
32
+ - Subtasks are indented with two spaces followed by the same `- [<status>] <text>` pattern.
33
+ - Subtasks are **single-level only** — no nesting subtasks under subtasks.
34
+ - The `# Ralph Tasks` header is written on save but not required for parsing.
35
+ - Lines that do not match the task pattern are ignored during parsing.
36
+
37
+ ### Round-Trip Preservation
38
+
39
+ Each `Task` stores its `original_line` (the raw line from disk). This is used for diagnostics and debugging. On save, the canonical `- [<status>] <text>` format is always written, so whitespace and formatting are normalized.
40
+
41
+ ## Data Models
42
+
43
+ ### `Ralph::Storage::Task`
44
+
45
+ Represents a single task (top-level or subtask).
46
+
47
+ | Attribute | Type | Description |
48
+ |-----------|------|-------------|
49
+ | `text` | `String` | Task description text |
50
+ | `status` | `Symbol` | One of `:todo`, `:in_progress`, `:complete` |
51
+ | `subtasks` | `Array<Task>` | Child tasks (empty for subtasks themselves) |
52
+ | `original_line` | `String` or `nil` | Raw line from disk, for diagnostics |
53
+
54
+ **Status query methods:**
55
+
56
+ - `todo?` — true when status is `:todo`
57
+ - `in_progress?` — true when status is `:in_progress`
58
+ - `complete?` — true when status is `:complete`
59
+
60
+ **Status mutation methods:**
61
+
62
+ - `mark_todo!` — sets status to `:todo`
63
+ - `mark_in_progress!` — sets status to `:in_progress`
64
+ - `mark_complete!` — sets status to `:complete`
65
+ - `toggle_status` — cycles `:todo` → `:in_progress` → `:complete` → `:todo`
66
+
67
+ **Serialization:**
68
+
69
+ `to_s` returns the canonical line: `- [<status_char>] <text>`
70
+
71
+ ### `Ralph::Storage::TasksCollection`
72
+
73
+ An `Enumerable` collection of top-level `Task` objects.
74
+
75
+ | Method | Signature | Description |
76
+ |--------|-----------|-------------|
77
+ | `each` | `(&block)` | Yields each top-level task |
78
+ | `empty?` | `-> Boolean` | True when collection has no tasks |
79
+ | `length` | `-> Integer` | Number of top-level tasks |
80
+ | `any?` | `-> Boolean` | True when collection has at least one task |
81
+ | `add` | `(Task) -> self` | Appends a task to the collection |
82
+ | `remove_at` | `(Integer) -> Task` | Removes task at 1-based index; raises `IndexError` if out of range |
83
+ | `current` | `-> Task or nil` | First task with `:in_progress` status |
84
+ | `next` | `-> Task or nil` | First task with `:todo` status |
85
+ | `all_complete?` | `-> Boolean` | True when non-empty and every task is `:complete` |
86
+
87
+ **Class methods:**
88
+
89
+ - `TasksCollection.parse(content)` — Parses a markdown string into a `TasksCollection`. Handles top-level tasks and single-level subtasks. Returns a new collection (empty if no tasks found).
90
+
91
+ **Display:**
92
+
93
+ - `display_with_indices` — Prints a numbered list of tasks with status icons to stdout. Used by the `--list-tasks` subcommand.
94
+
95
+ **Status icons** (for display only):
96
+
97
+ | Status | Icon |
98
+ |--------|------|
99
+ | `:complete` | `✅` |
100
+ | `:in_progress` | `🔄` |
101
+ | `:todo` | `⏸️` |
102
+
103
+ ## Storage
104
+
105
+ ### `Ralph::Storage::Tasks`
106
+
107
+ Manages the task file on disk. Always instantiate with `Tasks.new`.
108
+
109
+ | Method | Signature | Description |
110
+ |--------|-----------|-------------|
111
+ | `load_tasks` | `-> TasksCollection or nil` | Reads and parses `.ralph/ralph-tasks.md`. Returns `nil` if the file does not exist or cannot be parsed. |
112
+ | `save_tasks` | `(TasksCollection) -> void` | Writes the collection to `.ralph/ralph-tasks.md` with the `# Ralph Tasks` header. Writes top-level tasks and their subtasks. |
113
+ | `clear_tasks` | `-> void` | Deletes the tasks file if it exists. Silently ignores errors. |
114
+ | `tasks_exist?` | `-> Boolean` | True when `.ralph/ralph-tasks.md` exists on disk. |
115
+ | `add_task` | `(String) -> Task` | Loads existing tasks (or creates a new collection), appends a new `:todo` task, saves, and returns the new task. |
116
+ | `remove_task` | `(Integer) -> Task` | Removes the task at the given 1-based index. Raises `RuntimeError` if no tasks file exists. Raises `IndexError` if the index is out of range. |
117
+ | `initialize_tasks_file` | `-> String` | Creates a starter tasks file with a header and usage hint. Returns the file path. |
118
+
119
+ **Class methods:**
120
+
121
+ - `Tasks.dir` — Returns the `.ralph/` directory path (relative to `Dir.pwd`).
122
+ - `Tasks.path` — Returns the full path to `ralph-tasks.md`.
123
+
124
+ **Directory management:** The constructor creates `.ralph/` if it does not exist.
125
+
126
+ ## Task Lifecycle in the Loop
127
+
128
+ When `--tasks` (or `-t`) is enabled, the loop coordinates task progression across iterations.
129
+
130
+ ### Task Selection
131
+
132
+ Each iteration, the prompt builder determines the current focus:
133
+
134
+ 1. **Find in-progress task:** Look for the first task with status `[/]` (`:in_progress`). If found, this is the current task.
135
+ 2. **Find next todo task:** If no in-progress task, look for the first task with status `[ ]` (`:todo`). This becomes the next task to start.
136
+ 3. **All complete:** If every task is `[x]` (`:complete`), instruct the agent to output the completion promise.
137
+ 4. **No tasks:** If the collection is empty, instruct the agent to add tasks.
138
+
139
+ ### Status Transitions
140
+
141
+ The agent is responsible for updating task statuses in `.ralph/ralph-tasks.md` directly. The prompt instructs the agent to follow this workflow:
142
+
143
+ ```
144
+ [ ] todo → [/] in progress → [x] complete
145
+ ```
146
+
147
+ 1. Before starting work: mark the task as `[/]` in the file.
148
+ 2. After verifying the task is done: mark it as `[x]` in the file.
149
+ 3. Output `<promise>READY_FOR_NEXT_TASK</promise>` to signal task completion.
150
+
151
+ Ralph does not programmatically modify task statuses between iterations. The agent owns the file.
152
+
153
+ ### Task Promise Detection
154
+
155
+ When the loop detects `<promise>READY_FOR_NEXT_TASK</promise>` (or the value of `--task-promise`) in agent output:
156
+
157
+ - The loop prints a task completion message via `Output::TaskCompletion`.
158
+ - The next iteration's prompt will pick up the updated task file, find the next `[ ]` task, and continue.
159
+
160
+ The task promise does **not** stop the loop. It is informational — the loop continues to the next iteration.
161
+
162
+ ### Completion Promise in Tasks Mode
163
+
164
+ The completion promise (`<promise>COMPLETE</promise>` by default) is only accepted when the agent has signaled that all tasks are done. The prompt instructs the agent to only output the completion promise when every task in the file is `[x]`.
165
+
166
+ ### Tasks File Creation
167
+
168
+ When `--tasks` is enabled and no tasks file exists:
169
+
170
+ - The prompt builder includes instructions telling the agent to create the file or use `ralph --add-task`.
171
+ - Alternatively, the user can pre-populate the file with `ralph --add-task "description"` before starting the loop.
172
+ - `initialize_tasks_file` creates a starter file with a header and usage hint.
173
+
174
+ ## Prompt Integration
175
+
176
+ The `Prompt` class builds task-aware prompts when `state.tasks_mode` is true.
177
+
178
+ ### Task-Mode Prompt Structure
179
+
180
+ ```
181
+ # Ralph Wiggum Loop - Iteration N
182
+
183
+ You are in an iterative development loop working through a task list.
184
+
185
+ [Additional Context section, if present]
186
+
187
+ ## TASKS MODE: Working through task list
188
+
189
+ Current tasks from .ralph/ralph-tasks.md:
190
+ ```markdown
191
+ [full contents of ralph-tasks.md]
192
+ ```
193
+
194
+ [Task instructions — one of four states]
195
+
196
+ ### Task Workflow
197
+ 1. Find any task marked [/] (in progress). If none, pick the first [ ] task.
198
+ 2. Mark the task as [/] in ralph-tasks.md before starting.
199
+ 3. Complete the task.
200
+ 4. Mark as [x] when verified complete.
201
+ 5. Output <promise>READY_FOR_NEXT_TASK</promise> to move to the next task.
202
+ 6. Only output <promise>COMPLETE</promise> when ALL tasks are [x].
203
+
204
+ ---
205
+
206
+ ## Your Main Goal
207
+
208
+ [user's prompt text]
209
+
210
+ ## Critical Rules
211
+
212
+ [rules about one task at a time, promise usage, honesty]
213
+
214
+ ## Current Iteration: N / M (min: K)
215
+
216
+ Tasks Mode: ENABLED - Work on one task at a time from ralph-tasks.md
217
+ ```
218
+
219
+ ### Four Task Instruction States
220
+
221
+ The prompt builder selects one instruction block based on the current task state:
222
+
223
+ | State | Condition | Instruction |
224
+ |-------|-----------|-------------|
225
+ | Current task exists | `tasks.current` returns a task | Focus on completing the in-progress task. When done, mark as `[x]` and output the task promise. |
226
+ | Next task available | No in-progress task, `tasks.next` returns a task | Mark the next task as `[/]` before starting. When done, mark as `[x]` and output the task promise. |
227
+ | All tasks complete | `tasks.all_complete?` is true | All tasks done. Output the completion promise to finish. |
228
+ | No tasks found | Collection is empty | Add tasks to the file or use `ralph --add-task`. |
229
+
230
+ ### Error State
231
+
232
+ If the tasks file cannot be read (parse error, permissions, etc.), the prompt builder falls back to a short error message:
233
+
234
+ ```
235
+ ## TASKS MODE: Error reading tasks file
236
+
237
+ Unable to read .ralph/ralph-tasks.md
238
+ ```
239
+
240
+ ## CLI Subcommands
241
+
242
+ These subcommands are fire-and-exit — they perform their action and exit without starting the loop. See also [cli.md](./cli.md) for the full CLI specification.
243
+
244
+ ### `--list-tasks`
245
+
246
+ Loads and displays tasks from `.ralph/ralph-tasks.md` with numbered indices and status icons.
247
+
248
+ - If no tasks file exists, prints a message and exits.
249
+ - Uses `TasksCollection#display_with_indices`.
250
+
251
+ ### `--add-task DESC`
252
+
253
+ Appends a new `- [ ] DESC` task to the file.
254
+
255
+ - Creates the file with a `# Ralph Tasks` header if it does not exist.
256
+ - Requires a non-empty description argument.
257
+
258
+ ### `--remove-task N`
259
+
260
+ Removes the task at 1-based index N, including its subtasks.
261
+
262
+ - Requires an integer argument.
263
+ - Validates the index is in range `1..task_count`.
264
+ - Raises if no tasks file exists.
265
+
266
+ ## Consumers
267
+
268
+ | Consumer | What it uses | How |
269
+ |----------|-------------|-----|
270
+ | `Loop#run` | `check_completion` with `task_promise` | Detects `<promise>READY_FOR_NEXT_TASK</promise>` in agent output and prints task completion message |
271
+ | `Prompt#task_mode_prompt` | `Storage::Tasks`, `TasksCollection.parse` | Reads task file, determines current/next task, builds task-aware prompt |
272
+ | `Prompt#build_tasks_section` | `TasksCollection#current`, `#next`, `#all_complete?` | Selects the appropriate task instruction block |
273
+ | `CLI` | `Storage::Tasks#add_task`, `#remove_task`, `#load_tasks` | Implements `--add-task`, `--remove-task`, `--list-tasks` subcommands |
274
+ | `Output::Status` | `Storage::Tasks#load_tasks` | Displays task progress in the `--status` dashboard |
275
+ | `Output::TaskCompletion` | — | Prints task completion message when task promise is detected |
276
+ | `Output::TasksFileCreated` | — | Prints confirmation when the tasks file is initialized |
277
+
278
+ ## Testing Strategy
279
+
280
+ ### Unit Tests
281
+
282
+ - **Parsing:** Test `TasksCollection.parse` with:
283
+ - Empty string, header-only, single task, multiple tasks, subtasks, mixed statuses
284
+ - Lines that do not match the task pattern (ignored)
285
+ - Malformed status characters (treated as todo)
286
+ - **Models:** Test `Task` status queries, mutations, `toggle_status`, `to_s`
287
+ - **Collection:** Test `add`, `remove_at` (valid index, out of range), `current`, `next`, `all_complete?`, `empty?`
288
+ - **Storage:** Test `load_tasks`, `save_tasks` round-trip, `add_task`, `remove_task`, `clear_tasks`, `tasks_exist?`, `initialize_tasks_file`
289
+
290
+ ### Integration Tests
291
+
292
+ - **Loop integration:** Test that task promise detection in agent output triggers `Output::TaskCompletion` and continues the loop
293
+ - **Prompt integration:** Test that the prompt builder includes the correct task instructions for each of the four task states
294
+ - **CLI subcommands:** Test `--list-tasks`, `--add-task`, `--remove-task` end-to-end with a real tasks file
295
+ - **File creation:** Test that `--tasks` mode works when no tasks file exists (agent or user creates it)