melusine 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/README.md +249 -0
- package/SPEC.md +145 -0
- package/dist/catalog.d.ts +104 -0
- package/dist/cli.d.ts +27 -0
- package/dist/compile.d.ts +1 -0
- package/dist/generate.d.ts +11 -0
- package/dist/index.d.ts +23 -0
- package/dist/parse.d.ts +27 -0
- package/dist/run.d.ts +54 -0
- package/dist/scaffold.d.ts +18 -0
- package/dist/types.d.ts +542 -0
- package/dist/validate.d.ts +28 -0
- package/dist/walk.d.ts +24 -0
- package/examples/README.md +21 -0
- package/examples/onboarding/README.md +16 -0
- package/examples/onboarding/catalog.js +29 -0
- package/examples/onboarding/generated/onboarding.test.mjs +15 -0
- package/examples/onboarding/journey.md +18 -0
- package/examples/onboarding/onboarding-session.mjs +21 -0
- package/examples/order-fulfillment/README.md +16 -0
- package/examples/order-fulfillment/catalog.js +59 -0
- package/examples/order-fulfillment/generated/order-fulfillment.test.mjs +15 -0
- package/examples/order-fulfillment/journey.md +32 -0
- package/examples/order-fulfillment/order-workflow.mjs +48 -0
- package/examples/vending/README.md +16 -0
- package/examples/vending/catalog.js +32 -0
- package/examples/vending/generated/vending.test.mjs +15 -0
- package/examples/vending/journey.md +21 -0
- package/examples/vending/vending-machine.mjs +16 -0
- package/package.json +39 -0
- package/src/catalog.js +485 -0
- package/src/cli.js +331 -0
- package/src/compile.js +3 -0
- package/src/generate.js +52 -0
- package/src/index.js +28 -0
- package/src/parse.js +263 -0
- package/src/run.js +258 -0
- package/src/scaffold.js +142 -0
- package/src/types.js +330 -0
- package/src/validate.js +171 -0
- package/src/walk.js +57 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural kind inferred from a supported Mermaid node shape.
|
|
3
|
+
*/
|
|
4
|
+
export type NodeKind = "terminal" | "process" | "decision";
|
|
5
|
+
/**
|
|
6
|
+
* A node in the parsed journey graph.
|
|
7
|
+
*/
|
|
8
|
+
export type Node = {
|
|
9
|
+
/**
|
|
10
|
+
* Stable Mermaid node id.
|
|
11
|
+
*/
|
|
12
|
+
id: string;
|
|
13
|
+
/**
|
|
14
|
+
* Structural kind inferred from shape.
|
|
15
|
+
*/
|
|
16
|
+
kind: NodeKind;
|
|
17
|
+
/**
|
|
18
|
+
* Human-readable label from the diagram.
|
|
19
|
+
*/
|
|
20
|
+
label: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A directed edge in source order.
|
|
24
|
+
*/
|
|
25
|
+
export type Edge = {
|
|
26
|
+
/**
|
|
27
|
+
* Source node id.
|
|
28
|
+
*/
|
|
29
|
+
from: string;
|
|
30
|
+
/**
|
|
31
|
+
* Destination node id.
|
|
32
|
+
*/
|
|
33
|
+
to: string;
|
|
34
|
+
/**
|
|
35
|
+
* Branch label, such as "yes" or "no".
|
|
36
|
+
*/
|
|
37
|
+
label?: string | undefined;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Parsed journey graph. `start` is populated when validation finds exactly one
|
|
41
|
+
* zero-incoming terminal.
|
|
42
|
+
*/
|
|
43
|
+
export type JourneyGraph = {
|
|
44
|
+
/**
|
|
45
|
+
* Node id to node.
|
|
46
|
+
*/
|
|
47
|
+
nodes: Map<string, Node>;
|
|
48
|
+
/**
|
|
49
|
+
* Edges in Mermaid source order.
|
|
50
|
+
*/
|
|
51
|
+
edges: Edge[];
|
|
52
|
+
/**
|
|
53
|
+
* Unique start node id, or an empty string when invalid.
|
|
54
|
+
*/
|
|
55
|
+
start: string;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Opaque per-node frontmatter value.
|
|
59
|
+
*/
|
|
60
|
+
export type Binding = unknown;
|
|
61
|
+
/**
|
|
62
|
+
* A stable diagnostic for author-input issues or parser notes.
|
|
63
|
+
*/
|
|
64
|
+
export type Diagnostic = {
|
|
65
|
+
/**
|
|
66
|
+
* Diagnostic severity.
|
|
67
|
+
*/
|
|
68
|
+
severity: "error" | "warning" | "info";
|
|
69
|
+
/**
|
|
70
|
+
* Stable screaming-snake diagnostic code.
|
|
71
|
+
*/
|
|
72
|
+
code: string;
|
|
73
|
+
/**
|
|
74
|
+
* Human-readable explanation.
|
|
75
|
+
*/
|
|
76
|
+
message: string;
|
|
77
|
+
/**
|
|
78
|
+
* Node id when the diagnostic is node-scoped.
|
|
79
|
+
*/
|
|
80
|
+
node?: string | undefined;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Result returned by `parse()`.
|
|
84
|
+
*/
|
|
85
|
+
export type ParseResult = {
|
|
86
|
+
/**
|
|
87
|
+
* Parsed graph.
|
|
88
|
+
*/
|
|
89
|
+
graph: JourneyGraph;
|
|
90
|
+
/**
|
|
91
|
+
* Node id to opaque frontmatter config.
|
|
92
|
+
*/
|
|
93
|
+
bindings: Map<string, Binding>;
|
|
94
|
+
/**
|
|
95
|
+
* Frontmatter minus `nodes`.
|
|
96
|
+
*/
|
|
97
|
+
meta: Record<string, unknown>;
|
|
98
|
+
/**
|
|
99
|
+
* Parser and structural validation diagnostics.
|
|
100
|
+
*/
|
|
101
|
+
diagnostics: Diagnostic[];
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Per-node catalog configuration read from frontmatter.
|
|
105
|
+
*
|
|
106
|
+
* `use` chooses a reusable catalog key for this graph. `as` chooses where a
|
|
107
|
+
* task output is stored in execution context. Extra fields are passed to the
|
|
108
|
+
* catalog function in `options`.
|
|
109
|
+
*/
|
|
110
|
+
export type NodeConfig = {
|
|
111
|
+
/**
|
|
112
|
+
* Catalog key. Defaults to node id.
|
|
113
|
+
*/
|
|
114
|
+
use: string;
|
|
115
|
+
/**
|
|
116
|
+
* Positional arguments for this node.
|
|
117
|
+
*/
|
|
118
|
+
args: unknown[];
|
|
119
|
+
/**
|
|
120
|
+
* Context key override for task output.
|
|
121
|
+
*/
|
|
122
|
+
as: string | undefined;
|
|
123
|
+
/**
|
|
124
|
+
* Extra per-journey options.
|
|
125
|
+
*/
|
|
126
|
+
options: Record<string, unknown>;
|
|
127
|
+
/**
|
|
128
|
+
* Original binding value.
|
|
129
|
+
*/
|
|
130
|
+
raw: Binding | undefined;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Previous execution state supplied to catalog functions.
|
|
134
|
+
*/
|
|
135
|
+
export type ExecutedStep = TaskStep | ScoreStep | GapStep | ErrorStep;
|
|
136
|
+
/**
|
|
137
|
+
* Object passed to every task and scorer function.
|
|
138
|
+
*/
|
|
139
|
+
export type CatalogCall = {
|
|
140
|
+
/**
|
|
141
|
+
* Node-specific args from frontmatter.
|
|
142
|
+
*/
|
|
143
|
+
args: unknown[];
|
|
144
|
+
/**
|
|
145
|
+
* Mutable execution context.
|
|
146
|
+
*/
|
|
147
|
+
context: Record<string, unknown>;
|
|
148
|
+
/**
|
|
149
|
+
* Journey frontmatter minus `nodes`.
|
|
150
|
+
*/
|
|
151
|
+
meta: Record<string, unknown>;
|
|
152
|
+
/**
|
|
153
|
+
* Source graph node.
|
|
154
|
+
*/
|
|
155
|
+
node: Node;
|
|
156
|
+
/**
|
|
157
|
+
* Steps that have run before this node.
|
|
158
|
+
*/
|
|
159
|
+
previous: ExecutedStep[];
|
|
160
|
+
/**
|
|
161
|
+
* Normalized node config.
|
|
162
|
+
*/
|
|
163
|
+
config: NodeConfig;
|
|
164
|
+
/**
|
|
165
|
+
* Resolved catalog key.
|
|
166
|
+
*/
|
|
167
|
+
key: string;
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Pending fragment for a known unresolved step.
|
|
171
|
+
*/
|
|
172
|
+
export type TodoGap = {
|
|
173
|
+
kind: "todo";
|
|
174
|
+
/**
|
|
175
|
+
* Why the step is unresolved.
|
|
176
|
+
*/
|
|
177
|
+
reason: string;
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Pending fragment for missing human-supplied input.
|
|
181
|
+
*/
|
|
182
|
+
export type HoleGap = {
|
|
183
|
+
kind: "hole";
|
|
184
|
+
/**
|
|
185
|
+
* What input is missing.
|
|
186
|
+
*/
|
|
187
|
+
reason: string;
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* A catalog function may return one of these to stop execution honestly.
|
|
191
|
+
*/
|
|
192
|
+
export type GapSignal = TodoGap | HoleGap;
|
|
193
|
+
/**
|
|
194
|
+
* Function wrapped by `task(...)`.
|
|
195
|
+
*/
|
|
196
|
+
export type TaskHandler = (input: CatalogCall) => unknown | GapSignal | Promise<unknown | GapSignal>;
|
|
197
|
+
/**
|
|
198
|
+
* Raw values accepted from scorer functions before normalization.
|
|
199
|
+
*/
|
|
200
|
+
export type ScorerReturn = boolean | number | StructuredScore | NumericScore | GapSignal;
|
|
201
|
+
/**
|
|
202
|
+
* Structured pass/fail scorer result.
|
|
203
|
+
*/
|
|
204
|
+
export type StructuredScore = {
|
|
205
|
+
pass: boolean;
|
|
206
|
+
message?: string | undefined;
|
|
207
|
+
actual?: unknown;
|
|
208
|
+
expected?: unknown;
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Numeric scorer result. The result passes when `score >= threshold`.
|
|
212
|
+
*/
|
|
213
|
+
export type NumericScore = {
|
|
214
|
+
score: number;
|
|
215
|
+
threshold?: number | undefined;
|
|
216
|
+
message?: string | undefined;
|
|
217
|
+
actual?: unknown;
|
|
218
|
+
expected?: unknown;
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Normalized scorer result returned by Melusine.
|
|
222
|
+
*/
|
|
223
|
+
export type ScoreResult = {
|
|
224
|
+
/**
|
|
225
|
+
* Whether the score passed.
|
|
226
|
+
*/
|
|
227
|
+
pass: boolean;
|
|
228
|
+
/**
|
|
229
|
+
* Numeric score when one was returned.
|
|
230
|
+
*/
|
|
231
|
+
score?: number | undefined;
|
|
232
|
+
/**
|
|
233
|
+
* Numeric threshold when one was used.
|
|
234
|
+
*/
|
|
235
|
+
threshold?: number | undefined;
|
|
236
|
+
/**
|
|
237
|
+
* Optional failure or detail message.
|
|
238
|
+
*/
|
|
239
|
+
message?: string | undefined;
|
|
240
|
+
/**
|
|
241
|
+
* Optional actual value.
|
|
242
|
+
*/
|
|
243
|
+
actual?: unknown;
|
|
244
|
+
/**
|
|
245
|
+
* Optional expected value.
|
|
246
|
+
*/
|
|
247
|
+
expected?: unknown;
|
|
248
|
+
};
|
|
249
|
+
/**
|
|
250
|
+
* Function wrapped by `scorer(...)`.
|
|
251
|
+
*/
|
|
252
|
+
export type ScorerHandler = (input: CatalogCall) => ScorerReturn | Promise<ScorerReturn>;
|
|
253
|
+
/**
|
|
254
|
+
* Shared catalog entry options.
|
|
255
|
+
*/
|
|
256
|
+
export type CatalogEntryOptions = {
|
|
257
|
+
/**
|
|
258
|
+
* Minimum number of args required before running.
|
|
259
|
+
*/
|
|
260
|
+
requiredArgs?: number | undefined;
|
|
261
|
+
/**
|
|
262
|
+
* Option keys required before running.
|
|
263
|
+
*/
|
|
264
|
+
requiredOptions?: string[] | undefined;
|
|
265
|
+
/**
|
|
266
|
+
* Entry-owned metadata.
|
|
267
|
+
*/
|
|
268
|
+
meta?: Record<string, unknown> | undefined;
|
|
269
|
+
};
|
|
270
|
+
/**
|
|
271
|
+
* Options for `task(...)`.
|
|
272
|
+
*/
|
|
273
|
+
export type TaskOptions = CatalogEntryOptions & {
|
|
274
|
+
as?: string;
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* Options for `scorer(...)`.
|
|
278
|
+
*/
|
|
279
|
+
export type ScorerOptions = CatalogEntryOptions & {
|
|
280
|
+
threshold?: number;
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Reusable catalog task entry.
|
|
284
|
+
*/
|
|
285
|
+
export type TaskEntry = {
|
|
286
|
+
kind: "task";
|
|
287
|
+
run: TaskHandler;
|
|
288
|
+
/**
|
|
289
|
+
* Default context storage key.
|
|
290
|
+
*/
|
|
291
|
+
as: string | undefined;
|
|
292
|
+
/**
|
|
293
|
+
* Minimum required args.
|
|
294
|
+
*/
|
|
295
|
+
requiredArgs: number | undefined;
|
|
296
|
+
/**
|
|
297
|
+
* Required option keys.
|
|
298
|
+
*/
|
|
299
|
+
requiredOptions: string[];
|
|
300
|
+
/**
|
|
301
|
+
* Entry-owned metadata.
|
|
302
|
+
*/
|
|
303
|
+
meta: Record<string, unknown>;
|
|
304
|
+
};
|
|
305
|
+
/**
|
|
306
|
+
* Reusable catalog scorer entry.
|
|
307
|
+
*/
|
|
308
|
+
export type ScorerEntry = {
|
|
309
|
+
kind: "scorer";
|
|
310
|
+
run: ScorerHandler;
|
|
311
|
+
/**
|
|
312
|
+
* Default numeric score threshold.
|
|
313
|
+
*/
|
|
314
|
+
threshold: number | undefined;
|
|
315
|
+
/**
|
|
316
|
+
* Minimum required args.
|
|
317
|
+
*/
|
|
318
|
+
requiredArgs: number | undefined;
|
|
319
|
+
/**
|
|
320
|
+
* Required option keys.
|
|
321
|
+
*/
|
|
322
|
+
requiredOptions: string[];
|
|
323
|
+
/**
|
|
324
|
+
* Entry-owned metadata.
|
|
325
|
+
*/
|
|
326
|
+
meta: Record<string, unknown>;
|
|
327
|
+
};
|
|
328
|
+
/**
|
|
329
|
+
* A reusable catalog entry.
|
|
330
|
+
*/
|
|
331
|
+
export type CatalogEntry = TaskEntry | ScorerEntry;
|
|
332
|
+
/**
|
|
333
|
+
* ESM catalog object keyed by Mermaid node id or frontmatter `use`.
|
|
334
|
+
*/
|
|
335
|
+
export type Catalog = Record<string, CatalogEntry>;
|
|
336
|
+
/**
|
|
337
|
+
* Run/validation options for catalog execution.
|
|
338
|
+
*/
|
|
339
|
+
export type RunOptions = {
|
|
340
|
+
/**
|
|
341
|
+
* Count decision scorers toward the
|
|
342
|
+
* per-path scorer coverage rule. Defaults to false.
|
|
343
|
+
*/
|
|
344
|
+
countDecisionScorers?: boolean | undefined;
|
|
345
|
+
};
|
|
346
|
+
/**
|
|
347
|
+
* A collected unresolved node.
|
|
348
|
+
*/
|
|
349
|
+
export type Gap = {
|
|
350
|
+
/**
|
|
351
|
+
* Node that produced the gap.
|
|
352
|
+
*/
|
|
353
|
+
nodeId: string;
|
|
354
|
+
/**
|
|
355
|
+
* Gap kind.
|
|
356
|
+
*/
|
|
357
|
+
kind: "todo" | "hole";
|
|
358
|
+
/**
|
|
359
|
+
* Gap reason.
|
|
360
|
+
*/
|
|
361
|
+
reason: string;
|
|
362
|
+
/**
|
|
363
|
+
* Catalog key involved in the gap.
|
|
364
|
+
*/
|
|
365
|
+
entryKey?: string | undefined;
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* Runtime error produced while executing a catalog entry.
|
|
369
|
+
*/
|
|
370
|
+
export type RunError = {
|
|
371
|
+
/**
|
|
372
|
+
* Node that failed.
|
|
373
|
+
*/
|
|
374
|
+
nodeId: string;
|
|
375
|
+
/**
|
|
376
|
+
* Catalog key that failed.
|
|
377
|
+
*/
|
|
378
|
+
entryKey: string;
|
|
379
|
+
/**
|
|
380
|
+
* Error message.
|
|
381
|
+
*/
|
|
382
|
+
message: string;
|
|
383
|
+
};
|
|
384
|
+
/**
|
|
385
|
+
* Task execution step.
|
|
386
|
+
*/
|
|
387
|
+
export type TaskStep = {
|
|
388
|
+
type: "task";
|
|
389
|
+
/**
|
|
390
|
+
* Source node.
|
|
391
|
+
*/
|
|
392
|
+
node: Node;
|
|
393
|
+
/**
|
|
394
|
+
* Catalog key.
|
|
395
|
+
*/
|
|
396
|
+
entryKey: string;
|
|
397
|
+
/**
|
|
398
|
+
* Args supplied to the task.
|
|
399
|
+
*/
|
|
400
|
+
args: unknown[];
|
|
401
|
+
/**
|
|
402
|
+
* Task return value.
|
|
403
|
+
*/
|
|
404
|
+
output: unknown;
|
|
405
|
+
/**
|
|
406
|
+
* Context key used for the task output.
|
|
407
|
+
*/
|
|
408
|
+
storedAs: string;
|
|
409
|
+
};
|
|
410
|
+
/**
|
|
411
|
+
* Scorer execution step.
|
|
412
|
+
*/
|
|
413
|
+
export type ScoreStep = {
|
|
414
|
+
type: "score";
|
|
415
|
+
/**
|
|
416
|
+
* Source node.
|
|
417
|
+
*/
|
|
418
|
+
node: Node;
|
|
419
|
+
/**
|
|
420
|
+
* Catalog key.
|
|
421
|
+
*/
|
|
422
|
+
entryKey: string;
|
|
423
|
+
/**
|
|
424
|
+
* How the score was used.
|
|
425
|
+
*/
|
|
426
|
+
role: "decision" | "outcome";
|
|
427
|
+
/**
|
|
428
|
+
* Args supplied to the scorer.
|
|
429
|
+
*/
|
|
430
|
+
args: unknown[];
|
|
431
|
+
/**
|
|
432
|
+
* Normalized score result.
|
|
433
|
+
*/
|
|
434
|
+
result: ScoreResult;
|
|
435
|
+
/**
|
|
436
|
+
* Branch label selected by a decision scorer.
|
|
437
|
+
*/
|
|
438
|
+
branch?: string | undefined;
|
|
439
|
+
};
|
|
440
|
+
/**
|
|
441
|
+
* Gap execution step.
|
|
442
|
+
*/
|
|
443
|
+
export type GapStep = {
|
|
444
|
+
type: "gap";
|
|
445
|
+
/**
|
|
446
|
+
* Source node.
|
|
447
|
+
*/
|
|
448
|
+
node: Node;
|
|
449
|
+
/**
|
|
450
|
+
* Catalog key.
|
|
451
|
+
*/
|
|
452
|
+
entryKey: string;
|
|
453
|
+
/**
|
|
454
|
+
* Gap collected for the node.
|
|
455
|
+
*/
|
|
456
|
+
gap: Gap;
|
|
457
|
+
};
|
|
458
|
+
/**
|
|
459
|
+
* Error execution step.
|
|
460
|
+
*/
|
|
461
|
+
export type ErrorStep = {
|
|
462
|
+
type: "error";
|
|
463
|
+
/**
|
|
464
|
+
* Source node.
|
|
465
|
+
*/
|
|
466
|
+
node: Node;
|
|
467
|
+
/**
|
|
468
|
+
* Catalog key.
|
|
469
|
+
*/
|
|
470
|
+
entryKey: string;
|
|
471
|
+
/**
|
|
472
|
+
* Runtime error collected for the node.
|
|
473
|
+
*/
|
|
474
|
+
error: RunError;
|
|
475
|
+
};
|
|
476
|
+
/**
|
|
477
|
+
* Result returned by `run()` and `runText()`.
|
|
478
|
+
*/
|
|
479
|
+
export type RunResult = {
|
|
480
|
+
/**
|
|
481
|
+
* True when diagnostics, gaps, runtime errors, and
|
|
482
|
+
* outcome scorers all pass.
|
|
483
|
+
*/
|
|
484
|
+
ok: boolean;
|
|
485
|
+
/**
|
|
486
|
+
* Journey frontmatter minus `nodes`.
|
|
487
|
+
*/
|
|
488
|
+
meta: Record<string, unknown>;
|
|
489
|
+
/**
|
|
490
|
+
* Final execution context.
|
|
491
|
+
*/
|
|
492
|
+
context: Record<string, unknown>;
|
|
493
|
+
/**
|
|
494
|
+
* Executed steps in deterministic order.
|
|
495
|
+
*/
|
|
496
|
+
steps: ExecutedStep[];
|
|
497
|
+
/**
|
|
498
|
+
* Executed scorer steps.
|
|
499
|
+
*/
|
|
500
|
+
scores: ScoreStep[];
|
|
501
|
+
/**
|
|
502
|
+
* Reachable unresolved nodes in execution order.
|
|
503
|
+
*/
|
|
504
|
+
gaps: Gap[];
|
|
505
|
+
/**
|
|
506
|
+
* Runtime errors in execution order.
|
|
507
|
+
*/
|
|
508
|
+
errors: RunError[];
|
|
509
|
+
/**
|
|
510
|
+
* Parser, structural, and catalog diagnostics.
|
|
511
|
+
*/
|
|
512
|
+
diagnostics: Diagnostic[];
|
|
513
|
+
};
|
|
514
|
+
/**
|
|
515
|
+
* Options used when generating a node:test wrapper.
|
|
516
|
+
*/
|
|
517
|
+
export type GenerateOptions = {
|
|
518
|
+
/**
|
|
519
|
+
* Path imported by the generated wrapper.
|
|
520
|
+
*/
|
|
521
|
+
journeyPath: string;
|
|
522
|
+
/**
|
|
523
|
+
* Path imported by the generated wrapper.
|
|
524
|
+
*/
|
|
525
|
+
catalogPath: string;
|
|
526
|
+
/**
|
|
527
|
+
* Output path, used to make relative imports.
|
|
528
|
+
*/
|
|
529
|
+
outPath?: string | undefined;
|
|
530
|
+
/**
|
|
531
|
+
* Named catalog export to use.
|
|
532
|
+
*/
|
|
533
|
+
exportName?: string | undefined;
|
|
534
|
+
/**
|
|
535
|
+
* Test name. Defaults to journey metadata or file name.
|
|
536
|
+
*/
|
|
537
|
+
testName?: string | undefined;
|
|
538
|
+
/**
|
|
539
|
+
* Package import name. Defaults to `melusine`.
|
|
540
|
+
*/
|
|
541
|
+
packageName?: string | undefined;
|
|
542
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('./types.js').JourneyGraph} JourneyGraph
|
|
3
|
+
* @typedef {import('./types.js').Diagnostic} Diagnostic
|
|
4
|
+
* @typedef {import('./types.js').Node} Node
|
|
5
|
+
* @typedef {import('./types.js').Edge} Edge
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Validate the structural rules that are independent of any target.
|
|
9
|
+
*
|
|
10
|
+
* @param {JourneyGraph} graph Parsed graph to validate. `graph.start` is updated
|
|
11
|
+
* when the unique start terminal can be identified.
|
|
12
|
+
* @returns {Diagnostic[]} Structural diagnostics in deterministic order.
|
|
13
|
+
*/
|
|
14
|
+
export function validate(graph: JourneyGraph): Diagnostic[];
|
|
15
|
+
/**
|
|
16
|
+
* @param {JourneyGraph} graph
|
|
17
|
+
* @returns {Map<string, Edge[]>}
|
|
18
|
+
*/
|
|
19
|
+
export function incomingByNode(graph: JourneyGraph): Map<string, Edge[]>;
|
|
20
|
+
/**
|
|
21
|
+
* @param {JourneyGraph} graph
|
|
22
|
+
* @returns {Map<string, Edge[]>}
|
|
23
|
+
*/
|
|
24
|
+
export function outgoingByNode(graph: JourneyGraph): Map<string, Edge[]>;
|
|
25
|
+
export type JourneyGraph = import("./types.js").JourneyGraph;
|
|
26
|
+
export type Diagnostic = import("./types.js").Diagnostic;
|
|
27
|
+
export type Node = import("./types.js").Node;
|
|
28
|
+
export type Edge = import("./types.js").Edge;
|
package/dist/walk.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('./types.js').Edge} Edge
|
|
3
|
+
* @typedef {import('./types.js').JourneyGraph} JourneyGraph
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Enumerate all start-to-terminal graph paths in deterministic source order.
|
|
7
|
+
*
|
|
8
|
+
* @param {JourneyGraph} graph Validated acyclic graph.
|
|
9
|
+
* @returns {string[][]}
|
|
10
|
+
*/
|
|
11
|
+
export function enumeratePaths(graph: JourneyGraph): string[][];
|
|
12
|
+
/**
|
|
13
|
+
* Select the outgoing decision branch for a normalized scorer result.
|
|
14
|
+
*
|
|
15
|
+
* `yes` and `no` labels are preferred. If the expected label is absent, source
|
|
16
|
+
* order keeps branch selection deterministic.
|
|
17
|
+
*
|
|
18
|
+
* @param {Edge[]} edges Outgoing decision edges in source order.
|
|
19
|
+
* @param {boolean} pass Decision scorer pass value.
|
|
20
|
+
* @returns {Edge | undefined}
|
|
21
|
+
*/
|
|
22
|
+
export function selectDecisionEdge(edges: Edge[], pass: boolean): Edge | undefined;
|
|
23
|
+
export type Edge = import("./types.js").Edge;
|
|
24
|
+
export type JourneyGraph = import("./types.js").JourneyGraph;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Each example shows a different journey shape using the same catalog-mode API.
|
|
4
|
+
|
|
5
|
+
- `onboarding/` is a linear flow.
|
|
6
|
+
- `vending/` has one decision branch.
|
|
7
|
+
- `order-fulfillment/` has nested decisions.
|
|
8
|
+
|
|
9
|
+
Every folder includes:
|
|
10
|
+
|
|
11
|
+
- `journey.md` with YAML frontmatter and Mermaid.
|
|
12
|
+
- `catalog.js` with reusable `task(...)` and `scorer(...)` entries.
|
|
13
|
+
- A small toy system under test.
|
|
14
|
+
- `generated/*.test.mjs`, a committed wrapper generated by `melusine compile`.
|
|
15
|
+
|
|
16
|
+
Run all generated examples:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm run generate:examples
|
|
20
|
+
node --test examples/*/generated/*.test.mjs
|
|
21
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Onboarding Example
|
|
2
|
+
|
|
3
|
+
This is the simplest example: one start task, three process tasks, and one outcome scorer.
|
|
4
|
+
|
|
5
|
+
Files:
|
|
6
|
+
|
|
7
|
+
- `journey.md` defines the Mermaid flow.
|
|
8
|
+
- `catalog.js` creates and reuses an `OnboardingSession`.
|
|
9
|
+
- `onboarding-session.mjs` is the toy system under test.
|
|
10
|
+
- `generated/onboarding.test.mjs` is the generated wrapper.
|
|
11
|
+
|
|
12
|
+
Run it:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
node src/cli.js test examples/onboarding/journey.md --catalog examples/onboarding/catalog.js
|
|
16
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { scorer, task } from 'melusine';
|
|
4
|
+
import { OnboardingSession } from './onboarding-session.mjs';
|
|
5
|
+
|
|
6
|
+
export const catalog = {
|
|
7
|
+
start: task(() => new OnboardingSession(), { as: 'session' }),
|
|
8
|
+
|
|
9
|
+
collect: task(({ args, context }) => {
|
|
10
|
+
context.session.collectEmail(args[0]);
|
|
11
|
+
}, { requiredArgs: 1 }),
|
|
12
|
+
|
|
13
|
+
verify: task(({ context }) => {
|
|
14
|
+
context.session.verifyEmail();
|
|
15
|
+
}),
|
|
16
|
+
|
|
17
|
+
create: task(({ args, context }) => {
|
|
18
|
+
context.session.createWorkspace(args[0]);
|
|
19
|
+
}, { requiredArgs: 1 }),
|
|
20
|
+
|
|
21
|
+
ready: scorer(({ context }) => ({
|
|
22
|
+
pass: context.session.workspace === 'Analytical Engine' && context.session.verified === true,
|
|
23
|
+
actual: { workspace: context.session.workspace, verified: context.session.verified },
|
|
24
|
+
expected: { workspace: 'Analytical Engine', verified: true },
|
|
25
|
+
message: 'workspace should be ready after verified onboarding',
|
|
26
|
+
})),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default catalog;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { formatRunFailure, runText } from "melusine";
|
|
5
|
+
import * as catalogModule from "../catalog.js";
|
|
6
|
+
|
|
7
|
+
const catalog = (catalogModule.default ?? catalogModule.catalog);
|
|
8
|
+
const journeyUrl = new URL("../journey.md", import.meta.url);
|
|
9
|
+
|
|
10
|
+
test("journey: onboarding-linear", async () => {
|
|
11
|
+
assert.ok(catalog && typeof catalog === 'object', 'catalog module did not export a catalog object');
|
|
12
|
+
const text = await readFile(journeyUrl, 'utf8');
|
|
13
|
+
const result = await runText(text, catalog);
|
|
14
|
+
assert.equal(result.ok, true, formatRunFailure(result));
|
|
15
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
journey: onboarding-linear
|
|
3
|
+
nodes:
|
|
4
|
+
start: { as: session }
|
|
5
|
+
collect: { args: ["ada@example.com"] }
|
|
6
|
+
create: { args: ["Analytical Engine"] }
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Onboarding journey
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
graph LR
|
|
13
|
+
start(["Visitor opens signup"]) --> collect
|
|
14
|
+
collect["Enter email"] --> verify
|
|
15
|
+
verify["Verify email"] --> create
|
|
16
|
+
create["Create workspace"] --> ready
|
|
17
|
+
ready(["Workspace ready"])
|
|
18
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class OnboardingSession {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.email = null;
|
|
4
|
+
this.verified = false;
|
|
5
|
+
this.workspace = null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
collectEmail(email) {
|
|
9
|
+
this.email = email;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
verifyEmail() {
|
|
13
|
+
if (!this.email) throw new Error('email required');
|
|
14
|
+
this.verified = true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
createWorkspace(name) {
|
|
18
|
+
if (!this.verified) throw new Error('email must be verified');
|
|
19
|
+
this.workspace = name;
|
|
20
|
+
}
|
|
21
|
+
}
|