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 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 "$@"
@@ -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');