Validate in CI
Goal: a repo where a broken story fails the build — dangling references, unwinnable quests, and regressed playthroughs all caught by CI. ~10 minutes.
1. Run the validator headless
From your project directory:
parlance ci-check .
Same validator the editor runs on every save — headless. It prints a project summary (dialogue/node/choice/word counts) and every issue grouped by family, then exits:
| Exit code | Meaning |
|---|---|
0 |
clean |
1 |
validation errors (with --strict: errors or warnings) |
2 |
not a Parlance project |
Start CI with plain ci-check (errors only) so in-progress warnings don't
block the team; graduate to --strict when the backlog is clean — the demo
project stays --strict-clean permanently, and it's a good standard to covet.
2. Turn a playthrough into a test
Validation proves the story is well-formed; route fixtures prove specific playthroughs still work. A route is a scripted walk — which choices to take, with check outcomes forced so the walk is dice-independent — plus assertions about where you end up:
{
"id": "rt_talk_past",
"description": "Talking past the gatekeeper marks you as known at the gate.",
"dialogueId": "dlg_gate_first",
"steps": [{ "choiceId": "ch_1", "forced": "pass" }],
"assertEnd": { "flags": { "talked_past_gate": true } }
}
Drop it at tests/routes/rt_talk_past.json and run:
parlance route --all --strict
Because play is deterministic, the
replay is exact every run. When next month's edit
accidentally gates your only route to the success node, this fixture fails
with the step where the walk diverged. (ch_1 is the choice id from the
branching tutorial; use your own if
you renamed it.) The demo ships five of these, including one asserting the
failure branch still works.
Routes live in tests/, not data/, deliberately: a shipping game never
loads them (layout reference).
3. Wire it into GitHub Actions
name: narrative
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npx @orbitope/parlance-cli ci-check . --strict
- run: npx @orbitope/parlance-cli route --all --strict
Or use the reusable action. The same ci-check is packaged as a composite
action, pinned to a release tag so the check matches that release exactly:
- uses: Orbitope/parlance-releases/validate@vX.Y.Z
with:
project-dir: .
strict: "true"
Pin vX.Y.Z to the latest release — it runs the matching @orbitope/parlance-cli
version, so your CI never drifts from the release you pinned. The action wraps
ci-check; keep the route step above if you also gate on route fixtures.
Now a pull request that breaks the story shows a red ✗ like any other broken build — which changes the social contract: narrative edits get the same review-and-green-check flow as code, and "it validates" stops being a claim anyone has to take on faith. (Why that matters.)
No Node in your pipeline? The independent Python validator (reference) reports the same issues, enforced by a parity test.
4. Read failures fast
On GitHub Actions you usually don't need the log. ci-check (and the
reusable action) marks each issue on the file and line that caused it, so the
pull request's Files changed tab shows the error on the choice, node or
registry entry itself, titled with the check code (REF, SCHEMA, ...) and
carrying the same message as the log.
This turns on by itself when GITHUB_ACTIONS=true, so no setup is needed.
GitHub shows at most 10 errors and 10 warnings per step; the complete list is
in the step's log. To turn it off, pass --annotations none, or set
annotations: "false" on the action
(details).
When CI goes red, the validation checks reference is the decoder ring — every family, what it scans, and how to fix it. In the editor, the same issues are clickable rows that navigate straight to the offending entity.
Where next
That's the full loop — author → validate → playtest → review → CI → engine. The remaining depth lives in the editor guide and the reference pages.