@arcforge/err 2.0.120 → 2.0.121
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/package.json +2 -2
- package/src/err.ts +8 -2
- package/src/map.ts +71 -0
- package/src/render.ts +29 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcforge/err",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.121",
|
|
4
4
|
"description": "Structured Axon errors — the code map every user-facing failure is rendered from.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"deploy": "echo \"This package is published ONLY by apps/tui/scripts/release.ts, which pins workspace:* deps to concrete versions first. Publishing it directly ships an unresolvable dependency.\" && exit 1"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@arcforge/types": "2.0.
|
|
18
|
+
"@arcforge/types": "2.0.121"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/bun": "latest",
|
package/src/err.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { errorMap, type AxonErrorCode, type AxonErrorMap } from "./map"
|
|
1
|
+
import { errorMap, type AxonErrorCode, type AxonErrorMap, type AxonErrorMapEntry } from "./map"
|
|
2
2
|
import { captureStack, firstRealFrame } from "./stack"
|
|
3
3
|
import { renderError } from "./render"
|
|
4
4
|
import { emitError } from "./sink"
|
|
@@ -56,7 +56,10 @@ export function err(codeOrCause: AxonErrorCode | unknown, opts?: AxonErrorOpts):
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
const code = codeOrCause as AxonErrorCode
|
|
59
|
-
|
|
59
|
+
// Read through the ENTRY type, not the literal map type: `as const` keeps
|
|
60
|
+
// each entry's exact shape, so optional fields are absent from the type of
|
|
61
|
+
// entries that omit them and unreadable off the union.
|
|
62
|
+
const def: AxonErrorMapEntry = (errorMap as AxonErrorMap)[code]
|
|
60
63
|
|
|
61
64
|
const e = new Error(opts?.detail ?? def.title, { cause: opts?.cause }) as AxonError
|
|
62
65
|
|
|
@@ -65,6 +68,9 @@ export function err(codeOrCause: AxonErrorCode | unknown, opts?: AxonErrorOpts):
|
|
|
65
68
|
e.description = def.description
|
|
66
69
|
e.source = def.source
|
|
67
70
|
e.severity = opts?.severity ?? def.severity
|
|
71
|
+
// Carried onto the instance so renderers (and the CLI's own catch) can ask
|
|
72
|
+
// whose fault this is without reaching back into the map.
|
|
73
|
+
if (def.expected) e.expected = true
|
|
68
74
|
e.context = opts?.context
|
|
69
75
|
e.frames = captureStack(2) // drop captureStack's own frame + err()'s
|
|
70
76
|
e.isAxonError = true
|
package/src/map.ts
CHANGED
|
@@ -33,6 +33,25 @@ export type AxonErrorMapEntry = {
|
|
|
33
33
|
description: string
|
|
34
34
|
source: AxonErrorSource
|
|
35
35
|
severity: AxonErrorSeverity
|
|
36
|
+
/**
|
|
37
|
+
* Is this a failure the USER caused and can fix, rather than a fault in
|
|
38
|
+
* our code?
|
|
39
|
+
*
|
|
40
|
+
* `axon publish` outside a project directory is a typo, not a bug — the
|
|
41
|
+
* title and description already say everything actionable, so a renderer
|
|
42
|
+
* showing eighty lines of our internals underneath is telling the user to
|
|
43
|
+
* debug software they did not write. Marking it `expected` renders the
|
|
44
|
+
* headline alone.
|
|
45
|
+
*
|
|
46
|
+
* Deliberately NOT `severity`, which answers a different question:
|
|
47
|
+
* PROJECT_NOT_FOUND is genuinely `fatal` (the command cannot continue) AND
|
|
48
|
+
* genuinely expected. Recoverability and blame are independent axes.
|
|
49
|
+
*
|
|
50
|
+
* Absent means unexpected — the safe default. An unclassified failure gets
|
|
51
|
+
* the full trace, which is what makes it debuggable; the only cost of
|
|
52
|
+
* forgetting this flag is a noisier message, never a hidden bug.
|
|
53
|
+
*/
|
|
54
|
+
expected?: true
|
|
36
55
|
}
|
|
37
56
|
|
|
38
57
|
/**
|
|
@@ -52,6 +71,7 @@ export const errorMap = {
|
|
|
52
71
|
description: "The requested prompt could not be resolved — it is not declared by the agent, or the published package it names ships no prompt by that name.",
|
|
53
72
|
source: "runtime",
|
|
54
73
|
severity: "fatal",
|
|
74
|
+
expected: true,
|
|
55
75
|
},
|
|
56
76
|
PROMPT_FILE_NOT_FOUND: {
|
|
57
77
|
code: "AX-PROMPT-002",
|
|
@@ -59,6 +79,7 @@ export const errorMap = {
|
|
|
59
79
|
description: "The prompt is declared but its source file no longer exists on disk.",
|
|
60
80
|
source: "runtime",
|
|
61
81
|
severity: "fatal",
|
|
82
|
+
expected: true,
|
|
62
83
|
},
|
|
63
84
|
PROMPT_RENDER_FAILED: {
|
|
64
85
|
code: "AX-PROMPT-003",
|
|
@@ -87,6 +108,7 @@ export const errorMap = {
|
|
|
87
108
|
description: "The requested script was not found in the agent's blueprint. It may not be declared, or the agent needs to be re-prepared.",
|
|
88
109
|
source: "runtime",
|
|
89
110
|
severity: "fatal",
|
|
111
|
+
expected: true,
|
|
90
112
|
},
|
|
91
113
|
SCRIPT_FILE_NOT_FOUND: {
|
|
92
114
|
code: "AX-SCRIPT-002",
|
|
@@ -94,6 +116,7 @@ export const errorMap = {
|
|
|
94
116
|
description: "The script is declared but its source file no longer exists on disk.",
|
|
95
117
|
source: "runtime",
|
|
96
118
|
severity: "fatal",
|
|
119
|
+
expected: true,
|
|
97
120
|
},
|
|
98
121
|
ENGINE_MISSING: {
|
|
99
122
|
code: "AX-ENGINE-001",
|
|
@@ -236,6 +259,7 @@ export const errorMap = {
|
|
|
236
259
|
description: "The cognet named by `cognet:` in axon.config.ts could not be located. The detail says where it was looked for — a registry that answered 404, or a node_modules tree it was missing from. Check the specifier for a typo, and check which registry the CLI is pointed at (AXON_API_BASE).",
|
|
237
260
|
source: "cognet",
|
|
238
261
|
severity: "fatal",
|
|
262
|
+
expected: true,
|
|
239
263
|
},
|
|
240
264
|
|
|
241
265
|
// ── Project (build/project/) ────────────────────────────────────────────
|
|
@@ -303,6 +327,7 @@ export const errorMap = {
|
|
|
303
327
|
description: "The selected model routes through a provider (OpenRouter, Codex) that isn't connected yet — connect it before picking a model on that route.",
|
|
304
328
|
source: "tui",
|
|
305
329
|
severity: "fatal",
|
|
330
|
+
expected: true,
|
|
306
331
|
},
|
|
307
332
|
PALETTE_INPUT_REQUIRED: {
|
|
308
333
|
code: "AX-TUI-012",
|
|
@@ -310,6 +335,7 @@ export const errorMap = {
|
|
|
310
335
|
description: "A palette command needs a value for this field before it can run.",
|
|
311
336
|
source: "tui",
|
|
312
337
|
severity: "fatal",
|
|
338
|
+
expected: true,
|
|
313
339
|
},
|
|
314
340
|
BENCH_NO_CASES: {
|
|
315
341
|
code: "AX-TUI-013",
|
|
@@ -326,6 +352,7 @@ export const errorMap = {
|
|
|
326
352
|
description: "No axon.config.ts, module.config.ts, cognet.config.ts, bench.config.ts, or prompt.config.ts was found at this path — it isn't a recognized project directory.",
|
|
327
353
|
source: "manifest",
|
|
328
354
|
severity: "fatal",
|
|
355
|
+
expected: true,
|
|
329
356
|
},
|
|
330
357
|
PROJECT_WRONG_KIND: {
|
|
331
358
|
code: "AX-PROJECT-011",
|
|
@@ -333,6 +360,7 @@ export const errorMap = {
|
|
|
333
360
|
description: "This command targets one project kind, and the directory holds another — run the command that matches what is actually here.",
|
|
334
361
|
source: "manifest",
|
|
335
362
|
severity: "fatal",
|
|
363
|
+
expected: true,
|
|
336
364
|
},
|
|
337
365
|
PROJECT_EXISTS: {
|
|
338
366
|
code: "AX-PROJECT-018",
|
|
@@ -468,6 +496,7 @@ export const errorMap = {
|
|
|
468
496
|
description: "No recorded bench run exists with this id.",
|
|
469
497
|
source: "bench",
|
|
470
498
|
severity: "fatal",
|
|
499
|
+
expected: true,
|
|
471
500
|
},
|
|
472
501
|
BENCH_NOT_FOUND: {
|
|
473
502
|
code: "AX-BENCH-002",
|
|
@@ -475,6 +504,7 @@ export const errorMap = {
|
|
|
475
504
|
description: "No bench.config.ts was found at this path — it isn't a recognized bench project.",
|
|
476
505
|
source: "bench",
|
|
477
506
|
severity: "fatal",
|
|
507
|
+
expected: true,
|
|
478
508
|
},
|
|
479
509
|
BENCH_TESTS_NOT_FOUND: {
|
|
480
510
|
code: "AX-BENCH-003",
|
|
@@ -482,6 +512,7 @@ export const errorMap = {
|
|
|
482
512
|
description: "The bench config declares test files that don't exist on disk.",
|
|
483
513
|
source: "bench",
|
|
484
514
|
severity: "fatal",
|
|
515
|
+
expected: true,
|
|
485
516
|
},
|
|
486
517
|
BENCH_LOCAL_REF_NOT_FOUND: {
|
|
487
518
|
code: "AX-BENCH-004",
|
|
@@ -489,6 +520,7 @@ export const errorMap = {
|
|
|
489
520
|
description: "A factor variable references a local file/directory that doesn't exist.",
|
|
490
521
|
source: "bench",
|
|
491
522
|
severity: "fatal",
|
|
523
|
+
expected: true,
|
|
492
524
|
},
|
|
493
525
|
BENCH_CONFIG_INVALID: {
|
|
494
526
|
code: "AX-BENCH-005",
|
|
@@ -503,6 +535,7 @@ export const errorMap = {
|
|
|
503
535
|
description: "No package.json exists at the bench root — every bench project needs one for identity.",
|
|
504
536
|
source: "bench",
|
|
505
537
|
severity: "fatal",
|
|
538
|
+
expected: true,
|
|
506
539
|
},
|
|
507
540
|
BENCH_PACKAGE_NAME_REQUIRED: {
|
|
508
541
|
code: "AX-BENCH-007",
|
|
@@ -510,6 +543,7 @@ export const errorMap = {
|
|
|
510
543
|
description: "The bench's package.json has no name field.",
|
|
511
544
|
source: "bench",
|
|
512
545
|
severity: "fatal",
|
|
546
|
+
expected: true,
|
|
513
547
|
},
|
|
514
548
|
BENCH_PACKAGE_VERSION_REQUIRED: {
|
|
515
549
|
code: "AX-BENCH-008",
|
|
@@ -517,6 +551,7 @@ export const errorMap = {
|
|
|
517
551
|
description: "The bench's package.json has no version field.",
|
|
518
552
|
source: "bench",
|
|
519
553
|
severity: "fatal",
|
|
554
|
+
expected: true,
|
|
520
555
|
},
|
|
521
556
|
BENCH_CONTEXT_MISSING: {
|
|
522
557
|
code: "AX-BENCH-009",
|
|
@@ -636,6 +671,7 @@ export const errorMap = {
|
|
|
636
671
|
description: "The bench config's matrix has no axis with this key.",
|
|
637
672
|
source: "bench",
|
|
638
673
|
severity: "fatal",
|
|
674
|
+
expected: true,
|
|
639
675
|
},
|
|
640
676
|
BENCH_AXIS_VALUE_NOT_FOUND: {
|
|
641
677
|
code: "AX-BENCH-022",
|
|
@@ -643,6 +679,7 @@ export const errorMap = {
|
|
|
643
679
|
description: "The bench config's matrix axis has no declared value with this id.",
|
|
644
680
|
source: "bench",
|
|
645
681
|
severity: "fatal",
|
|
682
|
+
expected: true,
|
|
646
683
|
},
|
|
647
684
|
BENCH_WORKSPACE_ESCAPE: {
|
|
648
685
|
code: "AX-BENCH-023",
|
|
@@ -671,6 +708,7 @@ export const errorMap = {
|
|
|
671
708
|
description: "A workspace template's declared source path isn't a real directory.",
|
|
672
709
|
source: "bench",
|
|
673
710
|
severity: "fatal",
|
|
711
|
+
expected: true,
|
|
674
712
|
},
|
|
675
713
|
BENCH_LOG_INVALID: {
|
|
676
714
|
code: "AX-BENCH-027",
|
|
@@ -736,6 +774,7 @@ export const errorMap = {
|
|
|
736
774
|
description: "No axon.config.ts exists at this path.",
|
|
737
775
|
source: "manifest",
|
|
738
776
|
severity: "fatal",
|
|
777
|
+
expected: true,
|
|
739
778
|
},
|
|
740
779
|
CONFIG_LOAD_FAILED: {
|
|
741
780
|
code: "AX-BLUEPRINT-004",
|
|
@@ -773,6 +812,7 @@ export const errorMap = {
|
|
|
773
812
|
description: "This agent uses a Codex model, but your Axon account is not connected to ChatGPT — run :provider codex connect and try again.",
|
|
774
813
|
source: "kernel",
|
|
775
814
|
severity: "fatal",
|
|
815
|
+
expected: true,
|
|
776
816
|
},
|
|
777
817
|
RUN_IN_PROGRESS: {
|
|
778
818
|
code: "AX-KERNEL-002",
|
|
@@ -838,6 +878,7 @@ export const errorMap = {
|
|
|
838
878
|
description: "The requested thread id isn't registered in this session.",
|
|
839
879
|
source: "thread",
|
|
840
880
|
severity: "fatal",
|
|
881
|
+
expected: true,
|
|
841
882
|
},
|
|
842
883
|
THREAD_BRANCH_UNKNOWN: {
|
|
843
884
|
code: "AX-SESSION-003",
|
|
@@ -873,6 +914,7 @@ export const errorMap = {
|
|
|
873
914
|
description: "A control-channel call named a method the peer does not expose. The two ends disagree about the surface — usually a TUI and an extension built from different versions.",
|
|
874
915
|
source: "server",
|
|
875
916
|
severity: "fatal",
|
|
917
|
+
expected: true,
|
|
876
918
|
},
|
|
877
919
|
CONTROL_PATH_NOT_CALLABLE: {
|
|
878
920
|
code: "AX-CONTROL-002",
|
|
@@ -1096,6 +1138,7 @@ export const errorMap = {
|
|
|
1096
1138
|
description: "The action needs an active profile, but none is logged in yet.",
|
|
1097
1139
|
source: "tui",
|
|
1098
1140
|
severity: "fatal",
|
|
1141
|
+
expected: true,
|
|
1099
1142
|
},
|
|
1100
1143
|
PROFILE_NOT_AUTHENTICATED: {
|
|
1101
1144
|
code: "AX-TUI-003",
|
|
@@ -1103,6 +1146,15 @@ export const errorMap = {
|
|
|
1103
1146
|
description: "The target profile exists but has no stored session — it has never logged in, or its session was cleared.",
|
|
1104
1147
|
source: "tui",
|
|
1105
1148
|
severity: "fatal",
|
|
1149
|
+
expected: true,
|
|
1150
|
+
},
|
|
1151
|
+
BACKEND_UNREACHABLE: {
|
|
1152
|
+
code: "AX-TUI-044",
|
|
1153
|
+
title: "Backend Unreachable",
|
|
1154
|
+
description: "A stored credential could not be verified because the backend could not be reached. The credential is NOT discarded — it may be perfectly valid — but it cannot be trusted until it is checked, so the action is refused rather than proceeding unverified.",
|
|
1155
|
+
source: "tui",
|
|
1156
|
+
severity: "fatal",
|
|
1157
|
+
expected: true,
|
|
1106
1158
|
},
|
|
1107
1159
|
PROFILE_UNKNOWN: {
|
|
1108
1160
|
code: "AX-TUI-004",
|
|
@@ -1110,6 +1162,7 @@ export const errorMap = {
|
|
|
1110
1162
|
description: "The requested profile id isn't one of the profiles stored on this machine.",
|
|
1111
1163
|
source: "tui",
|
|
1112
1164
|
severity: "fatal",
|
|
1165
|
+
expected: true,
|
|
1113
1166
|
},
|
|
1114
1167
|
SESSION_ALREADY_RUNNING: {
|
|
1115
1168
|
code: "AX-TUI-005",
|
|
@@ -1117,6 +1170,7 @@ export const errorMap = {
|
|
|
1117
1170
|
description: "spawn() was asked to resume a session that already has a live instance. Focus the running instance instead of booting a second runtime over the same log.",
|
|
1118
1171
|
source: "tui",
|
|
1119
1172
|
severity: "fatal",
|
|
1173
|
+
expected: true,
|
|
1120
1174
|
},
|
|
1121
1175
|
SESSION_NOT_RUNNING: {
|
|
1122
1176
|
code: "AX-TUI-016",
|
|
@@ -1124,6 +1178,15 @@ export const errorMap = {
|
|
|
1124
1178
|
description: "focus() was pointed at a sessionId with no live instance behind it. Spawn (or resume) it first — focus is pure selection over running instances.",
|
|
1125
1179
|
source: "tui",
|
|
1126
1180
|
severity: "fatal",
|
|
1181
|
+
expected: true,
|
|
1182
|
+
},
|
|
1183
|
+
NO_ACTIVE_AGENT: {
|
|
1184
|
+
code: "AX-TUI-045",
|
|
1185
|
+
title: "No Active Agent",
|
|
1186
|
+
description: "The action needs a running agent, but none is active — start one first.",
|
|
1187
|
+
source: "tui",
|
|
1188
|
+
severity: "fatal",
|
|
1189
|
+
expected: true,
|
|
1127
1190
|
},
|
|
1128
1191
|
NO_FOCUSED_INSTANCE: {
|
|
1129
1192
|
code: "AX-TUI-017",
|
|
@@ -1131,6 +1194,7 @@ export const errorMap = {
|
|
|
1131
1194
|
description: "A module install/uninstall was requested with no running agent instance focused — spawn or focus one first.",
|
|
1132
1195
|
source: "tui",
|
|
1133
1196
|
severity: "fatal",
|
|
1197
|
+
expected: true,
|
|
1134
1198
|
},
|
|
1135
1199
|
MODEL_IMMUTABLE_DEPLOYED: {
|
|
1136
1200
|
code: "AX-TUI-006",
|
|
@@ -1138,6 +1202,7 @@ export const errorMap = {
|
|
|
1138
1202
|
description: "A deployed agent's config lives in the cloud, not on this machine — its model is fixed at deploy time. Change it in the local project and deploy again.",
|
|
1139
1203
|
source: "tui",
|
|
1140
1204
|
severity: "fatal",
|
|
1205
|
+
expected: true,
|
|
1141
1206
|
},
|
|
1142
1207
|
MODULE_INSTALL_FAILED: {
|
|
1143
1208
|
code: "AX-TUI-018",
|
|
@@ -1215,6 +1280,7 @@ export const errorMap = {
|
|
|
1215
1280
|
description: "`axon watch`/`axon unwatch` need a directory argument.",
|
|
1216
1281
|
source: "tui",
|
|
1217
1282
|
severity: "fatal",
|
|
1283
|
+
expected: true,
|
|
1218
1284
|
},
|
|
1219
1285
|
WATCH_PATH_NOT_FOUND: {
|
|
1220
1286
|
code: "AX-TUI-022",
|
|
@@ -1222,6 +1288,7 @@ export const errorMap = {
|
|
|
1222
1288
|
description: "`axon watch` was given a directory that doesn't exist on disk.",
|
|
1223
1289
|
source: "tui",
|
|
1224
1290
|
severity: "fatal",
|
|
1291
|
+
expected: true,
|
|
1225
1292
|
},
|
|
1226
1293
|
EDITOR_NOT_SET: {
|
|
1227
1294
|
code: "AX-TUI-023",
|
|
@@ -1243,6 +1310,7 @@ export const errorMap = {
|
|
|
1243
1310
|
description: "The init palette's \"watch a new directory\" entry needs both a directory path and an agent name, space-separated.",
|
|
1244
1311
|
source: "tui",
|
|
1245
1312
|
severity: "fatal",
|
|
1313
|
+
expected: true,
|
|
1246
1314
|
},
|
|
1247
1315
|
|
|
1248
1316
|
// ── Module boot-time execution (core runs defineModule setup) ────────────
|
|
@@ -1287,6 +1355,7 @@ export const errorMap = {
|
|
|
1287
1355
|
description: "A module's setup() called ctx.env.require() for a variable the agent's resolved environment does not provide. The module declares required env in module.config.ts; the agent must supply it (e.g. in .env).",
|
|
1288
1356
|
source: "runtime",
|
|
1289
1357
|
severity: "fatal",
|
|
1358
|
+
expected: true,
|
|
1290
1359
|
},
|
|
1291
1360
|
MODULE_POLICY_IMMUTABLE: {
|
|
1292
1361
|
code: "AX-MODULE-006",
|
|
@@ -1294,6 +1363,7 @@ export const errorMap = {
|
|
|
1294
1363
|
description: "A module's setup() called ctx.policy.update(). The resolved agent policy is authoritative and cannot be mutated at boot — declare policy needs statically in module.config.ts so the CLI reconciles them at install.",
|
|
1295
1364
|
source: "runtime",
|
|
1296
1365
|
severity: "fatal",
|
|
1366
|
+
expected: true,
|
|
1297
1367
|
},
|
|
1298
1368
|
|
|
1299
1369
|
// ── Registry retrieval (build/registry.ts) ──────────────────────────────
|
|
@@ -1303,6 +1373,7 @@ export const errorMap = {
|
|
|
1303
1373
|
description: "`axon clone` was run without an artifact to clone. Name one, for example: axon clone @axon/arxiv",
|
|
1304
1374
|
source: "cli",
|
|
1305
1375
|
severity: "fatal",
|
|
1376
|
+
expected: true,
|
|
1306
1377
|
},
|
|
1307
1378
|
FORK_REF_REQUIRED: {
|
|
1308
1379
|
code: "AX-PROJECT-023",
|
package/src/render.ts
CHANGED
|
@@ -20,6 +20,8 @@ export type AxonErrorLike = {
|
|
|
20
20
|
context: Record<string, unknown> | undefined
|
|
21
21
|
frames: AxonStackFrame[]
|
|
22
22
|
cause?: unknown
|
|
23
|
+
/** The user caused this and can fix it — renders without our internals. See map.ts. */
|
|
24
|
+
expected?: boolean
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/** One frame, Rust-style: file:line:col, then its captured source context with a caret. */
|
|
@@ -37,7 +39,23 @@ export function renderFrame(frame: AxonStackFrame): string {
|
|
|
37
39
|
|
|
38
40
|
const RULE_WIDTH = 80
|
|
39
41
|
|
|
40
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* The renderable report.
|
|
44
|
+
*
|
|
45
|
+
* Two shapes, chosen by whose fault the failure is:
|
|
46
|
+
*
|
|
47
|
+
* EXPECTED headline + description + context. `axon publish` outside a
|
|
48
|
+
* project is a typo; the description already says exactly what
|
|
49
|
+
* to do, and eighty lines of our call stack underneath tells the
|
|
50
|
+
* user to debug software they did not write.
|
|
51
|
+
*
|
|
52
|
+
* otherwise the full report — frames, source snippets, cause chain. An
|
|
53
|
+
* unclassified failure IS ours, and this is what makes it
|
|
54
|
+
* debuggable from a pasted terminal log.
|
|
55
|
+
*
|
|
56
|
+
* The default is the full report (see map.ts's `expected`), so forgetting to
|
|
57
|
+
* classify a code costs a noisy message and never a hidden bug.
|
|
58
|
+
*/
|
|
41
59
|
export function renderError(error: AxonErrorLike): string {
|
|
42
60
|
const lines = [`Axon Error: ${error.title}`, error.description]
|
|
43
61
|
|
|
@@ -49,8 +67,17 @@ export function renderError(error: AxonErrorLike): string {
|
|
|
49
67
|
lines.push("", "Context:", indent(renderContext(error.context)))
|
|
50
68
|
}
|
|
51
69
|
|
|
52
|
-
|
|
70
|
+
// OUR frames are what an expected failure omits — they describe code the
|
|
71
|
+
// user did not write and cannot act on.
|
|
72
|
+
if (!error.expected) {
|
|
73
|
+
lines.push("─".repeat(RULE_WIDTH), ...error.frames.map(renderFrame))
|
|
74
|
+
}
|
|
53
75
|
|
|
76
|
+
// A cause survives either way: something deliberately attached it, and it
|
|
77
|
+
// names the underlying fault ("ECONNREFUSED" under a missing file), which
|
|
78
|
+
// is the most actionable line in the whole report. Suppressing it would
|
|
79
|
+
// leave the user reading "the file is missing" with no hint that the real
|
|
80
|
+
// problem was the network.
|
|
54
81
|
if (error.cause !== undefined) {
|
|
55
82
|
lines.push("", "Caused by:", indent(causeMessage(error.cause)))
|
|
56
83
|
}
|