replay-self-healing-cli 0.1.0

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/ERROR_CODES.md ADDED
@@ -0,0 +1,68 @@
1
+ # Error Codes
2
+
3
+ All CLI errors are emitted as JSON with:
4
+
5
+ - `code`
6
+ - `message`
7
+ - optional `path`
8
+ - optional `expected`
9
+ - optional `received`
10
+ - optional `howToFix`
11
+ - optional `example`
12
+
13
+ ## CLI input errors
14
+
15
+ - `E_UNKNOWN_FLAG` unknown CLI flag
16
+ - `E_TIMEOUT_INVALID` invalid `--timeout-ms`
17
+ - `E_PROMPT_REQUIRED` prompt missing from args/stdin
18
+ - `E_CONTEXT_CONFLICT` both `--context` and `--context-file` set
19
+ - `E_CONTEXT_JSON_INVALID` invalid JSON in context
20
+ - `E_CONTEXT_SHAPE_INVALID` context is not object
21
+ - `E_CONTEXT_FILE_NOT_FOUND` context file missing
22
+ - `E_COMMAND_UNKNOWN` unsupported command
23
+
24
+ ## Runner discovery/execution errors
25
+
26
+ - `E_RUNNER_NOT_FOUND` no default runner discovered
27
+ - `E_RUNNER_PATH_NOT_FOUND` explicit runner path missing
28
+ - `E_RUNNER_UNSUPPORTED_EXTENSION` unsupported runner extension (e.g. `.ts`)
29
+ - `E_RUNNER_NOT_EXECUTABLE` runner not executable when required
30
+ - `E_RUNNER_START_FAILED` process spawn failure
31
+ - `E_RUNNER_TIMEOUT` runner timed out
32
+ - `E_RUNNER_EXIT_NON_ZERO` runner process exited with non-zero code
33
+
34
+ ## Runner output validation errors
35
+
36
+ - `E_RUNNER_EMPTY_STDOUT` runner wrote no stdout
37
+ - `E_RUNNER_INVALID_JSON` runner stdout not parseable JSON
38
+ - `E_RUNNER_OUTPUT_SHAPE` output is not JSON object
39
+ - `E_RUNNER_STATUS_INVALID` status is not `ok` or `error`
40
+ - `E_RUNNER_EVENTS_INVALID` events is not array
41
+ - `E_RUNNER_RESPONSE_MISSING` status `ok` but missing response string
42
+
43
+ ## Artifact IO and validation errors
44
+
45
+ - `E_INPUT_PATH_NOT_FOUND` `--in` target missing
46
+ - `E_INPUT_FILE_NOT_JSON` `--in` file is not `.json`
47
+ - `E_INPUT_NOT_FILE_OR_DIR` `--in` target not file/dir
48
+ - `E_ARTIFACT_JSON_INVALID` invalid JSON artifact file
49
+ - `E_ARTIFACT_SHAPE_INVALID` artifact is not object
50
+ - `E_ARTIFACT_TYPE_INVALID` artifactType mismatch
51
+ - `E_SCHEMA_VERSION_UNSUPPORTED` schemaVersion mismatch
52
+ - `E_ARTIFACT_ID_INVALID` missing/invalid id
53
+ - `E_ARTIFACT_STATUS_INVALID` missing/invalid status
54
+ - `E_ARTIFACT_EVENTS_INVALID` missing/invalid events array
55
+ - `E_ARTIFACT_EVENT_INVALID` event item not object
56
+ - `E_ARTIFACT_EVENT_TYPE_INVALID` event.type invalid
57
+ - `E_ARTIFACT_EVENT_SEQ_INVALID` event.seq invalid
58
+ - `E_ARTIFACT_EVENT_TIMESTAMP_INVALID` event.timestamp invalid
59
+ - `E_HEALED_ARTIFACT_INVALID` healed artifact fails validation
60
+
61
+ ## Heal rule errors
62
+
63
+ - `E_HEAL_RULES_INVALID_JSON` heal rules file invalid JSON
64
+ - `E_HEAL_RULES_SHAPE_INVALID` heal rules must be object
65
+
66
+ ## Internal fallback
67
+
68
+ - `E_UNHANDLED` unexpected runtime failure
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,284 @@
1
+ # replay-self-healing-cli
2
+
3
+ Zero-dependency CLI to capture LLM runs as replay artifacts, auto-heal trace inconsistencies, and produce validation/report output.
4
+
5
+ ## Why this exists
6
+
7
+ - Prompt-first UX: run one prompt and get replay artifacts immediately.
8
+ - Runner is user-defined: framework choice stays outside this tool.
9
+ - Self-healing: the CLI preserves raw output, repairs common trace issues, and logs every repair action.
10
+ - LLM-friendly errors: failures are always emitted as machine-readable JSON with fix guidance.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g replay-self-healing-cli
16
+ ```
17
+
18
+ Or run locally from this folder:
19
+
20
+ ```bash
21
+ npm install
22
+ node ./bin/replay.mjs help
23
+ ```
24
+
25
+ This package has **zero dependencies**.
26
+
27
+ ## 30-second quickstart
28
+
29
+ 1. Create a runner at `.replay/runner.mjs`:
30
+
31
+ ```js
32
+ #!/usr/bin/env node
33
+ import process from 'node:process';
34
+
35
+ let input = '';
36
+ for await (const chunk of process.stdin) {
37
+ input += chunk;
38
+ }
39
+
40
+ const request = JSON.parse(input);
41
+ const now = new Date().toISOString();
42
+
43
+ const output = {
44
+ status: 'ok',
45
+ framework: 'custom',
46
+ response: `Echo: ${request.prompt}`,
47
+ events: [
48
+ { seq: 1, timestamp: now, type: 'run_started', runId: request.runId, payload: {} },
49
+ {
50
+ seq: 2,
51
+ timestamp: now,
52
+ type: 'assistant_message',
53
+ runId: request.runId,
54
+ payload: { response: `Echo: ${request.prompt}` }
55
+ },
56
+ { seq: 3, timestamp: now, type: 'run_completed', runId: request.runId, payload: {} }
57
+ ],
58
+ sources: [],
59
+ usage: { inputTokens: 0, outputTokens: 0, costUsd: 0 }
60
+ };
61
+
62
+ process.stdout.write(JSON.stringify(output));
63
+ ```
64
+
65
+ 2. Run:
66
+
67
+ ```bash
68
+ replay "What are my top holdings?"
69
+ ```
70
+
71
+ 3. Output files are created automatically under:
72
+
73
+ ```text
74
+ .replay/artifacts/YYYY-MM-DD/
75
+ ```
76
+
77
+ ## Command overview
78
+
79
+ ### Capture (default)
80
+
81
+ ```bash
82
+ replay "your prompt"
83
+ replay capture "your prompt"
84
+ echo "your prompt" | replay
85
+ ```
86
+
87
+ Optional flags:
88
+
89
+ - `--runner <path>` override runner discovery
90
+ - `--out <dir>` override output directory
91
+ - `--context '{"k":"v"}'` inline JSON context
92
+ - `--context-file ./context.json` JSON context file
93
+ - `--timeout-ms 120000` runner timeout
94
+ - `--no-heal` skip heal pass
95
+
96
+ ### Validate artifacts
97
+
98
+ ```bash
99
+ replay validate --in .replay/artifacts
100
+ ```
101
+
102
+ ### Re-heal existing artifacts
103
+
104
+ ```bash
105
+ replay heal --in .replay/artifacts --out .replay/healed
106
+ ```
107
+
108
+ ### Report summary
109
+
110
+ ```bash
111
+ replay report --in .replay/artifacts
112
+ ```
113
+
114
+ ## Runner discovery (implied defaults)
115
+
116
+ If `--runner` is not provided, the CLI checks in this order:
117
+
118
+ 1. `REPLAY_RUNNER` env var
119
+ 2. `.replay/runner.mjs`
120
+ 3. `.replay/runner.js`
121
+ 4. `.replay/runner.cjs`
122
+ 5. `.replay/runner`
123
+ 6. `package.json` script: `replay:runner`
124
+
125
+ ## Runner input contract
126
+
127
+ Runner receives one JSON object via `stdin`:
128
+
129
+ ```json
130
+ {
131
+ "prompt": "What are my top holdings?",
132
+ "query": "What are my top holdings?",
133
+ "runId": "run_...",
134
+ "timestamp": "2026-03-01T12:00:00.000Z",
135
+ "context": {}
136
+ }
137
+ ```
138
+
139
+ ## Runner output contract
140
+
141
+ Runner must print one JSON object to `stdout`.
142
+
143
+ Success:
144
+
145
+ ```json
146
+ {
147
+ "status": "ok",
148
+ "framework": "langchain",
149
+ "response": "AAPL is your largest holding.",
150
+ "events": [
151
+ {
152
+ "seq": 1,
153
+ "timestamp": "2026-03-01T12:00:00.000Z",
154
+ "type": "run_started",
155
+ "runId": "run_123",
156
+ "payload": {}
157
+ }
158
+ ],
159
+ "sources": ["portfolio_db"],
160
+ "usage": {
161
+ "inputTokens": 100,
162
+ "outputTokens": 40,
163
+ "costUsd": 0.0021
164
+ }
165
+ }
166
+ ```
167
+
168
+ Failure:
169
+
170
+ ```json
171
+ {
172
+ "status": "error",
173
+ "error": {
174
+ "code": "RUNNER_TOOL_TIMEOUT",
175
+ "message": "get_portfolio_summary timed out after 15s",
176
+ "retryable": true
177
+ },
178
+ "events": []
179
+ }
180
+ ```
181
+
182
+ Schema references:
183
+
184
+ - [schemas/runner-output.schema.json](./schemas/runner-output.schema.json)
185
+ - [schemas/replay-artifact.schema.json](./schemas/replay-artifact.schema.json)
186
+
187
+ ## Self-healing behavior
188
+
189
+ The CLI always writes a raw artifact first, then heals into a replay artifact.
190
+
191
+ Healing currently includes:
192
+
193
+ - type alias mapping (`tool_end` -> `tool_completed`, etc.)
194
+ - missing `run_started` insertion
195
+ - missing terminal event insertion (`run_completed`/`run_failed`)
196
+ - non-object event payload normalization to `{}`
197
+ - sequence renumbering to strict monotonic order
198
+ - duplicate event dedupe (exact adjacent duplicates)
199
+ - synthetic `assistant_message` insertion from final response when missing
200
+
201
+ All applied repairs are written to `*.heal-log.json`.
202
+
203
+ ### Custom heal rules
204
+
205
+ Optional file: `.replay/heal.rules.json`
206
+
207
+ ```json
208
+ {
209
+ "version": "v1",
210
+ "eventAliases": {
211
+ "tool_end": "tool_completed",
212
+ "llm_message": "assistant_message"
213
+ }
214
+ }
215
+ ```
216
+
217
+ ## Artifact files
218
+
219
+ For each run:
220
+
221
+ - `*.raw.json` raw capture of runner process input/output
222
+ - `*.artifact.json` normalized artifact before healing
223
+ - `*.healed.json` healed replay artifact
224
+ - `*.heal-log.json` structured healing log
225
+
226
+ ## Error model (LLM-friendly)
227
+
228
+ Errors are always JSON on `stderr`:
229
+
230
+ ```json
231
+ {
232
+ "error": {
233
+ "code": "E_RUNNER_EVENTS_INVALID",
234
+ "message": "Runner output field `events` must be an array.",
235
+ "path": "$.events",
236
+ "expected": "array",
237
+ "received": "object",
238
+ "howToFix": "Return `events: []` when there are no events."
239
+ }
240
+ }
241
+ ```
242
+
243
+ See full catalog: [ERROR_CODES.md](./ERROR_CODES.md)
244
+
245
+ ## Publish checklist
246
+
247
+ 1. Authenticate npm on this machine:
248
+
249
+ ```bash
250
+ npm login
251
+ # or: npm adduser
252
+ ```
253
+
254
+ 2. Confirm identity and package name availability:
255
+
256
+ ```bash
257
+ npm whoami
258
+ npm view replay-self-healing-cli version
259
+ ```
260
+
261
+ 3. Run pre-publish checks:
262
+
263
+ ```bash
264
+ node ./bin/replay.mjs help
265
+ REPLAY_RUNNER=./examples/runner.mjs node ./bin/replay.mjs "test prompt"
266
+ node ./bin/replay.mjs validate --in .replay/artifacts
267
+ npm pack --dry-run
268
+ ```
269
+
270
+ 4. Publish:
271
+
272
+ ```bash
273
+ npm publish
274
+ ```
275
+
276
+ 5. Verify:
277
+
278
+ ```bash
279
+ npm view replay-self-healing-cli version
280
+ ```
281
+
282
+ ## License
283
+
284
+ MIT
package/bin/replay.mjs ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../src/cli.mjs';
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "replay-self-healing-cli",
3
+ "version": "0.1.0",
4
+ "description": "Zero-dependency self-healing replay harness CLI for LLM runs",
5
+ "type": "module",
6
+ "bin": {
7
+ "replay": "bin/replay.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "schemas",
13
+ "README.md",
14
+ "LICENSE",
15
+ "ERROR_CODES.md"
16
+ ],
17
+ "scripts": {
18
+ "test": "node --test"
19
+ },
20
+ "keywords": [
21
+ "llm",
22
+ "evals",
23
+ "replay",
24
+ "harness",
25
+ "cli",
26
+ "self-healing"
27
+ ],
28
+ "license": "MIT",
29
+ "engines": {
30
+ "node": ">=18.17.0"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/nathankoerschner/replay-self-healing-cli.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/nathankoerschner/replay-self-healing-cli/issues"
38
+ },
39
+ "homepage": "https://github.com/nathankoerschner/replay-self-healing-cli#readme",
40
+ "publishConfig": {
41
+ "access": "public"
42
+ }
43
+ }
@@ -0,0 +1,107 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://replay-self-healing-cli.dev/schemas/replay-artifact.schema.json",
4
+ "title": "Replay Artifact",
5
+ "type": "object",
6
+ "required": [
7
+ "schemaVersion",
8
+ "artifactType",
9
+ "id",
10
+ "timestamp",
11
+ "prompt",
12
+ "query",
13
+ "status",
14
+ "events"
15
+ ],
16
+ "properties": {
17
+ "schemaVersion": {
18
+ "type": "string",
19
+ "const": "1.0.0"
20
+ },
21
+ "artifactType": {
22
+ "type": "string",
23
+ "const": "replay"
24
+ },
25
+ "id": {
26
+ "type": "string"
27
+ },
28
+ "timestamp": {
29
+ "type": "string",
30
+ "format": "date-time"
31
+ },
32
+ "prompt": {
33
+ "type": "string"
34
+ },
35
+ "query": {
36
+ "type": "string"
37
+ },
38
+ "status": {
39
+ "type": "string",
40
+ "enum": ["ok", "error"]
41
+ },
42
+ "response": {
43
+ "type": "string"
44
+ },
45
+ "error": {
46
+ "type": "object"
47
+ },
48
+ "events": {
49
+ "type": "array",
50
+ "items": {
51
+ "type": "object",
52
+ "required": ["seq", "timestamp", "type", "runId", "payload"],
53
+ "properties": {
54
+ "seq": {
55
+ "type": "integer",
56
+ "minimum": 1
57
+ },
58
+ "timestamp": {
59
+ "type": "string",
60
+ "format": "date-time"
61
+ },
62
+ "type": {
63
+ "type": "string"
64
+ },
65
+ "runId": {
66
+ "type": "string"
67
+ },
68
+ "payload": {
69
+ "type": "object"
70
+ }
71
+ }
72
+ }
73
+ },
74
+ "toolCalls": {
75
+ "type": "array",
76
+ "items": {
77
+ "type": "object",
78
+ "required": ["toolName", "status", "durationMs"],
79
+ "properties": {
80
+ "toolName": {
81
+ "type": "string"
82
+ },
83
+ "status": {
84
+ "type": "string",
85
+ "enum": ["ok", "error"]
86
+ },
87
+ "durationMs": {
88
+ "type": "number",
89
+ "minimum": 0
90
+ }
91
+ }
92
+ }
93
+ },
94
+ "timings": {
95
+ "type": "object"
96
+ },
97
+ "sources": {
98
+ "type": "array",
99
+ "items": {
100
+ "type": "string"
101
+ }
102
+ },
103
+ "usage": {
104
+ "type": "object"
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,108 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://replay-self-healing-cli.dev/schemas/runner-output.schema.json",
4
+ "title": "Replay Runner Output",
5
+ "type": "object",
6
+ "required": ["status", "events"],
7
+ "properties": {
8
+ "status": {
9
+ "type": "string",
10
+ "enum": ["ok", "error"]
11
+ },
12
+ "framework": {
13
+ "type": "string"
14
+ },
15
+ "response": {
16
+ "type": "string"
17
+ },
18
+ "events": {
19
+ "type": "array",
20
+ "items": {
21
+ "type": "object",
22
+ "required": ["type"],
23
+ "properties": {
24
+ "runId": {
25
+ "type": "string"
26
+ },
27
+ "seq": {
28
+ "type": "integer",
29
+ "minimum": 1
30
+ },
31
+ "timestamp": {
32
+ "type": "string",
33
+ "format": "date-time"
34
+ },
35
+ "type": {
36
+ "type": "string"
37
+ },
38
+ "payload": {
39
+ "type": "object"
40
+ }
41
+ }
42
+ }
43
+ },
44
+ "sources": {
45
+ "type": "array",
46
+ "items": {
47
+ "type": "string"
48
+ }
49
+ },
50
+ "usage": {
51
+ "type": "object",
52
+ "properties": {
53
+ "inputTokens": {
54
+ "type": "number",
55
+ "minimum": 0
56
+ },
57
+ "outputTokens": {
58
+ "type": "number",
59
+ "minimum": 0
60
+ },
61
+ "costUsd": {
62
+ "type": "number",
63
+ "minimum": 0
64
+ }
65
+ }
66
+ },
67
+ "error": {
68
+ "type": "object",
69
+ "properties": {
70
+ "code": {
71
+ "type": "string"
72
+ },
73
+ "message": {
74
+ "type": "string"
75
+ },
76
+ "retryable": {
77
+ "type": "boolean"
78
+ }
79
+ }
80
+ }
81
+ },
82
+ "allOf": [
83
+ {
84
+ "if": {
85
+ "properties": {
86
+ "status": {
87
+ "const": "ok"
88
+ }
89
+ }
90
+ },
91
+ "then": {
92
+ "required": ["response"]
93
+ }
94
+ },
95
+ {
96
+ "if": {
97
+ "properties": {
98
+ "status": {
99
+ "const": "error"
100
+ }
101
+ }
102
+ },
103
+ "then": {
104
+ "required": ["error"]
105
+ }
106
+ }
107
+ ]
108
+ }