spec-and-loop 2.1.0 → 2.1.2
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 +29 -16
- package/README.md +40 -38
- package/lib/mini-ralph/errors.js +118 -25
- package/lib/mini-ralph/invoker.js +131 -26
- package/lib/mini-ralph/prompt.js +9 -0
- package/lib/mini-ralph/runner.js +448 -141
- package/lib/mini-ralph/state.js +138 -1
- package/lib/mini-ralph/status.js +142 -10
- package/package.json +5 -5
- package/scripts/ralph-run.sh +9 -38
- package/scripts/setup.js +4 -3
package/QUICKSTART.md
CHANGED
|
@@ -1,33 +1,37 @@
|
|
|
1
1
|
# Quick Start Guide
|
|
2
2
|
|
|
3
|
-
> **Version Compatibility:** OpenSpec 1.2.0 | spec-and-loop 2.0.0
|
|
4
|
-
|
|
5
3
|
Get up and running with **spec-and-loop** in 5 minutes!
|
|
6
4
|
|
|
5
|
+
> **Compatibility note:** Examples assume recent releases of `spec-and-loop`,
|
|
6
|
+
> `@fission-ai/openspec`, and `opencode-ai`. `Node.js >=24` is required. The
|
|
7
|
+
> supported OS contract is Linux and macOS.
|
|
8
|
+
|
|
7
9
|
## Prerequisites
|
|
8
10
|
|
|
9
11
|
Install these tools (one-time setup):
|
|
10
12
|
|
|
11
13
|
```bash
|
|
12
|
-
# 1.
|
|
13
|
-
|
|
14
|
+
# 1. Ensure Node.js >=24
|
|
15
|
+
node --version
|
|
16
|
+
|
|
17
|
+
# 2. Install openspec (OpenSpec CLI)
|
|
18
|
+
npm install -g @fission-ai/openspec
|
|
14
19
|
|
|
15
|
-
#
|
|
20
|
+
# 3. Install opencode (agentic coding assistant)
|
|
16
21
|
npm install -g opencode-ai
|
|
17
22
|
|
|
18
|
-
#
|
|
23
|
+
# 4. Install jq (command-line JSON processor)
|
|
19
24
|
# Ubuntu/Debian:
|
|
20
25
|
sudo apt install jq
|
|
21
26
|
|
|
22
27
|
# macOS:
|
|
23
28
|
brew install jq
|
|
24
29
|
|
|
25
|
-
#
|
|
30
|
+
# 5. Git (if not already installed)
|
|
26
31
|
git init
|
|
27
32
|
```
|
|
28
33
|
|
|
29
|
-
> **Note:**
|
|
30
|
-
> No external `ralph` CLI needed — `spec-and-loop` includes its own internal
|
|
34
|
+
> **Note:** No external `ralph` CLI is needed - `spec-and-loop` includes its own internal
|
|
31
35
|
> mini Ralph loop engine. Just install `opencode` and you're ready to go. The
|
|
32
36
|
> runtime prompt is self-contained and does not depend on editor-specific slash
|
|
33
37
|
> commands or local-only skills.
|
|
@@ -35,7 +39,7 @@ git init
|
|
|
35
39
|
## Installation
|
|
36
40
|
|
|
37
41
|
```bash
|
|
38
|
-
npm install -g spec-and-loop
|
|
42
|
+
npm install -g spec-and-loop
|
|
39
43
|
```
|
|
40
44
|
|
|
41
45
|
## Quick Demo (5 Minutes)
|
|
@@ -65,7 +69,7 @@ ralph-run --change add-hello-world
|
|
|
65
69
|
**That's it!** The script will:
|
|
66
70
|
- Read your OpenSpec artifacts (proposal, specs, design, tasks)
|
|
67
71
|
- Execute each task with full context using the internal mini Ralph engine
|
|
68
|
-
- Create a
|
|
72
|
+
- Create a runner-managed task commit when auto-commit is enabled and task-scoped staging succeeds
|
|
69
73
|
- Track progress in tasks.md
|
|
70
74
|
|
|
71
75
|
## What Just Happened?
|
|
@@ -77,8 +81,9 @@ ralph-run --change add-hello-world
|
|
|
77
81
|
- `tasks.md`: Implementation tasks as checkboxes
|
|
78
82
|
|
|
79
83
|
2. **Executed tasks** with opencode via mini Ralph
|
|
80
|
-
- Each task got full context (
|
|
81
|
-
-
|
|
84
|
+
- Each task got full context (loop-start PRD snapshot + fresh task snapshot + recent loop signals)
|
|
85
|
+
- Task completion and full-run completion are signaled with standalone promise lines: `<promise>READY_FOR_NEXT_TASK</promise>` and `<promise>COMPLETE</promise>`
|
|
86
|
+
- Runner-managed commits are created after completed tasks unless `--no-commit` is active
|
|
82
87
|
- Task checkboxes marked as complete
|
|
83
88
|
|
|
84
89
|
3. **Iterated** until all tasks done
|
|
@@ -89,7 +94,7 @@ ralph-run --change add-hello-world
|
|
|
89
94
|
## Verify Your Work
|
|
90
95
|
|
|
91
96
|
```bash
|
|
92
|
-
# Check the git history (
|
|
97
|
+
# Check the git history (runner-managed task commits by default)
|
|
93
98
|
git log --oneline
|
|
94
99
|
|
|
95
100
|
# See the change files
|
|
@@ -102,6 +107,13 @@ cat openspec/changes/add-hello-world/.ralph/PRD.md
|
|
|
102
107
|
ralph-run --status
|
|
103
108
|
```
|
|
104
109
|
|
|
110
|
+
`PRD.md` is a loop-start snapshot of `proposal.md`, `design.md`, and
|
|
111
|
+
`specs/*/spec.md`. During the run, `ralph-run` keeps refreshing `tasks.md`,
|
|
112
|
+
recent loop signals, and pending injected context each iteration, but it does
|
|
113
|
+
not regenerate `PRD.md` on every pass.
|
|
114
|
+
|
|
115
|
+
If you customize the prompt template, keep the promise tags on standalone lines so quoted or explanatory mentions do not advance the loop.
|
|
116
|
+
|
|
105
117
|
## Common Commands
|
|
106
118
|
|
|
107
119
|
### OpenSpec Commands
|
|
@@ -190,8 +202,8 @@ git diff HEAD~15 # See full implementation
|
|
|
190
202
|
|
|
191
203
|
**Solution:**
|
|
192
204
|
```bash
|
|
193
|
-
# Install
|
|
194
|
-
npm install -g @fission-ai/openspec
|
|
205
|
+
# Install OpenSpec
|
|
206
|
+
npm install -g @fission-ai/openspec
|
|
195
207
|
|
|
196
208
|
# Verify installation
|
|
197
209
|
openspec --version
|
|
@@ -481,5 +493,6 @@ Tests run automatically on every push and pull request via GitHub Actions on bot
|
|
|
481
493
|
|
|
482
494
|
- Check the **Troubleshooting** section above
|
|
483
495
|
- Review the **Full README.md** for detailed info
|
|
496
|
+
- Review [OPENSPEC-RALPH-BP.md](./OPENSPEC-RALPH-BP.md), [OPENSPEC-RALPH-WIGGUM-BOTW.md](./OPENSPEC-RALPH-WIGGUM-BOTW.md), and [RALPH-METHODOLOGY-ASSESSMENT.md](./RALPH-METHODOLOGY-ASSESSMENT.md) for methodology details
|
|
484
497
|
|
|
485
498
|
Happy coding!
|
package/README.md
CHANGED
|
@@ -6,28 +6,29 @@ OpenSpec + Ralph Loop integration for iterative development with opencode.
|
|
|
6
6
|

|
|
7
7
|
[](https://badge.fury.io/js/spec-and-loop.svg)
|
|
8
8
|
|
|
9
|
-
**Version:** spec-and-loop 2.0.0 + OpenSpec 1.2.0
|
|
10
|
-
|
|
11
9
|
**[Quick Start Guide](./QUICKSTART.md)** - Get up and running in 5 minutes!
|
|
12
10
|
|
|
13
11
|
## Why This Exists
|
|
14
12
|
|
|
15
|
-
OpenSpec provides excellent structure for planning (proposal → specs → design → tasks) but leaves execution manual. This package provides an iterative development loop
|
|
13
|
+
OpenSpec provides excellent structure for planning (proposal → specs → design → tasks) but leaves execution manual. This package provides an iterative development loop driven by an internal mini Ralph implementation that works with OpenCode and eliminates the need for any external Ralph runtime. When auto-commit is enabled, the runner owns task commits; when `--no-commit` is enabled, the prompt contract and runtime both leave changes uncommitted.
|
|
16
14
|
|
|
17
|
-
The runtime prompt is self-contained: it does not depend on Cursor-only slash
|
|
15
|
+
The runtime prompt is self-contained: it does not depend on Cursor-only slash
|
|
16
|
+
commands or editor-local skills.
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
Examples below assume current published releases of `spec-and-loop`,
|
|
19
|
+
`@fission-ai/openspec`, and `opencode-ai`. `Node.js >=24` is required.
|
|
20
|
+
The supported OS contract is Linux and macOS.
|
|
20
21
|
|
|
21
22
|
## Installation
|
|
22
23
|
|
|
23
24
|
```bash
|
|
24
|
-
npm install -g spec-and-loop
|
|
25
|
+
npm install -g spec-and-loop
|
|
25
26
|
```
|
|
26
27
|
|
|
27
28
|
**Prerequisites:** You need OpenSpec and the OpenCode AI agent installed:
|
|
28
29
|
|
|
29
30
|
```bash
|
|
30
|
-
npm install -g @fission-ai/openspec
|
|
31
|
+
npm install -g @fission-ai/openspec opencode-ai
|
|
31
32
|
```
|
|
32
33
|
|
|
33
34
|
Alternative OpenCode install methods:
|
|
@@ -63,8 +64,6 @@ For detailed step-by-step instructions, see [QUICKSTART.md](./QUICKSTART.md).
|
|
|
63
64
|
|
|
64
65
|
## Testing
|
|
65
66
|
|
|
66
|
-
*Testing suite for spec-and-loop 2.0.0*
|
|
67
|
-
|
|
68
67
|
Spec-and-loop includes a comprehensive test suite to ensure reliability and cross-platform compatibility.
|
|
69
68
|
|
|
70
69
|
**[Testing Guide](./TESTING.md)** - Detailed instructions for running tests
|
|
@@ -98,8 +97,6 @@ All tests are run automatically via GitHub Actions on every push and pull reques
|
|
|
98
97
|
|
|
99
98
|
## Prerequisites
|
|
100
99
|
|
|
101
|
-
*Required for spec-and-loop 2.0.0 with OpenSpec 1.2.0*
|
|
102
|
-
|
|
103
100
|
Before using spec-and-loop, ensure you have:
|
|
104
101
|
|
|
105
102
|
1. **Node.js** - For package installation (requires >=24.0.0)
|
|
@@ -107,9 +104,9 @@ Before using spec-and-loop, ensure you have:
|
|
|
107
104
|
node --version # Should be >=24.0.0
|
|
108
105
|
```
|
|
109
106
|
|
|
110
|
-
2. **openspec** - OpenSpec CLI for specification workflow
|
|
107
|
+
2. **openspec** - OpenSpec CLI for specification workflow
|
|
111
108
|
```bash
|
|
112
|
-
npm install -g @fission-ai/openspec
|
|
109
|
+
npm install -g @fission-ai/openspec
|
|
113
110
|
```
|
|
114
111
|
|
|
115
112
|
3. **opencode** - Agentic coding assistant
|
|
@@ -135,13 +132,15 @@ For complete installation instructions, see [QUICKSTART.md](./QUICKSTART.md).
|
|
|
135
132
|
|
|
136
133
|
## Commands
|
|
137
134
|
|
|
138
|
-
*Documentation applies to OpenSpec 1.2.0 and spec-and-loop 2.0.0*
|
|
139
|
-
|
|
140
135
|
### OpenSpec Commands
|
|
141
136
|
|
|
142
137
|
- `openspec init` - Initialize OpenSpec in current directory
|
|
143
138
|
- `openspec new change <name>` - Create a new change with artifact templates
|
|
144
|
-
- `openspec
|
|
139
|
+
- `openspec list` - List active changes
|
|
140
|
+
- `openspec status --change <name>` - Show artifact completion status for a change
|
|
141
|
+
- `openspec show <item-name>` - Display a change or spec in detail
|
|
142
|
+
- `openspec validate <item-name>` - Validate a change or spec
|
|
143
|
+
- `openspec archive <change-name>` - Archive a completed change
|
|
145
144
|
|
|
146
145
|
### Ralph Loop Commands
|
|
147
146
|
|
|
@@ -163,8 +162,6 @@ OBSERVABILITY AND CONTROL:
|
|
|
163
162
|
|
|
164
163
|
## How It Works
|
|
165
164
|
|
|
166
|
-
*Workflow for OpenSpec 1.2.0 + spec-and-loop 2.0.0*
|
|
167
|
-
|
|
168
165
|
### Step 1: Create Spec with OpenSpec
|
|
169
166
|
|
|
170
167
|
```bash
|
|
@@ -201,11 +198,12 @@ ralph-run --change my-feature
|
|
|
201
198
|
2. **PRD Generation**: Converts proposal + specs + design → PRD format for internal use
|
|
202
199
|
3. **Setup**: Creates .ralph directory, syncs tasks symlink, and sets up output capture
|
|
203
200
|
4. **Task Execution**: For each incomplete task:
|
|
204
|
-
- Generates context-rich prompt
|
|
201
|
+
- Generates a context-rich prompt from the invocation-time PRD snapshot plus a fresh task snapshot and recent loop signals
|
|
205
202
|
- Runs `opencode` with the prompt via the internal mini Ralph engine
|
|
206
203
|
- Captures output to temp directory for review and debugging
|
|
207
204
|
- Logs any errors to `.ralph/errors.md` with timestamps
|
|
208
|
-
-
|
|
205
|
+
- Expects standalone control lines: `<promise>READY_FOR_NEXT_TASK</promise>` for task completion and `<promise>COMPLETE</promise>` when all tasks are done
|
|
206
|
+
- Creates a runner-managed git commit for task-scoped changes when auto-commit is enabled; if the commit is blocked or fails, the anomaly is recorded in history and surfaced by `--status`
|
|
209
207
|
- Marks task complete in tasks.md
|
|
210
208
|
5. **Cleanup**: Automatically removes old output directories (older than 7 days)
|
|
211
209
|
6. **Completion**: All tasks done
|
|
@@ -228,8 +226,6 @@ ralph-run --add-context "Prefer async/await over callbacks"
|
|
|
228
226
|
|
|
229
227
|
## Example Workflow
|
|
230
228
|
|
|
231
|
-
*Example workflow for OpenSpec 1.2.0 and spec-and-loop 2.0.0*
|
|
232
|
-
|
|
233
229
|
```bash
|
|
234
230
|
# 1. Initialize OpenSpec in your project
|
|
235
231
|
cd my-web-app
|
|
@@ -264,7 +260,7 @@ git diff HEAD~15 # See full implementation
|
|
|
264
260
|
| **Agentic Execution** | opencode executes tasks with full context |
|
|
265
261
|
| **Iterative Loop** | Each task builds on previous commits |
|
|
266
262
|
| **Iteration Feedback** | Recent failures and no-progress iterations inform the next pass |
|
|
267
|
-
| **Granular History** |
|
|
263
|
+
| **Granular History** | Runner-managed commit per completed task when auto-commit succeeds |
|
|
268
264
|
| **Auto-Resume** | Interrupted? Run again — picks up where left off |
|
|
269
265
|
| **Context Injection** | `--add-context` injects guidance into the next iteration |
|
|
270
266
|
| **Loop Status** | `--status` shows active state, history, and struggle indicators |
|
|
@@ -275,8 +271,6 @@ git diff HEAD~15 # See full implementation
|
|
|
275
271
|
|
|
276
272
|
## Features
|
|
277
273
|
|
|
278
|
-
*Features available in spec-and-loop 2.0.0 with OpenSpec 1.2.0*
|
|
279
|
-
|
|
280
274
|
### Mini Ralph Loop Engine
|
|
281
275
|
|
|
282
276
|
`spec-and-loop` includes a first-party mini Ralph implementation (`lib/mini-ralph/`) that
|
|
@@ -313,8 +307,6 @@ this repository's OpenSpec-first workflow (multi-agent rotation, plugin toggles,
|
|
|
313
307
|
|
|
314
308
|
## Advanced Usage
|
|
315
309
|
|
|
316
|
-
*Advanced features for spec-and-loop 2.0.0*
|
|
317
|
-
|
|
318
310
|
### Context Injection
|
|
319
311
|
|
|
320
312
|
Inject custom instructions into the next iteration:
|
|
@@ -336,7 +328,7 @@ ralph-run --status
|
|
|
336
328
|
```
|
|
337
329
|
|
|
338
330
|
Shows: active loop state, current task, prompt summary, pending context, iteration history,
|
|
339
|
-
and struggle indicators if the loop appears stuck.
|
|
331
|
+
and struggle indicators if the loop appears stuck. Inactive runs are distinguished as completed or stopped-incomplete, and the latest unresolved auto-commit anomaly is shown when present.
|
|
340
332
|
|
|
341
333
|
### No-Commit Mode
|
|
342
334
|
|
|
@@ -346,6 +338,8 @@ Run without automatic git commits (useful for reviewing changes before committin
|
|
|
346
338
|
ralph-run --change my-feature --no-commit
|
|
347
339
|
```
|
|
348
340
|
|
|
341
|
+
In this mode the runner does not create commits, and the rendered prompt explicitly forbids the agent from running `git add` or `git commit`.
|
|
342
|
+
|
|
349
343
|
### Verbose Mode
|
|
350
344
|
|
|
351
345
|
For debugging:
|
|
@@ -360,6 +354,17 @@ ralph-run --verbose --change my-feature
|
|
|
360
354
|
cat openspec/changes/my-feature/.ralph/PRD.md
|
|
361
355
|
```
|
|
362
356
|
|
|
357
|
+
`PRD.md` is generated once when `ralph-run` starts and reused for the rest of
|
|
358
|
+
that run. Per-iteration freshness comes from re-reading `tasks.md`, recent loop
|
|
359
|
+
signals, and any pending `--add-context` injection.
|
|
360
|
+
|
|
361
|
+
If you customize the prompt template, preserve the standalone promise-line contract so only literal control lines advance the loop:
|
|
362
|
+
|
|
363
|
+
```text
|
|
364
|
+
<promise>READY_FOR_NEXT_TASK</promise>
|
|
365
|
+
<promise>COMPLETE</promise>
|
|
366
|
+
```
|
|
367
|
+
|
|
363
368
|
### Review Loop Output
|
|
364
369
|
|
|
365
370
|
```bash
|
|
@@ -385,8 +390,6 @@ ls openspec/changes/my-feature/.ralph/errors_*.md
|
|
|
385
390
|
|
|
386
391
|
## Architecture
|
|
387
392
|
|
|
388
|
-
*Architecture for spec-and-loop 2.0.0 with OpenSpec 1.2.0*
|
|
389
|
-
|
|
390
393
|
This package integrates:
|
|
391
394
|
- **OpenSpec**: Structured specification workflow
|
|
392
395
|
- **opencode**: Agentic coding assistant for task execution
|
|
@@ -395,7 +398,7 @@ This package integrates:
|
|
|
395
398
|
### Context Propagation
|
|
396
399
|
|
|
397
400
|
Each task execution includes:
|
|
398
|
-
- **
|
|
401
|
+
- **Invocation-time PRD snapshot**: Proposal, design, and spec content captured in `.ralph/PRD.md` when the current `ralph-run` invocation starts
|
|
399
402
|
- **Fresh task snapshot**: Raw `tasks.md` content plus the current task and completed-task summary rendered each iteration
|
|
400
403
|
- **Recent loop signals**: Compact reminders about prior failed or no-progress iterations
|
|
401
404
|
- **Pending context**: Any `--add-context` injection
|
|
@@ -420,7 +423,7 @@ openspec/changes/<name>/
|
|
|
420
423
|
│ └── api/
|
|
421
424
|
│ └── spec.md
|
|
422
425
|
└── .ralph/ # Internal loop state (auto-generated, per change)
|
|
423
|
-
├── PRD.md # Generated
|
|
426
|
+
├── PRD.md # Generated prompt snapshot from loop start
|
|
424
427
|
├── prompt-template.md # Template used for generating prompts
|
|
425
428
|
├── ralph-history.json # Iteration history and state
|
|
426
429
|
├── ralph-loop.state.json # Current loop state and iteration count
|
|
@@ -439,9 +442,7 @@ openspec/changes/<name>/
|
|
|
439
442
|
|
|
440
443
|
### Cross-Platform Support
|
|
441
444
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
`spec-and-loop` is designed to work seamlessly on both Linux and macOS. The script includes portable implementations for:
|
|
445
|
+
`spec-and-loop` is designed to work on both Linux and macOS. The script includes portable implementations for:
|
|
445
446
|
|
|
446
447
|
- **File modification times**: Uses `stat -f %m` on macOS and `stat -c %Y` on Linux
|
|
447
448
|
- **MD5 hashing**: Supports both `md5sum` (Linux) and `md5 -q` (macOS)
|
|
@@ -449,12 +450,10 @@ openspec/changes/<name>/
|
|
|
449
450
|
- **Temp directories**: Uses `TMPDIR` environment variable or `/tmp` as fallback
|
|
450
451
|
- **Cleanup**: Portable `find` and `rm` operations for old output directories
|
|
451
452
|
|
|
452
|
-
|
|
453
|
+
Windows is not currently part of the supported runtime contract.
|
|
453
454
|
|
|
454
455
|
## Troubleshooting
|
|
455
456
|
|
|
456
|
-
*Troubleshooting guide for spec-and-loop 2.0.0 with OpenSpec 1.2.0*
|
|
457
|
-
|
|
458
457
|
For common issues and solutions, see [QUICKSTART.md#troubleshooting](./QUICKSTART.md#troubleshooting).
|
|
459
458
|
|
|
460
459
|
**Quick fixes:**
|
|
@@ -477,6 +476,9 @@ export PATH="$PATH:$(npm root -g)/.bin"
|
|
|
477
476
|
|
|
478
477
|
- [OpenSpec](https://openspec.ai) - Structured specification workflow
|
|
479
478
|
- [opencode](https://opencode.ai) - Agentic coding assistant
|
|
479
|
+
- [Ralph-Friendly OpenSpec Best Practices](./OPENSPEC-RALPH-BP.md) - How to author loop-safe artifacts and tasks
|
|
480
|
+
- [OpenSpec + Ralph Wiggum BOTW](./OPENSPEC-RALPH-WIGGUM-BOTW.md) - Strengths, tradeoffs, and best-fit guidance
|
|
481
|
+
- [Ralph Methodology Assessment](./RALPH-METHODOLOGY-ASSESSMENT.md) - Repository-specific methodology review
|
|
480
482
|
|
|
481
483
|
## License
|
|
482
484
|
|
package/lib/mini-ralph/errors.js
CHANGED
|
@@ -35,6 +35,20 @@ function latest(ralphDir) {
|
|
|
35
35
|
return entries.length > 0 ? entries[0] : null;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
function matchIteration(entries, iteration) {
|
|
39
|
+
if (!Array.isArray(entries) || entries.length === 0) return null;
|
|
40
|
+
if (!Number.isFinite(iteration)) return null;
|
|
41
|
+
|
|
42
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
43
|
+
const entry = entries[index];
|
|
44
|
+
if (entry && entry.iteration === iteration) {
|
|
45
|
+
return entry;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
38
52
|
function append(ralphDir, entry) {
|
|
39
53
|
_ensureDir(ralphDir);
|
|
40
54
|
const file = errorsPath(ralphDir);
|
|
@@ -46,13 +60,15 @@ function append(ralphDir, entry) {
|
|
|
46
60
|
`Timestamp: ${timestamp}`,
|
|
47
61
|
`Iteration: ${entry.iteration}`,
|
|
48
62
|
`Task: ${entry.task}`,
|
|
49
|
-
`Exit Code: ${entry.exitCode}
|
|
63
|
+
...(entry.exitCode === null || entry.exitCode === undefined ? [] : [`Exit Code: ${entry.exitCode}`]),
|
|
64
|
+
...(entry.signal ? [`Signal: ${entry.signal}`] : []),
|
|
65
|
+
...(entry.failureStage ? [`Failure Stage: ${entry.failureStage}`] : []),
|
|
50
66
|
'',
|
|
51
67
|
'### stderr',
|
|
52
|
-
entry.stderr
|
|
68
|
+
_serializeStream(entry.stderr),
|
|
53
69
|
'',
|
|
54
70
|
'### stdout',
|
|
55
|
-
entry.stdout
|
|
71
|
+
_serializeStream(entry.stdout),
|
|
56
72
|
'',
|
|
57
73
|
].join('\n');
|
|
58
74
|
fs.writeFileSync(file, `${existing}${separator}${text}`, 'utf8');
|
|
@@ -83,30 +99,107 @@ function _ensureDir(ralphDir) {
|
|
|
83
99
|
function _readRawEntries(ralphDir) {
|
|
84
100
|
const file = errorsPath(ralphDir);
|
|
85
101
|
if (!fs.existsSync(file)) return [];
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
103
|
+
if (!content.trim()) return [];
|
|
104
|
+
|
|
105
|
+
const lines = content.replace(/\r\n/g, '\n').split('\n');
|
|
106
|
+
const entries = [];
|
|
107
|
+
let current = [];
|
|
108
|
+
|
|
109
|
+
for (const line of lines) {
|
|
110
|
+
if (line === '---') {
|
|
111
|
+
if (current.length > 0 && current.join('').trim()) {
|
|
112
|
+
entries.push(current.join('\n'));
|
|
113
|
+
}
|
|
114
|
+
current = [];
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (current.length === 0 && line === '') {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
current.push(line);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (current.length > 0 && current.join('').trim()) {
|
|
126
|
+
entries.push(current.join('\n'));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return entries;
|
|
89
130
|
}
|
|
90
131
|
|
|
91
132
|
function _parseEntry(entry) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
133
|
+
const normalized = entry.replace(/\r\n/g, '\n');
|
|
134
|
+
if (!normalized.trim()) return null;
|
|
135
|
+
|
|
136
|
+
const lines = normalized.split('\n');
|
|
137
|
+
const metadata = new Map();
|
|
138
|
+
const sections = { stderr: [], stdout: [] };
|
|
139
|
+
let currentSection = null;
|
|
140
|
+
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
if (line === '### stderr') {
|
|
143
|
+
currentSection = 'stderr';
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (line === '### stdout') {
|
|
148
|
+
currentSection = 'stdout';
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (currentSection) {
|
|
153
|
+
sections[currentSection].push(line);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const separatorIndex = line.indexOf(': ');
|
|
158
|
+
if (separatorIndex !== -1) {
|
|
159
|
+
metadata.set(line.slice(0, separatorIndex), line.slice(separatorIndex + 2));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const stderr = _parseSectionLines(sections.stderr);
|
|
164
|
+
const stdout = _parseSectionLines(sections.stdout);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
timestamp: (metadata.get('Timestamp') || '').trim(),
|
|
168
|
+
iteration: metadata.has('Iteration') ? Number(metadata.get('Iteration').trim()) : NaN,
|
|
169
|
+
task: (metadata.get('Task') || '').trim(),
|
|
170
|
+
exitCode: metadata.has('Exit Code') ? Number(metadata.get('Exit Code').trim()) : NaN,
|
|
171
|
+
signal: metadata.has('Signal') ? (metadata.get('Signal') || '').trim() : '',
|
|
172
|
+
failureStage: metadata.has('Failure Stage') ? (metadata.get('Failure Stage') || '').trim() : '',
|
|
173
|
+
stderr,
|
|
174
|
+
stdout,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function _serializeStream(value) {
|
|
179
|
+
if (value === undefined || value === null || value === '') return '';
|
|
180
|
+
return String(value)
|
|
181
|
+
.replace(/\r\n/g, '\n')
|
|
182
|
+
.split('\n')
|
|
183
|
+
.map((line) => ` ${line}`)
|
|
184
|
+
.join('\n');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function _parseSectionLines(lines) {
|
|
188
|
+
if (!lines.length) return '';
|
|
189
|
+
|
|
190
|
+
const sectionLines = [...lines];
|
|
191
|
+
while (sectionLines.length > 0 && sectionLines[sectionLines.length - 1] === '') {
|
|
192
|
+
sectionLines.pop();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!sectionLines.length) return '';
|
|
196
|
+
|
|
197
|
+
const isIndentedBlock = sectionLines.every((line) => line.startsWith(' '));
|
|
198
|
+
if (isIndentedBlock) {
|
|
199
|
+
return sectionLines.map((line) => line.slice(4)).join('\n');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return sectionLines.join('\n');
|
|
110
203
|
}
|
|
111
204
|
|
|
112
|
-
module.exports = { errorsPath, read, readEntries, count, latest, append, clear, archive };
|
|
205
|
+
module.exports = { errorsPath, read, readEntries, count, latest, matchIteration, append, clear, archive };
|