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 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. Install openspec (OpenSpec CLI) - pinned to version 1.2.0
13
- npm install -g @fission-ai/openspec@1.2.0
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
- # 2. Install opencode (agentic coding assistant)
20
+ # 3. Install opencode (agentic coding assistant)
16
21
  npm install -g opencode-ai
17
22
 
18
- # 3. Install jq (command-line JSON processor)
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
- # 4. Git (if not already installed)
30
+ # 5. Git (if not already installed)
26
31
  git init
27
32
  ```
28
33
 
29
- > **Note:** This guide is for OpenSpec 1.2.0 and spec-and-loop 2.0.0.
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@2.0.0
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 git commit after each task (unless `--no-commit` is passed)
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 (proposal + specs + design + fresh task snapshot)
81
- - Git commits created after each task
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 (one commit per task!)
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 with pinned version (recommended)
194
- npm install -g @fission-ai/openspec@1.2.0
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
  ![Coverage](https://img.shields.io/badge/coverage-0%25-red)
7
7
  [![npm version](https://badge.fury.io/js/spec-and-loop.svg)](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 — execute → commit → repeat — driven by an internal mini Ralph implementation that works with OpenCode and eliminates the need for any external Ralph runtime.
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 commands or editor-local skills.
15
+ The runtime prompt is self-contained: it does not depend on Cursor-only slash
16
+ commands or editor-local skills.
18
17
 
19
- **Version Requirements:** This documentation applies to OpenSpec 1.2.0 and spec-and-loop 2.0.0.
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@2.0.0
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@1.2.0 opencode-ai
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 (requires 1.2.0)
107
+ 2. **openspec** - OpenSpec CLI for specification workflow
111
108
  ```bash
112
- npm install -g @fission-ai/openspec@1.2.0
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 --help` - View all available commands and their syntax
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 (full OpenSpec artifacts + a fresh task snapshot + recent loop signals)
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
- - Creates git commit with task description (unless `--no-commit`)
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** | One git commit per task |
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
- - **OpenSpec artifacts**: Proposal, design, and spec content from the generated PRD
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 product requirements document
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
- *Cross-platform support verified for spec-and-loop 2.0.0*
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
- All features work identically on both platforms without requiring platform-specific configuration.
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
 
@@ -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
- const content = fs.readFileSync(file, 'utf8').trim();
87
- if (!content) return [];
88
- return content.split(/^---$/m).filter((entry) => entry.trim());
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
- const trimmed = entry.trim();
93
- if (!trimmed) return null;
94
-
95
- const timestampMatch = trimmed.match(/^Timestamp: (.+)$/m);
96
- const iterationMatch = trimmed.match(/^Iteration: (.+)$/m);
97
- const taskMatch = trimmed.match(/^Task: (.+)$/m);
98
- const exitCodeMatch = trimmed.match(/^Exit Code: (.+)$/m);
99
- const stderrMatch = trimmed.match(/(?:^|\n)### stderr\n([\s\S]*?)(?=\n### stdout\n|$)/);
100
- const stdoutMatch = trimmed.match(/(?:^|\n)### stdout\n([\s\S]*?)$/);
101
-
102
- return {
103
- timestamp: timestampMatch ? timestampMatch[1].trim() : '',
104
- iteration: iterationMatch ? Number(iterationMatch[1].trim()) : NaN,
105
- task: taskMatch ? taskMatch[1].trim() : '',
106
- exitCode: exitCodeMatch ? Number(exitCodeMatch[1].trim()) : NaN,
107
- stderr: stderrMatch ? stderrMatch[1].trim() : '',
108
- stdout: stdoutMatch ? stdoutMatch[1].trim() : '',
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 };