spec-and-loop 1.0.6 → 1.0.7
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 +22 -1
- package/scripts/ralph-run.sh +58 -23
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.7",
|
|
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,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
set -e
|
|
4
4
|
|
|
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 display
|
|
17
|
+
get_file_mtime_display() {
|
|
18
|
+
local file="$1"
|
|
19
|
+
if [[ "$OS" == "macOS" ]]; then
|
|
20
|
+
stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" "$file" 2>/dev/null || echo "Unknown"
|
|
21
|
+
else
|
|
22
|
+
stat -c "%y" "$file" 2>/dev/null | cut -d'.' -f1 || echo "Unknown"
|
|
23
|
+
fi
|
|
24
|
+
}
|
|
25
|
+
|
|
5
26
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
27
|
CHANGE_NAME="${1:-auto-detect}"
|
|
7
28
|
|
|
@@ -85,7 +106,7 @@ while true; do
|
|
|
85
106
|
echo ""
|
|
86
107
|
|
|
87
108
|
# File status
|
|
88
|
-
echo " Last Updated: $(
|
|
109
|
+
echo " Last Updated: $(get_file_mtime_display "$TASKS_FILE")"
|
|
89
110
|
echo ""
|
|
90
111
|
|
|
91
112
|
echo "======================================================================="
|
package/scripts/ralph-run.sh
CHANGED
|
@@ -9,6 +9,58 @@ 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
|
+
|
|
12
64
|
CHANGE_NAME=""
|
|
13
65
|
MAX_ITERATIONS=""
|
|
14
66
|
ERROR_OCCURRED=false
|
|
@@ -219,7 +271,7 @@ auto_detect_change() {
|
|
|
219
271
|
if [[ -d "$change_dir" ]]; then
|
|
220
272
|
local tasks_file="$change_dir/tasks.md"
|
|
221
273
|
if [[ -f "$tasks_file" ]]; then
|
|
222
|
-
local mod_time=$(
|
|
274
|
+
local mod_time=$(get_file_mtime "$tasks_file")
|
|
223
275
|
if [[ $mod_time -gt $latest_time ]]; then
|
|
224
276
|
latest_time=$mod_time
|
|
225
277
|
latest_change=$(basename "$change_dir")
|
|
@@ -323,7 +375,7 @@ parse_tasks() {
|
|
|
323
375
|
TASKS_MD5=""
|
|
324
376
|
|
|
325
377
|
if [[ -f "$tasks_file" ]]; then
|
|
326
|
-
TASKS_MD5=$(
|
|
378
|
+
TASKS_MD5=$(get_file_md5 "$tasks_file")
|
|
327
379
|
fi
|
|
328
380
|
|
|
329
381
|
log_verbose "Parsing tasks from tasks.md..."
|
|
@@ -356,7 +408,7 @@ check_tasks_modified() {
|
|
|
356
408
|
fi
|
|
357
409
|
|
|
358
410
|
local current_md5
|
|
359
|
-
current_md5=$(
|
|
411
|
+
current_md5=$(get_file_md5 "$tasks_file")
|
|
360
412
|
|
|
361
413
|
if [[ "$current_md5" != "$original_md5" ]]; then
|
|
362
414
|
return 0
|
|
@@ -566,15 +618,7 @@ sync_tasks_to_ralph() {
|
|
|
566
618
|
|
|
567
619
|
# Resolve absolute path to tasks file (portable across Linux/macOS)
|
|
568
620
|
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
|
|
621
|
+
abs_tasks_file=$(get_realpath "$tasks_file")
|
|
578
622
|
|
|
579
623
|
# Clean up old Ralph tasks file in change directory if exists
|
|
580
624
|
if [[ -f "$old_ralph_tasks_file" ]]; then
|
|
@@ -589,16 +633,7 @@ sync_tasks_to_ralph() {
|
|
|
589
633
|
if [[ -L "$ralph_tasks_file" ]]; then
|
|
590
634
|
log_verbose "Symlink exists, ensuring it points to correct location"
|
|
591
635
|
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
|
|
636
|
+
current_target=$(get_realpath "$ralph_tasks_file")
|
|
602
637
|
|
|
603
638
|
if [[ "$current_target" != "$abs_tasks_file" ]]; then
|
|
604
639
|
log_verbose "Updating symlink to point to new change directory"
|
|
@@ -625,7 +660,7 @@ create_prompt_template() {
|
|
|
625
660
|
log_verbose "Creating custom prompt template..."
|
|
626
661
|
|
|
627
662
|
local abs_change_dir
|
|
628
|
-
abs_change_dir=$(
|
|
663
|
+
abs_change_dir=$(get_realpath "$change_dir")
|
|
629
664
|
|
|
630
665
|
cat > "$template_file" << 'EOF'
|
|
631
666
|
# Ralph Wiggum Task Execution - Iteration {{iteration}} / {{max_iterations}}
|