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 +37 -0
- package/README.md +112 -0
- package/package.json +38 -4
- package/scripts/ralph-monitor.sh +75 -7
- package/scripts/ralph-run.sh +138 -36
- package/scripts/setup.js +1 -1
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
|
+

|
|
6
|
+

|
|
7
|
+
[](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.
|
|
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": "
|
|
14
|
-
"@th0rgal/ralph-wiggum": "
|
|
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",
|
package/scripts/ralph-monitor.sh
CHANGED
|
@@ -2,20 +2,88 @@
|
|
|
2
2
|
|
|
3
3
|
set -e
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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="$
|
|
18
|
-
RALPH_STATE="$
|
|
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: $(
|
|
156
|
+
echo " Last Updated: $(get_file_mtime_display "$TASKS_FILE")"
|
|
89
157
|
echo ""
|
|
90
158
|
|
|
91
159
|
echo "======================================================================="
|
package/scripts/ralph-run.sh
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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=$(
|
|
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=$(
|
|
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=$(
|
|
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
|
-
|
|
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
|
-
|
|
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=$(
|
|
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
|
-
#
|
|
823
|
-
local
|
|
824
|
-
|
|
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
|
|
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 !
|
|
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
|
-
|
|
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');
|