spec-and-loop 1.0.6 → 1.0.8

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 CHANGED
@@ -213,12 +213,49 @@ openspec new another-feature
213
213
  | **Auto-Resume** | Interrupted? Run again—picks up where left off |
214
214
  | **Context Injection** | Inject custom instructions during execution |
215
215
 
216
+ ## Testing
217
+
218
+ Spec-and-loop includes a comprehensive test suite to ensure reliability and cross-platform compatibility.
219
+
220
+ ### Running Tests
221
+
222
+ ```bash
223
+ # Run all tests
224
+ npm test
225
+
226
+ # Run unit tests only
227
+ npm run test:unit
228
+
229
+ # Run integration tests only
230
+ npm run test:integration
231
+
232
+ # Run tests with coverage
233
+ npm run test:coverage
234
+
235
+ # Run shellcheck linting
236
+ npm run lint
237
+ ```
238
+
239
+ ### Test Requirements
240
+
241
+ To run tests, you'll need:
242
+ - **Node.js** (>= 24.0.0)
243
+ - **Bats** (Bash testing framework): `apt install bats-core` or `brew install bats-core`
244
+ - **Shellcheck** (Bash linting): `apt install shellcheck` or `brew install shellcheck`
245
+
246
+ ### CI/CD
247
+
248
+ Tests run automatically on every push and pull request via GitHub Actions on both Linux and macOS.
249
+
250
+ **For more details, see [TESTING.md](./TESTING.md)**
251
+
216
252
  ## Next Steps
217
253
 
218
254
  1. **Read the full README.md** for detailed documentation
219
255
  2. **Try a real feature** in your project
220
256
  3. **Explore the .ralph/** directory to see internal state
221
257
  4. **Check out .hidden/** directory for advanced guides
258
+ 5. **Review TESTING.md** for testing guidelines
222
259
 
223
260
  ## Resources
224
261
 
package/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  OpenSpec + Ralph Loop integration for iterative development with opencode.
4
4
 
5
+ ![CI Status](https://img.shields.io/github/actions/workflow/status/ncheaz/spec-and-loop/test.yml)
6
+ ![Coverage](https://img.shields.io/badge/coverage-0%25-red)
7
+ [![npm version](https://badge.fury.io/js/spec-and-loop.svg)](https://badge.fury.io/js/spec-and-loop)
8
+
5
9
  **[🚀 Quick Start Guide](./QUICKSTART.md)** - Get up and running in 5 minutes!
6
10
 
7
11
  ## Why This Exists
@@ -56,6 +60,114 @@ For detailed step-by-step instructions, see [QUICKSTART.md](./QUICKSTART.md).
56
60
 
57
61
  <!-- Duplicate Quick Start removed; see QUICKSTART.md for full instructions -->
58
62
 
63
+ ## Testing
64
+
65
+ Spec-and-loop includes a comprehensive test suite to ensure reliability and cross-platform compatibility.
66
+
67
+ **[📋 Testing Guide](./TESTING.md)** - Detailed instructions for running tests
68
+
69
+ ### Quick Test Commands
70
+
71
+ ```bash
72
+ # Run all tests
73
+ npm test
74
+
75
+ # Run unit tests only
76
+ npm run test:unit
77
+
78
+ # Run integration tests only
79
+ npm run test:integration
80
+
81
+ # Run tests with coverage
82
+ npm run test:coverage
83
+
84
+ # Run shellcheck linting
85
+ npm run lint
86
+ ```
87
+
88
+ ### CI/CD Status
89
+
90
+ - **Linux**: Tests run on Ubuntu (latest)
91
+ - **macOS**: Tests run on macOS (latest)
92
+ - **Node.js**: Tested on Node.js 24
93
+
94
+ All tests are run automatically via GitHub Actions on every push and pull request.
95
+
96
+ ### CI/CD Workflow
97
+
98
+ The CI/CD pipeline is defined in `.github/workflows/test.yml` and performs the following steps:
99
+
100
+ 1. **Checkout Code**: Pulls the latest code from the repository
101
+ 2. **Setup Node.js**: Installs Node.js version 24 with npm caching
102
+ 3. **Install System Dependencies**:
103
+ - Linux: `apt-get install bats-core jq shellcheck`
104
+ - macOS: `brew install bats-core jq shellcheck`
105
+ 4. **Install npm Dependencies**: Runs `npm ci` to install dependencies
106
+ 5. **Install Global CLIs**: Installs openspec, ralph, and opencode globally
107
+ 6. **Run Shellcheck Linting**: Checks bash scripts for errors and best practices
108
+ 7. **Run Unit Tests**: Executes bash and JavaScript unit tests
109
+ 8. **Run Integration Tests**: Validates full workflow end-to-end
110
+ 9. **Upload Artifacts**: Uploads test logs and coverage reports
111
+
112
+ ### Triggering CI/CD
113
+
114
+ The workflow runs automatically on:
115
+ - Push to `main` or `develop` branches
116
+ - Pull requests to `main` or `develop` branches
117
+ - Manual trigger via GitHub Actions UI
118
+
119
+ To manually trigger:
120
+ 1. Go to Actions tab in GitHub
121
+ 2. Select "Test Suite" workflow
122
+ 3. Click "Run workflow"
123
+ 4. Select branch and test suite (all/unit/integration)
124
+
125
+ ### Troubleshooting CI/CD
126
+
127
+ **Tests Failing on One Platform**
128
+
129
+ If tests pass on Linux but fail on macOS (or vice versa):
130
+ - Check for platform-specific command differences (GNU vs BSD tools)
131
+ - Review platform-specific tests in `test-symlink-linux.bats`, `test-symlink-macos.bats`, etc.
132
+ - Verify stat, md5sum/md5, and other commands use correct flags
133
+
134
+ **Coverage Below Threshold**
135
+
136
+ If coverage drops below 80%:
137
+ - Review coverage reports uploaded as artifacts
138
+ - Identify which functions lost coverage
139
+ - Add tests to cover the missing code paths
140
+
141
+ **Linting Failures**
142
+
143
+ If shellcheck finds issues:
144
+ - Review the warnings in the CI logs
145
+ - Fix the issues locally: `npm run lint`
146
+ - Commit the fixes
147
+
148
+ **Timeout Issues**
149
+
150
+ If tests timeout:
151
+ - Integration tests may take longer than expected
152
+ - Check for infinite loops or hanging processes
153
+ - Review test fixture setup/teardown
154
+
155
+ **Artifact Access**
156
+
157
+ Download test logs and coverage reports:
158
+ 1. Go to the failed workflow run
159
+ 2. Scroll to "Artifacts" section
160
+ 3. Download relevant artifacts (test logs, coverage reports)
161
+ 4. Analyze locally to identify issues
162
+
163
+ ### Test Coverage
164
+
165
+ Critical functions have >80% test coverage. View detailed coverage reports:
166
+ ```bash
167
+ npm run test:coverage
168
+ open coverage/index.html
169
+ ```
170
+
59
171
  ## Prerequisites
60
172
 
61
173
  Before using spec-and-loop, ensure you have:
package/package.json CHANGED
@@ -1,17 +1,51 @@
1
1
  {
2
2
  "name": "spec-and-loop",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "OpenSpec + Ralph Loop integration for iterative development with opencode",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "ralph-run": "bin/ralph-run"
8
8
  },
9
9
  "scripts": {
10
- "postinstall": "node scripts/setup.js"
10
+ "postinstall": "node scripts/setup.js",
11
+ "test": "npm run test:unit && npm run test:integration",
12
+ "test:unit": "bats tests/unit/bash/*.bats && npm run test:js",
13
+ "test:js": "jest tests/unit/javascript",
14
+ "test:integration": "bats tests/integration/*.bats",
15
+ "test:watch": "npm run test:js -- --watch",
16
+ "test:coverage": "npm run test:unit -- --coverage",
17
+ "lint": "shellcheck scripts/*.sh"
11
18
  },
12
19
  "dependencies": {
13
- "@fission-ai/openspec": "latest",
14
- "@th0rgal/ralph-wiggum": "latest"
20
+ "@fission-ai/openspec": "1.2.0",
21
+ "@th0rgal/ralph-wiggum": "1.2.2"
22
+ },
23
+ "devDependencies": {
24
+ "@types/jest": "^29.5.0",
25
+ "bats": "^1.13.0",
26
+ "jest": "^29.7.0"
27
+ },
28
+ "jest": {
29
+ "testEnvironment": "node",
30
+ "collectCoverage": true,
31
+ "coverageDirectory": "coverage",
32
+ "coverageReporters": [
33
+ "text",
34
+ "lcov",
35
+ "html",
36
+ "json"
37
+ ],
38
+ "coverageThreshold": {
39
+ "global": {
40
+ "branches": 80,
41
+ "functions": 80,
42
+ "lines": 80,
43
+ "statements": 80
44
+ }
45
+ },
46
+ "testMatch": [
47
+ "**/tests/unit/javascript/**/*.test.js"
48
+ ]
15
49
  },
16
50
  "keywords": [
17
51
  "openspec",
@@ -2,20 +2,88 @@
2
2
 
3
3
  set -e
4
4
 
5
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ # Detect OS for cross-platform compatibility
6
+ detect_os() {
7
+ case "$(uname -s)" in
8
+ Linux*) OS="Linux";;
9
+ Darwin*) OS="macOS";;
10
+ *) OS="Unknown";;
11
+ esac
12
+ }
13
+
14
+ detect_os
15
+
16
+ # Cross-platform file modification time
17
+ get_file_mtime() {
18
+ local file="$1"
19
+ if [[ "$OS" == "macOS" ]]; then
20
+ stat -f %m "$file" 2>/dev/null || echo 0
21
+ else
22
+ stat -c %Y "$file" 2>/dev/null || echo 0
23
+ fi
24
+ }
25
+
26
+ # Cross-platform file modification time display
27
+ get_file_mtime_display() {
28
+ local file="$1"
29
+ if [[ "$OS" == "macOS" ]]; then
30
+ stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" "$file" 2>/dev/null || echo "Unknown"
31
+ else
32
+ stat -c "%y" "$file" 2>/dev/null | cut -d'.' -f1 || echo "Unknown"
33
+ fi
34
+ }
35
+
36
+ resolve_project_root() {
37
+ local git_root=""
38
+ git_root=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
39
+
40
+ if [[ -n "$git_root" ]]; then
41
+ printf "%s" "$git_root"
42
+ else
43
+ pwd
44
+ fi
45
+ }
46
+
47
+ auto_detect_change() {
48
+ local project_root="$1"
49
+ local changes_dir="$project_root/openspec/changes"
50
+
51
+ if [[ ! -d "$changes_dir" ]]; then
52
+ echo ""
53
+ return 1
54
+ fi
55
+
56
+ local latest_change=""
57
+ local latest_time=0
58
+
59
+ for change_dir in "$changes_dir"/*; do
60
+ if [[ -d "$change_dir" && -f "$change_dir/tasks.md" ]]; then
61
+ local mod_time
62
+ mod_time=$(get_file_mtime "$change_dir/tasks.md")
63
+
64
+ if [[ "$mod_time" -gt "$latest_time" ]]; then
65
+ latest_time="$mod_time"
66
+ latest_change=$(basename "$change_dir")
67
+ fi
68
+ fi
69
+ done
70
+
71
+ printf "%s" "$latest_change"
72
+ }
73
+
74
+ PROJECT_ROOT="$(resolve_project_root)"
6
75
  CHANGE_NAME="${1:-auto-detect}"
7
76
 
8
77
  if [[ "$CHANGE_NAME" == "auto-detect" ]]; then
9
- # Auto-detect most recent change
10
- CHANGE_NAME=$(ls -t openspec/changes/ 2>/dev/null | head -1)
78
+ CHANGE_NAME=$(auto_detect_change "$PROJECT_ROOT")
11
79
  if [[ -z "$CHANGE_NAME" ]]; then
12
- echo "No changes found in openspec/changes/"
80
+ echo "No changes found in $PROJECT_ROOT/openspec/changes/"
13
81
  exit 1
14
82
  fi
15
83
  fi
16
84
 
17
- TASKS_FILE="$SCRIPT_DIR/../openspec/changes/$CHANGE_NAME/tasks.md"
18
- RALPH_STATE="$SCRIPT_DIR/../.ralph/ralph-loop.state.json"
85
+ TASKS_FILE="$PROJECT_ROOT/openspec/changes/$CHANGE_NAME/tasks.md"
86
+ RALPH_STATE="$PROJECT_ROOT/.ralph/ralph-loop.state.json"
19
87
 
20
88
  if [[ ! -f "$TASKS_FILE" ]]; then
21
89
  echo "Tasks file not found: $TASKS_FILE"
@@ -85,7 +153,7 @@ while true; do
85
153
  echo ""
86
154
 
87
155
  # File status
88
- echo " Last Updated: $(stat -c "%y" "$TASKS_FILE" 2>/dev/null | cut -d'.' -f1)"
156
+ echo " Last Updated: $(get_file_mtime_display "$TASKS_FILE")"
89
157
  echo ""
90
158
 
91
159
  echo "======================================================================="
@@ -9,6 +9,116 @@ if [[ -d "$HOME/.bun/bin" ]]; then
9
9
  export PATH="$HOME/.bun/bin:$PATH"
10
10
  fi
11
11
 
12
+ # Detect OS for cross-platform compatibility
13
+ detect_os() {
14
+ case "$(uname -s)" in
15
+ Linux*) OS="Linux";;
16
+ Darwin*) OS="macOS";;
17
+ *) OS="Unknown";;
18
+ esac
19
+ }
20
+
21
+ detect_os
22
+
23
+ # Cross-platform file modification time
24
+ get_file_mtime() {
25
+ local file="$1"
26
+ if [[ "$OS" == "macOS" ]]; then
27
+ stat -f %m "$file" 2>/dev/null || echo 0
28
+ else
29
+ stat -c %Y "$file" 2>/dev/null || echo 0
30
+ fi
31
+ }
32
+
33
+ # Cross-platform MD5 hash
34
+ get_file_md5() {
35
+ local file="$1"
36
+ if command -v md5sum >/dev/null 2>&1; then
37
+ md5sum "$file" | cut -d' ' -f1
38
+ elif command -v md5 >/dev/null 2>&1; then
39
+ md5 -q "$file"
40
+ else
41
+ echo "0"
42
+ fi
43
+ }
44
+
45
+ # Cross-platform realpath with fallback
46
+ get_realpath() {
47
+ local path="$1"
48
+ if command -v realpath >/dev/null 2>&1; then
49
+ realpath "$path" 2>/dev/null || echo ""
50
+ elif readlink -f / >/dev/null 2>&1; then
51
+ readlink -f "$path" 2>/dev/null || echo ""
52
+ else
53
+ # Fallback for systems without realpath
54
+ local dir
55
+ dir=$(cd "$(dirname "$path")" 2>/dev/null && pwd -P || echo "")
56
+ if [[ -n "$dir" ]]; then
57
+ echo "$dir/$(basename "$path")"
58
+ else
59
+ echo ""
60
+ fi
61
+ fi
62
+ }
63
+
64
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
65
+ LOCAL_NODE_BIN="$SCRIPT_DIR/../node_modules/.bin"
66
+ BUNDLED_RALPH_JS="$SCRIPT_DIR/../node_modules/@th0rgal/ralph-wiggum/bin/ralph.js"
67
+ RALPH_CMD=()
68
+
69
+ if [[ -d "$LOCAL_NODE_BIN" ]]; then
70
+ case ":$PATH:" in
71
+ *":$LOCAL_NODE_BIN:"*) ;;
72
+ *) export PATH="$LOCAL_NODE_BIN:$PATH" ;;
73
+ esac
74
+ fi
75
+
76
+ get_temp_root() {
77
+ local temp_root="${TMPDIR:-/tmp}"
78
+ temp_root="${temp_root%/}"
79
+
80
+ if [[ -z "$temp_root" ]]; then
81
+ temp_root="/tmp"
82
+ fi
83
+
84
+ printf "%s" "$temp_root"
85
+ }
86
+
87
+ make_temp_dir() {
88
+ local prefix="${1:-ralph-run}"
89
+ local temp_root
90
+ temp_root=$(get_temp_root)
91
+
92
+ local temp_dir=""
93
+ temp_dir=$(mktemp -d "${temp_root}/${prefix}-XXXXXX" 2>/dev/null) || \
94
+ temp_dir=$(mktemp -d -t "$prefix" 2>/dev/null) || \
95
+ temp_dir=""
96
+
97
+ if [[ -n "$temp_dir" ]]; then
98
+ printf "%s" "$temp_dir"
99
+ return 0
100
+ fi
101
+
102
+ local fallback_dir="${temp_root}/${prefix}-$(date +"%Y%m%d_%H%M%S")-$$"
103
+ mkdir -p "$fallback_dir"
104
+ printf "%s" "$fallback_dir"
105
+ }
106
+
107
+ resolve_ralph_command() {
108
+ if command -v ralph >/dev/null 2>&1; then
109
+ RALPH_CMD=("ralph")
110
+ return 0
111
+ fi
112
+
113
+ if [[ -f "$BUNDLED_RALPH_JS" ]] && command -v node >/dev/null 2>&1; then
114
+ RALPH_CMD=("node" "$BUNDLED_RALPH_JS")
115
+ return 0
116
+ fi
117
+
118
+ RALPH_CMD=()
119
+ return 1
120
+ }
121
+
12
122
  CHANGE_NAME=""
13
123
  MAX_ITERATIONS=""
14
124
  ERROR_OCCURRED=false
@@ -74,7 +184,7 @@ PREREQUISITES:
74
184
  - Git repository (git init)
75
185
  - OpenSpec artifacts created (openspec init, opsx-new, opsx-ff)
76
186
  - ralph CLI installed (npm install -g @th0rgal/ralph-wiggum)
77
- - opencode CLI installed (npm install -g opencode)
187
+ - opencode CLI installed (npm install -g opencode-ai)
78
188
 
79
189
  EOF
80
190
  }
@@ -141,21 +251,29 @@ validate_git_repository() {
141
251
  validate_dependencies() {
142
252
  log_verbose "Validating dependencies..."
143
253
 
144
- # Check for ralph
145
- if ! command -v ralph &> /dev/null; then
254
+ if ! resolve_ralph_command; then
146
255
  log_error "ralph CLI not found."
147
256
  log_error "Please install open-ralph-wiggum: npm install -g @th0rgal/ralph-wiggum"
257
+ log_error "If spec-and-loop is globally installed, reinstalling it should also restore the bundled Ralph dependency."
148
258
  exit 1
149
259
  fi
150
- log_verbose "Found: ralph"
260
+ log_verbose "Found Ralph command: ${RALPH_CMD[*]}"
151
261
 
152
262
  # Check for opencode
153
263
  if ! command -v opencode &> /dev/null; then
154
264
  log_error "opencode CLI not found."
155
- log_error "Please install opencode: npm install -g opencode"
265
+ log_error "Please install opencode: npm install -g opencode-ai"
156
266
  exit 1
157
267
  fi
158
268
  log_verbose "Found: opencode"
269
+
270
+ # Check for jq
271
+ if ! command -v jq &> /dev/null; then
272
+ log_error "jq CLI not found."
273
+ log_error "Please install jq: brew install jq / apt-get install jq"
274
+ exit 1
275
+ fi
276
+ log_verbose "Found: jq"
159
277
 
160
278
  log_verbose "All dependencies validated"
161
279
  }
@@ -219,7 +337,7 @@ auto_detect_change() {
219
337
  if [[ -d "$change_dir" ]]; then
220
338
  local tasks_file="$change_dir/tasks.md"
221
339
  if [[ -f "$tasks_file" ]]; then
222
- local mod_time=$(stat -c %Y "$tasks_file" 2>/dev/null || echo 0)
340
+ local mod_time=$(get_file_mtime "$tasks_file")
223
341
  if [[ $mod_time -gt $latest_time ]]; then
224
342
  latest_time=$mod_time
225
343
  latest_change=$(basename "$change_dir")
@@ -323,7 +441,7 @@ parse_tasks() {
323
441
  TASKS_MD5=""
324
442
 
325
443
  if [[ -f "$tasks_file" ]]; then
326
- TASKS_MD5=$(md5sum "$tasks_file" | cut -d' ' -f1)
444
+ TASKS_MD5=$(get_file_md5 "$tasks_file")
327
445
  fi
328
446
 
329
447
  log_verbose "Parsing tasks from tasks.md..."
@@ -356,7 +474,7 @@ check_tasks_modified() {
356
474
  fi
357
475
 
358
476
  local current_md5
359
- current_md5=$(md5sum "$tasks_file" | cut -d' ' -f1)
477
+ current_md5=$(get_file_md5 "$tasks_file")
360
478
 
361
479
  if [[ "$current_md5" != "$original_md5" ]]; then
362
480
  return 0
@@ -566,15 +684,7 @@ sync_tasks_to_ralph() {
566
684
 
567
685
  # Resolve absolute path to tasks file (portable across Linux/macOS)
568
686
  local abs_tasks_file=""
569
- if command -v realpath >/dev/null 2>&1; then
570
- abs_tasks_file=$(realpath "$tasks_file" 2>/dev/null || true)
571
- elif readlink -f / >/dev/null 2>&1; then
572
- abs_tasks_file=$(readlink -f "$tasks_file" 2>/dev/null || true)
573
- else
574
- local _tdir
575
- _tdir=$(cd "$(dirname "$tasks_file")" 2>/dev/null && pwd -P || echo "")
576
- abs_tasks_file="$_tdir/$(basename "$tasks_file")"
577
- fi
687
+ abs_tasks_file=$(get_realpath "$tasks_file")
578
688
 
579
689
  # Clean up old Ralph tasks file in change directory if exists
580
690
  if [[ -f "$old_ralph_tasks_file" ]]; then
@@ -589,16 +699,7 @@ sync_tasks_to_ralph() {
589
699
  if [[ -L "$ralph_tasks_file" ]]; then
590
700
  log_verbose "Symlink exists, ensuring it points to correct location"
591
701
  local current_target=""
592
- if command -v realpath >/dev/null 2>&1; then
593
- current_target=$(realpath "$ralph_tasks_file" 2>/dev/null || echo "")
594
- elif readlink -f / >/dev/null 2>&1; then
595
- current_target=$(readlink -f "$ralph_tasks_file" 2>/dev/null || echo "")
596
- else
597
- current_target=$(readlink "$ralph_tasks_file" 2>/dev/null || echo "")
598
- if [[ -n "$current_target" && "$current_target" != /* ]]; then
599
- current_target="$(cd "$(dirname "$ralph_tasks_file")" && pwd -P)/$current_target"
600
- fi
601
- fi
702
+ current_target=$(get_realpath "$ralph_tasks_file")
602
703
 
603
704
  if [[ "$current_target" != "$abs_tasks_file" ]]; then
604
705
  log_verbose "Updating symlink to point to new change directory"
@@ -625,7 +726,7 @@ create_prompt_template() {
625
726
  log_verbose "Creating custom prompt template..."
626
727
 
627
728
  local abs_change_dir
628
- abs_change_dir=$(realpath "$change_dir" 2>/dev/null)
729
+ abs_change_dir=$(get_realpath "$change_dir")
629
730
 
630
731
  cat > "$template_file" << 'EOF'
631
732
  # Ralph Wiggum Task Execution - Iteration {{iteration}} / {{max_iterations}}
@@ -819,11 +920,9 @@ setup_output_capture() {
819
920
 
820
921
  log_verbose "Setting up output capture..."
821
922
 
822
- # Create timestamped output directory in /tmp
823
- local timestamp=$(date +"%Y%m%d_%H%M%S")
824
- local output_dir="/tmp/ralph-run-$timestamp"
825
-
826
- mkdir -p "$output_dir"
923
+ # Use the system temp directory so macOS and Linux both work naturally.
924
+ local output_dir
925
+ output_dir=$(make_temp_dir "ralph-run")
827
926
  log_info "Output directory: $output_dir"
828
927
 
829
928
  # Store output directory path in Ralph directory for reference
@@ -835,8 +934,11 @@ setup_output_capture() {
835
934
  cleanup_old_output() {
836
935
  log_verbose "Cleaning up old Ralph output directories..."
837
936
 
937
+ local temp_root
938
+ temp_root=$(get_temp_root)
939
+
838
940
  # Keep last 3 Ralph output directories, delete older ones
839
- find /tmp -type d -name "ralph-run-*" -mtime +7d 2>/dev/null | while read old_dir; do
941
+ find "$temp_root" -type d -name "ralph-run*" -mtime +7 2>/dev/null | while IFS= read -r old_dir; do
840
942
  log_verbose "Removing old output directory: $old_dir"
841
943
  rm -rf "$old_dir"
842
944
  done
@@ -851,7 +953,7 @@ execute_ralph_loop() {
851
953
  log_info "Max iterations: $max_iterations"
852
954
  log_info "Change directory: $change_dir"
853
955
 
854
- if ! command -v ralph &> /dev/null; then
956
+ if ! resolve_ralph_command; then
855
957
  log_error "ralph CLI not found."
856
958
  log_error "Please install open-ralph-wiggum: npm install -g @th0rgal/ralph-wiggum"
857
959
  return 1
@@ -897,7 +999,7 @@ execute_ralph_loop() {
897
999
 
898
1000
  # Run Ralph and capture output to both console and files
899
1001
  {
900
- ralph --prompt-file "$ralph_dir/PRD.md" \
1002
+ "${RALPH_CMD[@]}" --prompt-file "$ralph_dir/PRD.md" \
901
1003
  --agent opencode \
902
1004
  --tasks \
903
1005
  --max-iterations "$max_iterations" \
package/scripts/setup.js CHANGED
@@ -38,6 +38,6 @@ console.log(' ralph-run # Auto-detect change');
38
38
  console.log('');
39
39
  console.log('Prerequisites:');
40
40
  console.log(' - openspec CLI: npm install -g openspec');
41
- console.log(' - opencode CLI: npm install -g opencode');
41
+ console.log(' - opencode CLI: npm install -g opencode-ai');
42
42
  console.log(' - jq CLI: apt install jq / brew install jq');
43
43
  console.log(' - git: git init');