spec-and-loop 1.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.
- package/QUICKSTART.md +236 -0
- package/README.md +344 -0
- package/bin/ralph-run +13 -0
- package/package.json +41 -0
- package/scripts/ralph-run.sh +751 -0
- package/scripts/setup.js +43 -0
package/QUICKSTART.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Quick Start Guide
|
|
2
|
+
|
|
3
|
+
Get up and running with **spec-and-loop** in 5 minutes!
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
Install these tools (one-time setup):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 1. Install openspec (OpenSpec CLI)
|
|
11
|
+
npm install -g openspec
|
|
12
|
+
|
|
13
|
+
# 2. Install opencode (agentic coding assistant)
|
|
14
|
+
npm install -g opencode
|
|
15
|
+
|
|
16
|
+
# 3. Install jq (command-line JSON processor)
|
|
17
|
+
# Ubuntu/Debian:
|
|
18
|
+
sudo apt install jq
|
|
19
|
+
|
|
20
|
+
# macOS:
|
|
21
|
+
brew install jq
|
|
22
|
+
|
|
23
|
+
# 4. Git (if not already installed)
|
|
24
|
+
git init
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install -g spec-and-loop
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Prerequisites:** Install openspec and opencode:
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g openspec opencode
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Demo (5 Minutes)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 1. Create a test project
|
|
42
|
+
mkdir demo-project
|
|
43
|
+
cd demo-project
|
|
44
|
+
git init
|
|
45
|
+
|
|
46
|
+
# 2. Initialize OpenSpec
|
|
47
|
+
openspec init
|
|
48
|
+
|
|
49
|
+
# 3. Create a new change
|
|
50
|
+
opsx-new add-hello-world
|
|
51
|
+
|
|
52
|
+
# 4. Fast-forward through artifact creation
|
|
53
|
+
opsx-ff
|
|
54
|
+
|
|
55
|
+
# 5. Run the ralph loop (executes tasks with opencode)
|
|
56
|
+
ralph-run --change add-hello-world
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**That's it!** The script will:
|
|
60
|
+
- Read your OpenSpec artifacts (proposal, specs, design, tasks)
|
|
61
|
+
- Execute each task with full context using opencode
|
|
62
|
+
- Create a git commit after each task
|
|
63
|
+
- Track progress in tasks.md
|
|
64
|
+
|
|
65
|
+
## What Just Happened?
|
|
66
|
+
|
|
67
|
+
1. **Created a spec** with OpenSpec
|
|
68
|
+
- `proposal.md`: Why you're adding this feature
|
|
69
|
+
- `specs/*/spec.md`: Detailed requirements
|
|
70
|
+
- `design.md`: Technical decisions
|
|
71
|
+
- `tasks.md`: Implementation tasks as checkboxes
|
|
72
|
+
|
|
73
|
+
2. **Executed tasks** with opencode
|
|
74
|
+
- Each task got full context (proposal + specs + design + git history)
|
|
75
|
+
- Git commits created after each task
|
|
76
|
+
- Task checkboxes marked as complete
|
|
77
|
+
|
|
78
|
+
3. **Iterated** until all tasks done
|
|
79
|
+
- Errors from previous tasks inform subsequent tasks
|
|
80
|
+
- Each task builds on the previous commit
|
|
81
|
+
- Full granular git history
|
|
82
|
+
|
|
83
|
+
## Verify Your Work
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Check the git history (one commit per task!)
|
|
87
|
+
git log --oneline
|
|
88
|
+
|
|
89
|
+
# See the change files
|
|
90
|
+
ls -la openspec/changes/add-hello-world/
|
|
91
|
+
|
|
92
|
+
# View the generated PRD (internal use)
|
|
93
|
+
cat openspec/changes/add-hello-world/.ralph/PRD.md
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Common Commands
|
|
97
|
+
|
|
98
|
+
### OpenSpec Commands
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
openspec init # Initialize in current directory
|
|
102
|
+
openspec new <name> # Start a new change
|
|
103
|
+
openspec continue <name> # Continue working on change
|
|
104
|
+
openspec ff <name> # Fast-forward artifact creation
|
|
105
|
+
openspec apply <name> # Apply change (implementation)
|
|
106
|
+
openspec archive <name> # Archive completed change
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Ralph Loop Commands
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
ralph-run # Auto-detect most recent change and run
|
|
113
|
+
ralph-run --change <name> # Run for specific change
|
|
114
|
+
ralph-run --verbose # Run with debug output
|
|
115
|
+
ralph-run --help # Show help message
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Real-World Example
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# 1. Initialize in your project
|
|
122
|
+
cd my-web-app
|
|
123
|
+
git init
|
|
124
|
+
openspec init
|
|
125
|
+
|
|
126
|
+
# 2. Create a feature
|
|
127
|
+
openspec new user-authentication
|
|
128
|
+
|
|
129
|
+
# 3. Go through the workflow
|
|
130
|
+
# - Create proposal: Why add auth?
|
|
131
|
+
# - Create specs: Login flow, password reset, OAuth
|
|
132
|
+
# - Create design: Use JWT, store hashed passwords
|
|
133
|
+
# - Create tasks: 15 checkboxes for implementation
|
|
134
|
+
|
|
135
|
+
# 4. Fast-forward to create all artifacts
|
|
136
|
+
openspec ff user-authentication
|
|
137
|
+
|
|
138
|
+
# 5. Execute the implementation
|
|
139
|
+
ralph-run --change user-authentication
|
|
140
|
+
|
|
141
|
+
# 6. Watch the magic happen!
|
|
142
|
+
# [INFO] Found 15 tasks to execute
|
|
143
|
+
# [INFO] Executing task 1/15: Create User model
|
|
144
|
+
# ✓ Complete
|
|
145
|
+
# [INFO] Executing task 2/15: Implement password hashing
|
|
146
|
+
# ✓ Complete
|
|
147
|
+
# ...
|
|
148
|
+
|
|
149
|
+
# 7. Verify the implementation
|
|
150
|
+
git log --oneline # 15 commits, one per task
|
|
151
|
+
git diff HEAD~15 # See full implementation
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Troubleshooting
|
|
155
|
+
|
|
156
|
+
### "openspec CLI not found" or "opencode CLI not found"
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npm install -g openspec opencode
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### "jq CLI not found"
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# Ubuntu/Debian
|
|
166
|
+
sudo apt install jq
|
|
167
|
+
|
|
168
|
+
# macOS
|
|
169
|
+
brew install jq
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### "Not a git repository"
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
git init
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### "command not found: ralph-run"
|
|
179
|
+
|
|
180
|
+
**Problem:** npm bin directory not in PATH
|
|
181
|
+
|
|
182
|
+
**Solution:**
|
|
183
|
+
```bash
|
|
184
|
+
# Add to ~/.bashrc or ~/.zshrc
|
|
185
|
+
export PATH="$PATH:$(npm root -g)/.bin"
|
|
186
|
+
|
|
187
|
+
# Reload shell
|
|
188
|
+
source ~/.bashrc
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### "No tasks to execute"
|
|
192
|
+
|
|
193
|
+
**Problem:** All tasks already complete!
|
|
194
|
+
|
|
195
|
+
**Solution:**
|
|
196
|
+
```bash
|
|
197
|
+
# Check tasks.md
|
|
198
|
+
grep "^\- \[x\]" openspec/changes/my-feature/tasks.md
|
|
199
|
+
|
|
200
|
+
# Or create a new change
|
|
201
|
+
opsx-new another-feature
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Features at a Glance
|
|
205
|
+
|
|
206
|
+
| Feature | Description |
|
|
207
|
+
|---------|-------------|
|
|
208
|
+
| **Structured Planning** | OpenSpec workflow: proposal → specs → design → tasks |
|
|
209
|
+
| **Agentic Execution** | opencode executes tasks with full context |
|
|
210
|
+
| **Iterative Loop** | Each task builds on previous commits |
|
|
211
|
+
| **Error Propagation** | Failures inform subsequent tasks |
|
|
212
|
+
| **Granular History** | One git commit per task |
|
|
213
|
+
| **Auto-Resume** | Interrupted? Run again—picks up where left off |
|
|
214
|
+
| **Context Injection** | Inject custom instructions during execution |
|
|
215
|
+
|
|
216
|
+
## Next Steps
|
|
217
|
+
|
|
218
|
+
1. **Read the full README.md** for detailed documentation
|
|
219
|
+
2. **Try a real feature** in your project
|
|
220
|
+
3. **Explore the .ralph/** directory to see internal state
|
|
221
|
+
4. **Check out .hidden/** directory for advanced guides
|
|
222
|
+
|
|
223
|
+
## Resources
|
|
224
|
+
|
|
225
|
+
- [Full README](./README.md) - Comprehensive documentation
|
|
226
|
+
- [OpenSpec](https://openspec.ai) - Specification workflow
|
|
227
|
+
- [opencode](https://opencode.ai) - Agentic coding assistant
|
|
228
|
+
- [open-ralph-wiggum](https://github.com/Th0rgal/open-ralph-wiggum) - Iterative execution loop
|
|
229
|
+
|
|
230
|
+
## Need Help?
|
|
231
|
+
|
|
232
|
+
- Check the **Troubleshooting** section above
|
|
233
|
+
- Review the **Full README.md** for detailed info
|
|
234
|
+
- Check **.hidden/** directory for advanced guides
|
|
235
|
+
|
|
236
|
+
Happy coding! 🚀
|
package/README.md
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# Spec and Loop
|
|
2
|
+
|
|
3
|
+
OpenSpec + Ralph Loop integration for iterative development with opencode.
|
|
4
|
+
|
|
5
|
+
**[🚀 Quick Start Guide](./QUICKSTART.md)** - Get up and running in 5 minutes!
|
|
6
|
+
|
|
7
|
+
## Why This Exists
|
|
8
|
+
|
|
9
|
+
OpenSpec provides excellent structure for planning (proposal → specs → design → tasks) but leaves execution manual. Ralph Wiggum's iterative development loop (execute → commit → repeat) is powerful but requires PRD format instead of OpenSpec specs.
|
|
10
|
+
|
|
11
|
+
**This utility bridges the gap**: use OpenSpec for planning, then automatically execute tasks with full context using opencode agentic coding assistant.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g spec-and-loop
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Prerequisites:** You need openspec and opencode installed:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g openspec opencode
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
**[🚀 Get Started in 5 Minutes](./QUICKSTART.md)**
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 1. Initialize OpenSpec in your project
|
|
31
|
+
openspec init
|
|
32
|
+
|
|
33
|
+
# 2. Create a new change
|
|
34
|
+
openspec new add-user-auth
|
|
35
|
+
|
|
36
|
+
# 3. Fast-forward through artifact creation
|
|
37
|
+
openspec ff add-user-auth
|
|
38
|
+
|
|
39
|
+
# 4. Run the ralph loop (executes tasks with opencode)
|
|
40
|
+
ralph-run --change add-user-auth
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For detailed step-by-step instructions, see [QUICKSTART.md](./QUICKSTART.md).
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# 1. Initialize OpenSpec in your project
|
|
49
|
+
openspec init
|
|
50
|
+
|
|
51
|
+
# 2. Create a new change
|
|
52
|
+
openspec new add-user-auth
|
|
53
|
+
|
|
54
|
+
# 3. Fast-forward through artifact creation
|
|
55
|
+
openspec ff add-user-auth
|
|
56
|
+
|
|
57
|
+
# 4. Run the ralph loop (executes tasks with opencode)
|
|
58
|
+
ralph-run --change add-user-auth
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Or auto-detect the most recent change:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
ralph-run
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Prerequisites
|
|
68
|
+
|
|
69
|
+
Before using spec-and-loop, ensure you have:
|
|
70
|
+
|
|
71
|
+
1. **Node.js & npm** - For package installation
|
|
72
|
+
```bash
|
|
73
|
+
node --version # Should be 14+
|
|
74
|
+
npm --version # Should be 6+
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
2. **openspec** - OpenSpec CLI for specification workflow
|
|
78
|
+
```bash
|
|
79
|
+
npm install -g openspec
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
3. **opencode** - Agentic coding assistant
|
|
83
|
+
```bash
|
|
84
|
+
npm install -g opencode
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
4. **jq** - Command-line JSON processor
|
|
88
|
+
```bash
|
|
89
|
+
# Ubuntu/Debian
|
|
90
|
+
sudo apt install jq
|
|
91
|
+
|
|
92
|
+
# macOS
|
|
93
|
+
brew install jq
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
5. **git** - Version control (for commits per task)
|
|
97
|
+
```bash
|
|
98
|
+
git init
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
For complete installation instructions, see [QUICKSTART.md](./QUICKSTART.md).
|
|
102
|
+
|
|
103
|
+
## Commands
|
|
104
|
+
|
|
105
|
+
### OpenSpec Commands
|
|
106
|
+
|
|
107
|
+
- `openspec init` - Initialize OpenSpec in current directory
|
|
108
|
+
- `openspec new <name>` - Start a new change
|
|
109
|
+
- `openspec ff <name>` - Fast-forward artifact creation
|
|
110
|
+
- `openspec continue <name>` - Continue working on change
|
|
111
|
+
- `openspec apply <name>` - Apply change (implementation)
|
|
112
|
+
- `openspec archive <name>` - Archive a completed change
|
|
113
|
+
|
|
114
|
+
### Ralph Loop Commands
|
|
115
|
+
|
|
116
|
+
- `ralph-run --change <name>` - Run the ralph loop for a specific change
|
|
117
|
+
- `ralph-run` - Auto-detect most recent change and run
|
|
118
|
+
|
|
119
|
+
## How It Works
|
|
120
|
+
|
|
121
|
+
### Step 1: Create Spec with OpenSpec
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
openspec new my-feature
|
|
125
|
+
openspec ff my-feature
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
This creates:
|
|
129
|
+
- **proposal.md**: Why you're making this change
|
|
130
|
+
- **specs/<capability>/spec.md**: Detailed requirements for each capability
|
|
131
|
+
- **design.md**: Technical decisions and architecture
|
|
132
|
+
- **tasks.md**: Implementation tasks as checkboxes
|
|
133
|
+
|
|
134
|
+
**Example tasks.md:**
|
|
135
|
+
```markdown
|
|
136
|
+
## Implementation
|
|
137
|
+
|
|
138
|
+
- [ ] Create database schema
|
|
139
|
+
- [ ] Implement API endpoints
|
|
140
|
+
- [ ] Write unit tests
|
|
141
|
+
- [ ] Add documentation
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Step 2: Run Ralph Loop
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
ralph-run --change my-feature
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**What happens:**
|
|
151
|
+
|
|
152
|
+
1. **Validation**: Checks for required OpenSpec artifacts
|
|
153
|
+
2. **PRD Generation**: Converts proposal + specs + design → PRD format (for internal use)
|
|
154
|
+
3. **Task Execution**: For each incomplete task:
|
|
155
|
+
- Generates context-rich prompt (task + specs + design + git history + errors)
|
|
156
|
+
- Runs `opencode` with the prompt
|
|
157
|
+
- Creates git commit with task description
|
|
158
|
+
- Marks task complete in tasks.md
|
|
159
|
+
4. **Completion**: All tasks done, errors cleared
|
|
160
|
+
|
|
161
|
+
### Step 3: Monitor Progress
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Check remaining tasks
|
|
165
|
+
grep "^- \[ \]" openspec/changes/my-feature/tasks.md
|
|
166
|
+
|
|
167
|
+
# View git commits
|
|
168
|
+
git log --oneline
|
|
169
|
+
|
|
170
|
+
# See errors (if any failed)
|
|
171
|
+
cat openspec/changes/my-feature/.ralph/errors.md
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Example Workflow
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# 1. Plan feature with OpenSpec
|
|
178
|
+
openspec new user-auth
|
|
179
|
+
openspec ff user-auth
|
|
180
|
+
|
|
181
|
+
# 2. Execute with Ralph
|
|
182
|
+
ralph-run --change user-auth
|
|
183
|
+
|
|
184
|
+
# Output:
|
|
185
|
+
# [INFO] Found 15 tasks to execute
|
|
186
|
+
# [INFO] Executing task 1/15: Create User model with password field
|
|
187
|
+
# ✓ Complete
|
|
188
|
+
# [INFO] Executing task 2/15: Implement password hashing
|
|
189
|
+
# ✓ Complete
|
|
190
|
+
# ...
|
|
191
|
+
|
|
192
|
+
# 3. Verify implementation
|
|
193
|
+
git log --oneline # 15 commits, one per task
|
|
194
|
+
git diff HEAD~15 # See full implementation
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Features at a Glance
|
|
198
|
+
|
|
199
|
+
| Feature | Description |
|
|
200
|
+
|---------|-------------|
|
|
201
|
+
| **Structured Planning** | OpenSpec workflow: proposal → specs → design → tasks |
|
|
202
|
+
| **Agentic Execution** | opencode executes tasks with full context |
|
|
203
|
+
| **Iterative Loop** | Each task builds on previous commits |
|
|
204
|
+
| **Error Propagation** | Failures inform subsequent tasks |
|
|
205
|
+
| **Granular History** | One git commit per task |
|
|
206
|
+
| **Auto-Resume** | Interrupted? Run again—picks up where left off |
|
|
207
|
+
| **Context Injection** | Inject custom instructions during execution |
|
|
208
|
+
|
|
209
|
+
For detailed feature descriptions, see below.
|
|
210
|
+
|
|
211
|
+
## Features
|
|
212
|
+
|
|
213
|
+
### Ralph Wiggum + Agentic Coding
|
|
214
|
+
|
|
215
|
+
- **Iterative refinement**: Each task builds on previous commits with full context
|
|
216
|
+
- **Error propagation**: Failures inform subsequent iterations—don't repeat mistakes
|
|
217
|
+
- **Granular history**: Commit per task makes debugging and rollback easy
|
|
218
|
+
- **Context awareness**: AI sees proposal, specs, design, git history, and errors
|
|
219
|
+
|
|
220
|
+
### OpenSpec + opencode Synergy
|
|
221
|
+
|
|
222
|
+
| OpenSpec | opencode | Together |
|
|
223
|
+
|----------|----------|----------|
|
|
224
|
+
| Structured planning | Agentic execution | Plan → Execute loop |
|
|
225
|
+
| Human-readable specs | AI-understandable context | Full context propagation |
|
|
226
|
+
| Task breakdown | Task implementation | Automatable workflow |
|
|
227
|
+
|
|
228
|
+
### Script Features
|
|
229
|
+
|
|
230
|
+
- **Auto-resume**: Interrupted? Run again—picks up where left off
|
|
231
|
+
- **Context injection**: Inject custom instructions during execution
|
|
232
|
+
- **Error recovery**: Errors propagate to guide subsequent tasks
|
|
233
|
+
- **Bidirectional tracking**: Tasks.md and .ralph/tracking.json stay synced
|
|
234
|
+
- **Idempotent**: Run multiple times safely
|
|
235
|
+
|
|
236
|
+
## Advanced Usage
|
|
237
|
+
|
|
238
|
+
### Context Injection
|
|
239
|
+
|
|
240
|
+
Inject custom instructions during execution:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Create injection file
|
|
244
|
+
echo "Use Redis instead of Memcached" > openspec/changes/my-feature/.ralph/.context_injection
|
|
245
|
+
|
|
246
|
+
# Next opencode invocation includes:
|
|
247
|
+
## Injected Context
|
|
248
|
+
Use Redis instead of Memcached
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Verbose Mode
|
|
252
|
+
|
|
253
|
+
For debugging:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
ralph-run --verbose --change my-feature
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### View Generated PRD
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
cat openspec/changes/my-feature/.ralph/PRD.md
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Manually Inject Context
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
echo "Consider performance implications" > openspec/changes/my-feature/.ralph/.context_injection
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Architecture
|
|
272
|
+
|
|
273
|
+
This package integrates:
|
|
274
|
+
- **OpenSpec**: Structured specification workflow
|
|
275
|
+
- **opencode**: Agentic coding assistant for task execution
|
|
276
|
+
- **Ralph Loop**: Iterative development with commits per task, error tracking
|
|
277
|
+
|
|
278
|
+
### Context Propagation
|
|
279
|
+
|
|
280
|
+
Each task execution includes:
|
|
281
|
+
- **Task description**: What to implement
|
|
282
|
+
- **Proposal summary**: Why this change matters
|
|
283
|
+
- **Relevant specs**: Requirements to satisfy
|
|
284
|
+
- **Design decisions**: Architectural constraints
|
|
285
|
+
- **Git history**: Last 10 commits (what's already done)
|
|
286
|
+
- **Previous errors**: What failed before (to avoid repeating)
|
|
287
|
+
|
|
288
|
+
### Task Tracking
|
|
289
|
+
|
|
290
|
+
Bidirectional synchronization:
|
|
291
|
+
- **tasks.md**: Human-readable checkboxes `[ ]` → `[x]`
|
|
292
|
+
- **.ralph/tracking.json**: Machine-readable state
|
|
293
|
+
- **Atomic updates**: Both succeed or both fail
|
|
294
|
+
- **Stable IDs**: Line numbers persist across script runs
|
|
295
|
+
|
|
296
|
+
### File Structure
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
openspec/changes/<name>/
|
|
300
|
+
├── proposal.md # Your "why"
|
|
301
|
+
├── design.md # Your "how"
|
|
302
|
+
├── tasks.md # Your "what" (checkboxes)
|
|
303
|
+
└── specs/ # Your requirements
|
|
304
|
+
├── auth/
|
|
305
|
+
│ └── spec.md
|
|
306
|
+
└── api/
|
|
307
|
+
└── spec.md
|
|
308
|
+
└── .ralph/ # Internal state (auto-generated)
|
|
309
|
+
├── PRD.md # Generated from artifacts
|
|
310
|
+
├── tracking.json # Task completion state
|
|
311
|
+
├── errors.md # Failure history
|
|
312
|
+
├── context-injections.md # Manual injections log
|
|
313
|
+
└── .context_injection # Pending injection
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Troubleshooting
|
|
317
|
+
|
|
318
|
+
For common issues and solutions, see [QUICKSTART.md#troubleshooting](./QUICKSTART.md#troubleshooting).
|
|
319
|
+
|
|
320
|
+
**Quick fixes:**
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
# opencode not found?
|
|
324
|
+
npm install -g opencode
|
|
325
|
+
|
|
326
|
+
# jq not found?
|
|
327
|
+
sudo apt install jq # or: brew install jq
|
|
328
|
+
|
|
329
|
+
# Not a git repository?
|
|
330
|
+
git init
|
|
331
|
+
|
|
332
|
+
# command not found: ralph-run?
|
|
333
|
+
export PATH="$PATH:$(npm root -g)/.bin"
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Resources
|
|
337
|
+
|
|
338
|
+
- [OpenSpec](https://openspec.ai) - Structured specification workflow
|
|
339
|
+
- [open-ralph-wiggum](https://github.com/Th0rgal/open-ralph-wiggum) - Iterative execution loop
|
|
340
|
+
- [opencode](https://opencode.ai) - Agentic coding assistant
|
|
341
|
+
|
|
342
|
+
## License
|
|
343
|
+
|
|
344
|
+
MIT
|
package/bin/ralph-run
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
const scriptPath = path.join(__dirname, '..', 'scripts', 'ralph-run.sh');
|
|
7
|
+
const args = process.argv.slice(2).map(arg => `"${arg.replace(/"/g, '\\"')}"`).join(' ');
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
execSync(`bash "${scriptPath}" ${args}`, { stdio: 'inherit' });
|
|
11
|
+
} catch (err) {
|
|
12
|
+
process.exit(err.status || 1);
|
|
13
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "spec-and-loop",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenSpec + Ralph Loop integration for iterative development with opencode",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ralph-run": "./bin/ralph-run"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"postinstall": "node scripts/setup.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"openspec": "^1.0.0"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"openspec",
|
|
17
|
+
"ralph-wiggum",
|
|
18
|
+
"opencode",
|
|
19
|
+
"iterative-development",
|
|
20
|
+
"agentic-coding"
|
|
21
|
+
],
|
|
22
|
+
"author": "",
|
|
23
|
+
"license": "GPL-3.0",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/ncheaz/spec-and-loop.git"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"bin/",
|
|
30
|
+
"scripts/",
|
|
31
|
+
"README.md",
|
|
32
|
+
"QUICKSTART.md"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=24.0.0"
|
|
36
|
+
},
|
|
37
|
+
"os": [
|
|
38
|
+
"darwin",
|
|
39
|
+
"linux"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
trap 'handle_error $?' EXIT
|
|
5
|
+
|
|
6
|
+
VERSION="1.0.0"
|
|
7
|
+
CHANGE_NAME=""
|
|
8
|
+
ERROR_OCCURRED=false
|
|
9
|
+
|
|
10
|
+
handle_error() {
|
|
11
|
+
local exit_code=$1
|
|
12
|
+
if [[ $exit_code -ne 0 ]]; then
|
|
13
|
+
ERROR_OCCURRED=true
|
|
14
|
+
log_error "Script failed with exit code: $exit_code"
|
|
15
|
+
log_error "Use --verbose flag for more debugging information"
|
|
16
|
+
fi
|
|
17
|
+
}
|
|
18
|
+
VERBOSE=false
|
|
19
|
+
SHOW_HELP=false
|
|
20
|
+
|
|
21
|
+
usage() {
|
|
22
|
+
cat << EOF
|
|
23
|
+
ralph-run - OpenSpec + Ralph Loop integration for iterative development with opencode
|
|
24
|
+
|
|
25
|
+
USAGE:
|
|
26
|
+
ralph-run [OPTIONS]
|
|
27
|
+
|
|
28
|
+
OPTIONS:
|
|
29
|
+
--change <name> Specify the OpenSpec change to execute (default: auto-detect)
|
|
30
|
+
--verbose, -v Enable verbose mode for debugging
|
|
31
|
+
--help, -h Show this help message
|
|
32
|
+
|
|
33
|
+
EXAMPLES:
|
|
34
|
+
ralph-run # Auto-detect most recent change
|
|
35
|
+
ralph-run --change my-feature # Execute specific change
|
|
36
|
+
ralph-run --verbose # Run with debug output
|
|
37
|
+
|
|
38
|
+
PREREQUISITES:
|
|
39
|
+
- Git repository (git init)
|
|
40
|
+
- OpenSpec artifacts created (openspec init, opsx-new, opsx-ff)
|
|
41
|
+
- opencode CLI installed (npm install -g opencode)
|
|
42
|
+
- jq CLI installed (apt install jq / brew install jq)
|
|
43
|
+
|
|
44
|
+
EOF
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
parse_arguments() {
|
|
48
|
+
while [[ $# -gt 0 ]]; do
|
|
49
|
+
case $1 in
|
|
50
|
+
--change)
|
|
51
|
+
CHANGE_NAME="$2"
|
|
52
|
+
shift 2
|
|
53
|
+
;;
|
|
54
|
+
--verbose|-v)
|
|
55
|
+
VERBOSE=true
|
|
56
|
+
shift
|
|
57
|
+
;;
|
|
58
|
+
--help|-h)
|
|
59
|
+
SHOW_HELP=true
|
|
60
|
+
shift
|
|
61
|
+
;;
|
|
62
|
+
*)
|
|
63
|
+
echo "Error: Unknown option: $1"
|
|
64
|
+
usage
|
|
65
|
+
exit 1
|
|
66
|
+
;;
|
|
67
|
+
esac
|
|
68
|
+
done
|
|
69
|
+
|
|
70
|
+
if [[ "$SHOW_HELP" == true ]]; then
|
|
71
|
+
usage
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
log_verbose() {
|
|
77
|
+
if [[ "$VERBOSE" == true ]]; then
|
|
78
|
+
echo "[VERBOSE] $*"
|
|
79
|
+
fi
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
log_info() {
|
|
83
|
+
echo "[INFO] $*"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
log_error() {
|
|
87
|
+
echo "[ERROR] $*" >&2
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
validate_git_repository() {
|
|
91
|
+
log_verbose "Validating git repository..."
|
|
92
|
+
|
|
93
|
+
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
|
94
|
+
log_error "Not a git repository. Please run this script within a git repository."
|
|
95
|
+
log_error "Run: git init"
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
log_verbose "Git repository validated"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
validate_dependencies() {
|
|
103
|
+
log_verbose "Validating dependencies..."
|
|
104
|
+
|
|
105
|
+
# Check for opencode
|
|
106
|
+
if ! command -v opencode &> /dev/null; then
|
|
107
|
+
log_error "opencode CLI not found."
|
|
108
|
+
log_error "Please install opencode: npm install -g opencode"
|
|
109
|
+
exit 1
|
|
110
|
+
fi
|
|
111
|
+
log_verbose "Found: opencode"
|
|
112
|
+
|
|
113
|
+
# Check for jq
|
|
114
|
+
if ! command -v jq &> /dev/null; then
|
|
115
|
+
log_error "jq CLI not found."
|
|
116
|
+
log_error "Please install jq:"
|
|
117
|
+
log_error " Ubuntu/Debian: apt install jq"
|
|
118
|
+
log_error " macOS: brew install jq"
|
|
119
|
+
exit 1
|
|
120
|
+
fi
|
|
121
|
+
log_verbose "Found: jq"
|
|
122
|
+
|
|
123
|
+
log_verbose "All dependencies validated"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
validate_openspec_artifacts() {
|
|
127
|
+
local change_dir="$1"
|
|
128
|
+
|
|
129
|
+
log_verbose "Validating OpenSpec artifacts..."
|
|
130
|
+
|
|
131
|
+
local required_files=(
|
|
132
|
+
"proposal.md"
|
|
133
|
+
"tasks.md"
|
|
134
|
+
"design.md"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
for file in "${required_files[@]}"; do
|
|
138
|
+
if [[ ! -f "$change_dir/$file" ]]; then
|
|
139
|
+
log_error "Required artifact not found: $file"
|
|
140
|
+
exit 1
|
|
141
|
+
fi
|
|
142
|
+
log_verbose "Found artifact: $file"
|
|
143
|
+
done
|
|
144
|
+
|
|
145
|
+
if [[ ! -d "$change_dir/specs" ]]; then
|
|
146
|
+
log_error "Required directory not found: specs/"
|
|
147
|
+
exit 1
|
|
148
|
+
fi
|
|
149
|
+
log_verbose "Found directory: specs/"
|
|
150
|
+
|
|
151
|
+
log_info "All OpenSpec artifacts validated"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
setup_ralph_directory() {
|
|
155
|
+
local change_dir="$1"
|
|
156
|
+
local ralph_dir="$change_dir/.ralph"
|
|
157
|
+
|
|
158
|
+
log_verbose "Setting up .ralph directory..."
|
|
159
|
+
|
|
160
|
+
if [[ ! -d "$ralph_dir" ]]; then
|
|
161
|
+
mkdir -p "$ralph_dir"
|
|
162
|
+
log_verbose "Created .ralph directory: $ralph_dir"
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
echo "$ralph_dir"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
auto_detect_change() {
|
|
169
|
+
local changes_dir="openspec/changes"
|
|
170
|
+
|
|
171
|
+
if [[ ! -d "$changes_dir" ]]; then
|
|
172
|
+
log_error "OpenSpec changes directory not found: $changes_dir"
|
|
173
|
+
exit 1
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
log_verbose "Auto-detecting most recently modified change..."
|
|
177
|
+
|
|
178
|
+
local latest_change=""
|
|
179
|
+
local latest_time=0
|
|
180
|
+
|
|
181
|
+
for change_dir in "$changes_dir"/*; do
|
|
182
|
+
if [[ -d "$change_dir" ]]; then
|
|
183
|
+
local tasks_file="$change_dir/tasks.md"
|
|
184
|
+
if [[ -f "$tasks_file" ]]; then
|
|
185
|
+
local mod_time=$(stat -c %Y "$tasks_file" 2>/dev/null || echo 0)
|
|
186
|
+
if [[ $mod_time -gt $latest_time ]]; then
|
|
187
|
+
latest_time=$mod_time
|
|
188
|
+
latest_change=$(basename "$change_dir")
|
|
189
|
+
fi
|
|
190
|
+
fi
|
|
191
|
+
fi
|
|
192
|
+
done
|
|
193
|
+
|
|
194
|
+
if [[ -z "$latest_change" ]]; then
|
|
195
|
+
log_error "No changes found with tasks.md in $changes_dir"
|
|
196
|
+
exit 1
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
log_verbose "Auto-detected change: $latest_change"
|
|
200
|
+
printf "%s" "$latest_change"
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
read_openspec_artifacts() {
|
|
204
|
+
local change_dir="$1"
|
|
205
|
+
|
|
206
|
+
log_verbose "Reading OpenSpec artifacts..."
|
|
207
|
+
|
|
208
|
+
local proposal_content=""
|
|
209
|
+
local specs_content=""
|
|
210
|
+
local design_content=""
|
|
211
|
+
|
|
212
|
+
if [[ -f "$change_dir/proposal.md" ]]; then
|
|
213
|
+
proposal_content=$(cat "$change_dir/proposal.md")
|
|
214
|
+
log_verbose "Read proposal.md"
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
if [[ -d "$change_dir/specs" ]]; then
|
|
218
|
+
while IFS= read -r -d '' spec_file; do
|
|
219
|
+
local spec_name=$(basename "$(dirname "$spec_file")")
|
|
220
|
+
specs_content+="$spec_name/spec.md"$'\n'
|
|
221
|
+
specs_content+="$(cat "$spec_file")"$'\n'$'\n'
|
|
222
|
+
log_verbose "Read spec: $spec_name/spec.md"
|
|
223
|
+
done < <(find "$change_dir/specs" -name "spec.md" -print0 | sort -z)
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
if [[ -f "$change_dir/design.md" ]]; then
|
|
227
|
+
design_content=$(cat "$change_dir/design.md")
|
|
228
|
+
log_verbose "Read design.md"
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
declare -g OPENSPEC_PROPOSAL="$proposal_content"
|
|
232
|
+
declare -g OPENSPEC_SPECS="$specs_content"
|
|
233
|
+
declare -g OPENSPEC_DESIGN="$design_content"
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
generate_prd() {
|
|
237
|
+
local change_dir="$1"
|
|
238
|
+
|
|
239
|
+
log_verbose "Generating PRD from OpenSpec artifacts..."
|
|
240
|
+
|
|
241
|
+
local prd_content=""
|
|
242
|
+
|
|
243
|
+
prd_content+="# Product Requirements Document"$'\n'$'\n'
|
|
244
|
+
prd_content+="*Generated from OpenSpec artifacts*"$'\n'$'\n'
|
|
245
|
+
|
|
246
|
+
prd_content+="## Proposal"$'\n'$'\n'
|
|
247
|
+
prd_content+="$OPENSPEC_PROPOSAL"$'\n'$'\n'
|
|
248
|
+
|
|
249
|
+
prd_content+="## Specifications"$'\n'$'\n'
|
|
250
|
+
prd_content+="$OPENSPEC_SPECS"$'\n'$'\n'
|
|
251
|
+
|
|
252
|
+
prd_content+="## Design"$'\n'$'\n'
|
|
253
|
+
prd_content+="$OPENSPEC_DESIGN"$'\n'
|
|
254
|
+
|
|
255
|
+
echo "$prd_content"
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
write_prd() {
|
|
259
|
+
local ralph_dir="$1"
|
|
260
|
+
local prd_content="$2"
|
|
261
|
+
|
|
262
|
+
log_verbose "Writing PRD.md to .ralph/ directory..."
|
|
263
|
+
|
|
264
|
+
echo "$prd_content" > "$ralph_dir/PRD.md"
|
|
265
|
+
|
|
266
|
+
log_verbose "PRD.md written to $ralph_dir/PRD.md"
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
parse_tasks() {
|
|
270
|
+
local change_dir="$1"
|
|
271
|
+
local tasks_file="$change_dir/tasks.md"
|
|
272
|
+
|
|
273
|
+
log_verbose "Parsing tasks from tasks.md..."
|
|
274
|
+
|
|
275
|
+
declare -g TASKS=()
|
|
276
|
+
declare -g TASK_IDS=()
|
|
277
|
+
declare -g TASKS_MD5=""
|
|
278
|
+
|
|
279
|
+
if [[ -f "$tasks_file" ]]; then
|
|
280
|
+
TASKS_MD5=$(md5sum "$tasks_file" | cut -d' ' -f1)
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
log_verbose "Parsing tasks from tasks.md..."
|
|
284
|
+
|
|
285
|
+
declare -g TASKS=()
|
|
286
|
+
declare -g TASK_IDS=()
|
|
287
|
+
|
|
288
|
+
local line_number=0
|
|
289
|
+
while IFS= read -r line; do
|
|
290
|
+
((line_number++)) || true
|
|
291
|
+
|
|
292
|
+
if [[ "$line" == "- [ ]"* ]]; then
|
|
293
|
+
local task_desc="${line#- [ ] }"
|
|
294
|
+
TASKS+=("$task_desc")
|
|
295
|
+
TASK_IDS+=("$line_number")
|
|
296
|
+
log_verbose "Found incomplete task (line $line_number): $task_desc"
|
|
297
|
+
fi
|
|
298
|
+
done < "$tasks_file"
|
|
299
|
+
|
|
300
|
+
log_verbose "Found ${#TASKS[@]} incomplete tasks"
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
check_tasks_modified() {
|
|
304
|
+
local change_dir="$1"
|
|
305
|
+
local original_md5="$2"
|
|
306
|
+
local tasks_file="$change_dir/tasks.md"
|
|
307
|
+
|
|
308
|
+
if [[ ! -f "$tasks_file" ]]; then
|
|
309
|
+
return 1
|
|
310
|
+
fi
|
|
311
|
+
|
|
312
|
+
local current_md5
|
|
313
|
+
current_md5=$(md5sum "$tasks_file" | cut -d' ' -f1)
|
|
314
|
+
|
|
315
|
+
if [[ "$current_md5" != "$original_md5" ]]; then
|
|
316
|
+
return 0
|
|
317
|
+
fi
|
|
318
|
+
|
|
319
|
+
return 1
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
format_error_entry() {
|
|
323
|
+
local task_id="$1"
|
|
324
|
+
local task_description="$2"
|
|
325
|
+
local error_output="$3"
|
|
326
|
+
|
|
327
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
328
|
+
|
|
329
|
+
echo "---"
|
|
330
|
+
echo "Timestamp: $timestamp"
|
|
331
|
+
echo "Task ID: $task_id"
|
|
332
|
+
echo "Task: $task_description"
|
|
333
|
+
echo ""
|
|
334
|
+
echo "Error Output:"
|
|
335
|
+
echo "$error_output"
|
|
336
|
+
echo ""
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
append_error() {
|
|
340
|
+
local ralph_dir="$1"
|
|
341
|
+
local task_id="$2"
|
|
342
|
+
local task_description="$3"
|
|
343
|
+
local error_output="$4"
|
|
344
|
+
|
|
345
|
+
local errors_file="$ralph_dir/errors.md"
|
|
346
|
+
|
|
347
|
+
log_verbose "Appending error to errors.md..."
|
|
348
|
+
|
|
349
|
+
local error_entry
|
|
350
|
+
error_entry=$(format_error_entry "$task_id" "$task_description" "$error_output")
|
|
351
|
+
|
|
352
|
+
echo "$error_entry" >> "$errors_file"
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
read_errors() {
|
|
356
|
+
local ralph_dir="$1"
|
|
357
|
+
local limit="${2:-10}"
|
|
358
|
+
|
|
359
|
+
local errors_file="$ralph_dir/errors.md"
|
|
360
|
+
|
|
361
|
+
if [[ ! -f "$errors_file" ]]; then
|
|
362
|
+
return 0
|
|
363
|
+
fi
|
|
364
|
+
|
|
365
|
+
log_verbose "Reading errors from errors.md..."
|
|
366
|
+
|
|
367
|
+
tail -n "$limit" "$errors_file"
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
clear_errors() {
|
|
371
|
+
local ralph_dir="$1"
|
|
372
|
+
|
|
373
|
+
local errors_file="$ralph_dir/errors.md"
|
|
374
|
+
|
|
375
|
+
if [[ -f "$errors_file" ]]; then
|
|
376
|
+
rm "$errors_file"
|
|
377
|
+
log_verbose "Cleared errors.md"
|
|
378
|
+
fi
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
archive_errors() {
|
|
382
|
+
local ralph_dir="$1"
|
|
383
|
+
|
|
384
|
+
local errors_file="$ralph_dir/errors.md"
|
|
385
|
+
|
|
386
|
+
if [[ ! -f "$errors_file" ]]; then
|
|
387
|
+
return 0
|
|
388
|
+
fi
|
|
389
|
+
|
|
390
|
+
local timestamp=$(date -u +"%Y%m%d_%H%M%S")
|
|
391
|
+
local archive_file="$ralph_dir/errors_${timestamp}.md"
|
|
392
|
+
|
|
393
|
+
cp "$errors_file" "$archive_file"
|
|
394
|
+
log_verbose "Archived errors to $archive_file"
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
handle_context_injection() {
|
|
398
|
+
local ralph_dir="$1"
|
|
399
|
+
|
|
400
|
+
local injection_file="$ralph_dir/.context_injection"
|
|
401
|
+
|
|
402
|
+
if [[ -f "$injection_file" ]]; then
|
|
403
|
+
local injected_context
|
|
404
|
+
injected_context=$(cat "$injection_file")
|
|
405
|
+
rm "$injection_file"
|
|
406
|
+
echo "$injected_context"
|
|
407
|
+
return 0
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
return 1
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
initialize_context_injections() {
|
|
414
|
+
local ralph_dir="$1"
|
|
415
|
+
|
|
416
|
+
local injections_file="$ralph_dir/context-injections.md"
|
|
417
|
+
|
|
418
|
+
if [[ ! -f "$injections_file" ]]; then
|
|
419
|
+
echo "# Context Injections" > "$injections_file"
|
|
420
|
+
echo "" >> "$injections_file"
|
|
421
|
+
log_verbose "Created context-injections.md"
|
|
422
|
+
fi
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
validate_script_state() {
|
|
426
|
+
local change_dir="$1"
|
|
427
|
+
|
|
428
|
+
log_verbose "Validating script state..."
|
|
429
|
+
|
|
430
|
+
local required_dirs=(
|
|
431
|
+
".ralph"
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
for dir in "${required_dirs[@]}"; do
|
|
435
|
+
if [[ ! -d "$change_dir/$dir" ]]; then
|
|
436
|
+
log_verbose "Required directory not found: $dir (will be created)"
|
|
437
|
+
fi
|
|
438
|
+
done
|
|
439
|
+
|
|
440
|
+
local required_files=(
|
|
441
|
+
"tasks.md"
|
|
442
|
+
"proposal.md"
|
|
443
|
+
"design.md"
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
for file in "${required_files[@]}"; do
|
|
447
|
+
if [[ ! -f "$change_dir/$file" ]]; then
|
|
448
|
+
log_error "Required file not found: $file"
|
|
449
|
+
return 1
|
|
450
|
+
fi
|
|
451
|
+
done
|
|
452
|
+
|
|
453
|
+
log_verbose "Script state validated"
|
|
454
|
+
return 0
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
get_git_history() {
|
|
458
|
+
local limit="${1:-10}"
|
|
459
|
+
|
|
460
|
+
log_verbose "Retrieving git history (last $limit commits)..."
|
|
461
|
+
|
|
462
|
+
local git_history=""
|
|
463
|
+
git_history=$(git log -n "$limit" --pretty=format:"%h|%ad|%an|%s" --date=iso 2>/dev/null || echo "")
|
|
464
|
+
|
|
465
|
+
if [[ -z "$git_history" ]]; then
|
|
466
|
+
log_verbose "No git history found"
|
|
467
|
+
return
|
|
468
|
+
fi
|
|
469
|
+
|
|
470
|
+
echo "$git_history"
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
gather_opencode_context() {
|
|
474
|
+
local task_description="$1"
|
|
475
|
+
|
|
476
|
+
log_verbose "Gathering opencode context..."
|
|
477
|
+
|
|
478
|
+
local context=""
|
|
479
|
+
|
|
480
|
+
context+="## Task"$'\n'
|
|
481
|
+
context+="$task_description"$'\n'$'\n'
|
|
482
|
+
|
|
483
|
+
context+="## Proposal Summary"$'\n'
|
|
484
|
+
context+="$(echo "$OPENSPEC_PROPOSAL" | head -20)"$'\n'$'\n'
|
|
485
|
+
|
|
486
|
+
context+="## Design Decisions"$'\n'
|
|
487
|
+
context+="$(echo "$OPENSPEC_DESIGN" | head -30)"$'\n'$'\n'
|
|
488
|
+
|
|
489
|
+
context+="## Specifications"$'\n'
|
|
490
|
+
context+="$(echo "$OPENSPEC_SPECS" | head -50)"$'\n'$'\n'
|
|
491
|
+
|
|
492
|
+
echo "$context"
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
generate_opencode_prompt() {
|
|
496
|
+
local task_description="$1"
|
|
497
|
+
local ralph_dir="$2"
|
|
498
|
+
|
|
499
|
+
log_verbose "Generating opencode prompt..."
|
|
500
|
+
|
|
501
|
+
local context
|
|
502
|
+
context=$(gather_opencode_context "$task_description")
|
|
503
|
+
|
|
504
|
+
local prompt=""
|
|
505
|
+
prompt+="You are implementing a task as part of an OpenSpec change."$'\n'$'\n'
|
|
506
|
+
prompt+="$context"$'\n'
|
|
507
|
+
|
|
508
|
+
prompt+="## Recent Git History"$'\n'
|
|
509
|
+
local git_history
|
|
510
|
+
git_history=$(get_git_history 10)
|
|
511
|
+
if [[ -n "$git_history" ]]; then
|
|
512
|
+
echo "$git_history" | while IFS='|' read -r hash date author message; do
|
|
513
|
+
prompt+="- $hash ($date $author): $message"$'\n'
|
|
514
|
+
done
|
|
515
|
+
fi
|
|
516
|
+
prompt+=$'\n'
|
|
517
|
+
|
|
518
|
+
prompt+="## Error History"$'\n'
|
|
519
|
+
local errors_file="$ralph_dir/errors.md"
|
|
520
|
+
if [[ -f "$errors_file" ]]; then
|
|
521
|
+
prompt+="$(cat "$errors_file" | tail -50)"$'\n'
|
|
522
|
+
else
|
|
523
|
+
prompt+="(No previous errors)"$'\n'
|
|
524
|
+
fi
|
|
525
|
+
prompt+=$'\n'
|
|
526
|
+
|
|
527
|
+
prompt+="## Instructions"$'\n'
|
|
528
|
+
prompt+="Implement the task above. Use the context provided to understand the requirements and any relevant design decisions."$'\n'
|
|
529
|
+
prompt+="If there are previous errors, use them to guide your implementation to avoid repeating mistakes."$'\n'
|
|
530
|
+
|
|
531
|
+
local injected_context
|
|
532
|
+
if injected_context=$(handle_context_injection "$ralph_dir"); then
|
|
533
|
+
prompt+=$'\n'
|
|
534
|
+
prompt+="## Injected Context"$'\n'
|
|
535
|
+
prompt+="$injected_context"$'\n'
|
|
536
|
+
fi
|
|
537
|
+
|
|
538
|
+
echo "$prompt"
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
execute_opencode() {
|
|
542
|
+
local prompt="$1"
|
|
543
|
+
|
|
544
|
+
log_verbose "Executing opencode CLI..."
|
|
545
|
+
|
|
546
|
+
if ! command -v opencode &> /dev/null; then
|
|
547
|
+
log_error "opencode CLI not found. Please install opencode."
|
|
548
|
+
return 1
|
|
549
|
+
fi
|
|
550
|
+
|
|
551
|
+
local output
|
|
552
|
+
output=$(echo "$prompt" | opencode 2>&1)
|
|
553
|
+
local exit_code=$?
|
|
554
|
+
|
|
555
|
+
echo "$output"
|
|
556
|
+
return $exit_code
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
create_git_commit() {
|
|
560
|
+
local task_description="$1"
|
|
561
|
+
|
|
562
|
+
log_verbose "Creating git commit..."
|
|
563
|
+
|
|
564
|
+
if ! git diff-index --quiet HEAD --; then
|
|
565
|
+
git add -A
|
|
566
|
+
git commit -m "$task_description" 2>&1
|
|
567
|
+
log_verbose "Git commit created"
|
|
568
|
+
else
|
|
569
|
+
log_verbose "No changes to commit"
|
|
570
|
+
fi
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
execute_task_loop() {
|
|
574
|
+
local ralph_dir="$1"
|
|
575
|
+
|
|
576
|
+
log_info "Starting task execution loop..."
|
|
577
|
+
|
|
578
|
+
local total_tasks=${#TASKS[@]}
|
|
579
|
+
log_info "Total tasks to execute: $total_tasks"
|
|
580
|
+
|
|
581
|
+
for i in "${!TASKS[@]}"; do
|
|
582
|
+
local task_description="${TASKS[$i]}"
|
|
583
|
+
local task_id="${TASK_IDS[$i]}"
|
|
584
|
+
|
|
585
|
+
log_info "Executing task $((i+1))/$total_tasks: $task_description"
|
|
586
|
+
|
|
587
|
+
local prompt
|
|
588
|
+
prompt=$(generate_opencode_prompt "$task_description" "$ralph_dir")
|
|
589
|
+
|
|
590
|
+
local output
|
|
591
|
+
output=$(execute_opencode "$prompt")
|
|
592
|
+
local exit_code=$?
|
|
593
|
+
|
|
594
|
+
if [[ $exit_code -eq 0 ]]; then
|
|
595
|
+
log_info "Task completed successfully"
|
|
596
|
+
create_git_commit "$task_description"
|
|
597
|
+
else
|
|
598
|
+
log_error "Task failed with exit code: $exit_code"
|
|
599
|
+
log_error "Output: $output"
|
|
600
|
+
break
|
|
601
|
+
fi
|
|
602
|
+
done
|
|
603
|
+
|
|
604
|
+
log_info "Task execution loop complete"
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
initialize_tracking() {
|
|
608
|
+
local ralph_dir="$1"
|
|
609
|
+
local tracking_file="$ralph_dir/tracking.json"
|
|
610
|
+
|
|
611
|
+
log_verbose "Initializing tracking..."
|
|
612
|
+
|
|
613
|
+
if [[ ! -f "$tracking_file" ]]; then
|
|
614
|
+
log_verbose "Creating tracking.json..."
|
|
615
|
+
echo '{"tasks":{}}' > "$tracking_file"
|
|
616
|
+
fi
|
|
617
|
+
|
|
618
|
+
echo "$tracking_file"
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
read_tracking() {
|
|
622
|
+
local tracking_file="$1"
|
|
623
|
+
|
|
624
|
+
log_verbose "Reading tracking.json..."
|
|
625
|
+
|
|
626
|
+
if [[ -f "$tracking_file" ]]; then
|
|
627
|
+
cat "$tracking_file"
|
|
628
|
+
else
|
|
629
|
+
echo '{"tasks":{}}'
|
|
630
|
+
fi
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
update_task_checkbox() {
|
|
634
|
+
local change_dir="$1"
|
|
635
|
+
local task_id="$2"
|
|
636
|
+
local complete="$3"
|
|
637
|
+
|
|
638
|
+
log_verbose "Updating task checkbox (line $task_id, complete=$complete)..."
|
|
639
|
+
|
|
640
|
+
local tasks_file="$change_dir/tasks.md"
|
|
641
|
+
local temp_file=$(mktemp)
|
|
642
|
+
|
|
643
|
+
local line_number=0
|
|
644
|
+
while IFS= read -r line; do
|
|
645
|
+
((line_number++)) || true
|
|
646
|
+
|
|
647
|
+
if [[ $line_number -eq $task_id ]]; then
|
|
648
|
+
if [[ "$complete" == "true" ]]; then
|
|
649
|
+
echo "${line/- \[ \]/- [x]}" >> "$temp_file"
|
|
650
|
+
else
|
|
651
|
+
echo "${line/- \[x\]/- [ ]}" >> "$temp_file"
|
|
652
|
+
fi
|
|
653
|
+
else
|
|
654
|
+
echo "$line" >> "$temp_file"
|
|
655
|
+
fi
|
|
656
|
+
done < "$tasks_file"
|
|
657
|
+
|
|
658
|
+
mv "$temp_file" "$tasks_file"
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
update_tracking() {
|
|
662
|
+
local tracking_file="$1"
|
|
663
|
+
local task_id="$2"
|
|
664
|
+
local complete="$3"
|
|
665
|
+
|
|
666
|
+
log_verbose "Updating tracking.json (task $task_id, complete=$complete)..."
|
|
667
|
+
|
|
668
|
+
local status="pending"
|
|
669
|
+
if [[ "$complete" == "true" ]]; then
|
|
670
|
+
status="complete"
|
|
671
|
+
fi
|
|
672
|
+
|
|
673
|
+
local tracking_json
|
|
674
|
+
tracking_json=$(cat "$tracking_file")
|
|
675
|
+
|
|
676
|
+
local updated_json
|
|
677
|
+
updated_json=$(echo "$tracking_json" | jq --arg id "$task_id" --arg status "$status" '.tasks[$id] = {status: $status}' 2>/dev/null || echo '{"tasks":{}}')
|
|
678
|
+
|
|
679
|
+
echo "$updated_json" > "$tracking_file"
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
update_task_status_atomic() {
|
|
683
|
+
local change_dir="$1"
|
|
684
|
+
local task_id="$2"
|
|
685
|
+
local complete="$3"
|
|
686
|
+
local tracking_file="$4"
|
|
687
|
+
|
|
688
|
+
log_verbose "Updating task status atomically..."
|
|
689
|
+
|
|
690
|
+
local tasks_file="$change_dir/tasks.md"
|
|
691
|
+
local tasks_backup="${tasks_file}.bak"
|
|
692
|
+
local tracking_backup="${tracking_file}.bak"
|
|
693
|
+
|
|
694
|
+
cp "$tasks_file" "$tasks_backup"
|
|
695
|
+
cp "$tracking_file" "$tracking_backup"
|
|
696
|
+
|
|
697
|
+
update_task_checkbox "$change_dir" "$task_id" "$complete"
|
|
698
|
+
update_tracking "$tracking_file" "$task_id" "$complete"
|
|
699
|
+
|
|
700
|
+
if [[ ! -f "$tasks_file" ]] || [[ ! -f "$tracking_file" ]]; then
|
|
701
|
+
log_error "Update failed: files not found"
|
|
702
|
+
mv "$tasks_backup" "$tasks_file"
|
|
703
|
+
mv "$tracking_backup" "$tracking_file"
|
|
704
|
+
return 1
|
|
705
|
+
fi
|
|
706
|
+
|
|
707
|
+
rm "$tasks_backup"
|
|
708
|
+
rm "$tracking_backup"
|
|
709
|
+
|
|
710
|
+
log_verbose "Atomic update successful"
|
|
711
|
+
return 0
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
main() {
|
|
715
|
+
parse_arguments "$@"
|
|
716
|
+
|
|
717
|
+
log_verbose "Starting ralph-run v$VERSION"
|
|
718
|
+
log_verbose "Change name: ${CHANGE_NAME:-<auto-detect>}"
|
|
719
|
+
|
|
720
|
+
validate_git_repository
|
|
721
|
+
validate_dependencies
|
|
722
|
+
|
|
723
|
+
if [[ -z "$CHANGE_NAME" ]]; then
|
|
724
|
+
CHANGE_NAME=$(auto_detect_change)
|
|
725
|
+
log_info "Auto-detected change: $CHANGE_NAME"
|
|
726
|
+
fi
|
|
727
|
+
|
|
728
|
+
local change_dir="openspec/changes/$CHANGE_NAME"
|
|
729
|
+
validate_openspec_artifacts "$change_dir"
|
|
730
|
+
validate_script_state "$change_dir"
|
|
731
|
+
local ralph_dir=$(setup_ralph_directory "$change_dir")
|
|
732
|
+
local tracking_file=$(initialize_tracking "$ralph_dir")
|
|
733
|
+
|
|
734
|
+
log_info "Change directory: $change_dir"
|
|
735
|
+
log_info "Ralph directory: $ralph_dir"
|
|
736
|
+
|
|
737
|
+
read_openspec_artifacts "$change_dir"
|
|
738
|
+
local prd_content=$(generate_prd "$change_dir")
|
|
739
|
+
write_prd "$ralph_dir" "$prd_content"
|
|
740
|
+
|
|
741
|
+
parse_tasks "$change_dir"
|
|
742
|
+
|
|
743
|
+
log_info "PRD generation complete"
|
|
744
|
+
log_info "Found ${#TASKS[@]} tasks to execute"
|
|
745
|
+
|
|
746
|
+
execute_task_loop "$ralph_dir"
|
|
747
|
+
|
|
748
|
+
log_info "ralph-run.sh initialized successfully"
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
main "$@"
|
package/scripts/setup.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
console.log('Setting up spec-and-loop...');
|
|
8
|
+
|
|
9
|
+
// Get the installation directory
|
|
10
|
+
const installDir = __dirname;
|
|
11
|
+
const ralphRunScript = path.join(installDir, 'ralph-run.sh');
|
|
12
|
+
|
|
13
|
+
console.log(`Installation directory: ${installDir}`);
|
|
14
|
+
|
|
15
|
+
// Make ralph-run.sh executable
|
|
16
|
+
if (fs.existsSync(ralphRunScript)) {
|
|
17
|
+
try {
|
|
18
|
+
execSync(`chmod +x "${ralphRunScript}"`);
|
|
19
|
+
console.log('✓ Made ralph-run.sh executable');
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.warn(`Could not make ralph-run.sh executable: ${err.message}`);
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
console.error(`Error: ralph-run.sh not found at ${ralphRunScript}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('');
|
|
29
|
+
console.log('spec-and-loop setup complete!');
|
|
30
|
+
console.log('');
|
|
31
|
+
console.log('Usage:');
|
|
32
|
+
console.log(' cd /path/to/your/project');
|
|
33
|
+
console.log(' openspec init # Initialize OpenSpec');
|
|
34
|
+
console.log(' openspec new <name> # Create a new change');
|
|
35
|
+
console.log(' openspec ff <name> # Fast-forward artifacts');
|
|
36
|
+
console.log(' ralph-run --change <name> # Run ralph loop');
|
|
37
|
+
console.log(' ralph-run # Auto-detect change');
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log('Prerequisites:');
|
|
40
|
+
console.log(' - openspec CLI: npm install -g openspec');
|
|
41
|
+
console.log(' - opencode CLI: npm install -g opencode');
|
|
42
|
+
console.log(' - jq CLI: apt install jq / brew install jq');
|
|
43
|
+
console.log(' - git: git init');
|