melusine
v0.1.0
Published
Compile Mermaid journey flowcharts into framework-agnostic test plans.
Downloads
133
Readme
melusine
melusine runs Mermaid journey charts as tests.
You write:
- A Markdown file with YAML frontmatter and a Mermaid flowchart.
- A reusable JavaScript catalog with
task(...)andscorer(...)functions.
Melusine parses the chart, validates the graph, runs the catalog functions in graph order, passes task results through context, records gaps, and reports whether the journey passed.
Install
npm install --save-dev melusineFor this repository:
npm install
npm testStart a Project
Create starter files in an existing JS/Node project:
npx melusine initThis creates:
melusine.catalog.jsjourneys/example.journey.mdmelusine:validateandmelusine:testpackage scripts whenpackage.jsonexists
Run the starter:
npm run melusine:validate
npm run melusine:testBuild a Journey
Save this as checkout.journey.md:
---
journey: checkout-happy-path
nodes:
start:
as: checkout
addItem:
args: ["book", 1]
pay:
args: ["card"]
---
# Checkout happy path
```mermaid
graph TD
start(["Customer opens checkout"]) --> addItem
addItem["Add item"] --> payable
payable{"Cart is payable?"}
payable -->|yes| pay
payable -->|no| blocked
pay["Pay"] --> paid
paid(["Order paid"])
blocked(["Payment blocked"])
```GitHub and GitLab can render Mermaid diagrams in Markdown. Useful Mermaid links:
Write a Catalog
Save this as checkout.catalog.js:
import { scorer, task } from 'melusine';
import { Checkout } from './checkout.js';
export default {
start: task(() => new Checkout(), { as: 'checkout' }),
addItem: task(({ args, context }) => {
context.checkout.addItem(args[0], args[1]);
}, { requiredArgs: 2 }),
payable: scorer(({ context }) => context.checkout.total > 0),
pay: task(({ args, context }) => {
context.checkout.pay(args[0]);
}, { requiredArgs: 1 }),
paid: scorer(({ context }) => ({
pass: context.checkout.status === 'paid',
actual: context.checkout.status,
expected: 'paid',
message: 'checkout should be paid',
})),
blocked: scorer(() => ({
pass: false,
message: 'checkout should have been payable',
})),
};Catalog entries are reusable. Multiple journey files can reference the same addItem, pay, and paid entries by node id.
Frontmatter
Frontmatter is the YAML block at the top of the journey file.
---
journey: checkout-happy-path
nodes:
start:
as: checkout
addItem:
args: ["book", 1]
alternateAdd:
use: addItem
args: ["pen", 2]
---nodes is keyed by Mermaid node id.
argsbecomesinput.argsfor the catalog function.asstores a task result under that context key.usepoints one node at a different catalog key.- Extra fields become
input.config.options.
Everything outside nodes becomes input.meta.
Graph Rules
Use a Mermaid graph or flowchart block:
graph TD
start(["Start"]) --> step
step["Do something"] --> decision
decision{"Choose?"}
decision -->|yes| done
decision -->|no| failed
done(["Done"])
failed(["Failed"])Supported in v1:
| Shape | Example | Meaning |
| --- | --- | --- |
| Terminal | start(["Start"]) | Start or final outcome |
| Process | step["Do something"] | Task |
| Decision | ok{"Ready?"} | Branch scorer |
| Edge | A --> B | Next node |
| Labeled edge | A -->|yes| B | Decision branch |
Rules:
- There must be one start node: a terminal with no incoming edges.
- Process nodes must map to tasks.
- Decision nodes must map to scorers.
- Final terminal nodes must map to scorers.
- Every completed path needs an outcome scorer by default.
- Branches cannot merge back together in v1.
- Only
-->edges are supported.
CLI
melusine validate <journey.md> [--catalog <catalog.js>] [--export <name>] [--json]
melusine test <journey.md> --catalog <catalog.js> [--export <name>] [--json]
melusine compile <journey.md> --catalog <catalog.js> [--export <name>] --out <file>
melusine init [--force]Common workflow:
npx melusine validate checkout.journey.md --catalog checkout.catalog.js
npx melusine test checkout.journey.md --catalog checkout.catalog.js
npx melusine compile checkout.journey.md --catalog checkout.catalog.js --out checkout.test.mjs
node --test checkout.test.mjstest runs the journey directly. compile writes a small node:test wrapper that reads the journey file, imports the catalog, and calls the same runner.
Library API
import { parse, runText, scorer, task } from 'melusine';
const result = await runText(markdown, catalog);Important exports:
task(fn, options)defines a reusable action.scorer(fn, options)defines a reusable check.runText(text, catalog)parses and executes a journey.parse(text)returns the graph, frontmatter, and diagnostics.generateTest(options)creates a generated wrapper string.
Scorers can return:
true
false
{ pass: true, message: 'ok', actual, expected }
0.8
{ score: 0.8, threshold: 0.7 }Numeric scores pass when score >= threshold. The default threshold is 1.
Examples
The examples/ directory has complete runnable examples:
examples/onboarding/shows a linear flow.examples/vending/shows one decision branch.examples/order-fulfillment/shows nested decisions.
Run all examples:
npm run generate:examples
node --test examples/*/generated/*.test.mjsGaps
A catalog entry can return todo(reason) or hole(reason). Melusine also creates a hole when a required arg or option is missing.
Gaps make the run fail and appear in result.gaps. They are never converted into passing assertions.
