Skip to Content
DocsIntegrationsCI scaffolds

CI scaffolds

klera ci <target> writes a starter pipeline that runs flows, captures the JSON report, converts it to JUnit XML, and posts results to the test-results pane your CI provider already paints on every PR. Three targets ship today: GitHub Actions, GitLab CI, CircleCI.

The output is a plain YAML file under your CI provider’s conventional path. You commit it, you edit it. klera does not own the file after generation.

Generate

klera ci github-actions --out .github/workflows/klera.yml

Without --out, the generated YAML is printed to stdout — useful for piping into a wrapper script or eyeballing before you commit.

Flags

  • --out <path> — write the generated YAML to <path>. Without it, prints to stdout.
  • --flows <glob> — flow glob the pipeline iterates over. Defaults to flows/*.flow.yaml. Use flows/**/*.flow.{md,yaml} for nested layouts.

What each pipeline does

Every generated pipeline follows the same five-stage shape. Differences are in YAML syntax, not in the data flow.

Install the workspace

pnpm install --frozen-lockfile against Node 20. The scaffolds assume pnpm — if you use npm or yarn, swap the install command in the generated file.

Gate or regenerate the prose cache

Every .flow.md ships a paired .flow.json cache. CI confirms the cache is in sync with the prose before any flows run. Default mode is compileMode: 'check'klera compile --all --check fails the build if any cache is stale and tells the PR author to regenerate locally.

compileMode: 'auto' flips the behaviour: CI regenerates the cache and posts a Markdown comment with the IR step-list before/after for review. See Prose-cache CI below.

Run flows

A shell loop walks the configured flow glob and runs klera run "$flow" --report "reports/$base.json" for each one. Failures are captured but the loop continues so the test-results pane sees every flow, not just the first to fail.

Render JUnit XML

klera report reports/<flow>.json --junit reports/<flow>.junit.xml projects each JSON report into JUnit XML. JSON stays canonical; JUnit is the derivative the CI provider knows how to render.

Publish

Each provider has its own primitive: GitHub uses dorny/test-reporter, GitLab uses artifacts.reports.junit, CircleCI uses store_test_results. All three surface failed klera steps inline on the PR diff with the engine’s matcher / dispatch / assertion error message.

Cross-link: see Reports for the JSON schema, the HTML report, and the --junit projection in detail.

GitHub Actions

The generator emits a workflow that fires on push to main and every pull_request. Truncated:

# Generated by `klera ci github-actions`. Adjust to taste. name: klera on: push: { branches: [main] } pull_request: { branches: [main] } jobs: flows: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: { version: 9 } - uses: actions/setup-node@v4 with: { node-version: 20, cache: pnpm } - run: pnpm install --frozen-lockfile - name: klera — gate on prose-cache freshness run: pnpm exec klera compile --all --check - name: Run klera flows env: OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }} OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: | mkdir -p reports for flow in flows/*.flow.yaml; do base=$(basename "$flow" .flow.yaml) pnpm exec klera run "$flow" --report "reports/$base.json" || EXIT=1 done exit ${EXIT:-0} - name: Render JUnit XML if: always() run: | for json in reports/*.json; do base=$(basename "$json" .json) pnpm exec klera report "$json" --junit "reports/$base.junit.xml" done - name: Publish test results if: always() uses: dorny/test-reporter@v1 with: name: klera flows path: 'reports/*.junit.xml' reporter: java-junit - name: Upload klera reports if: always() uses: actions/upload-artifact@v4 with: { name: klera-reports, path: reports/ }

Optional secrets the generated workflow forwards if set:

  • OTEL_EXPORTER_OTLP_ENDPOINT / OTEL_EXPORTER_OTLP_HEADERS — ship the run to your OTel backend. See Observability.
  • ANTHROPIC_API_KEY — enables LLM-narrated triage verdicts on failure.

GitLab CI

GitLab has built-in JUnit ingestion via artifacts.reports.junit, so the generator’s output is shorter — no third-party plugin needed.

# Generated by `klera ci gitlab-ci`. Adjust to taste. stages: [test] klera: stage: test image: node:20 before_script: - corepack enable - corepack prepare pnpm@9 --activate - pnpm install --frozen-lockfile script: - pnpm exec klera compile --all --check - mkdir -p reports - | EXIT=0 for flow in flows/*.flow.yaml; do base=$(basename "$flow" .flow.yaml) pnpm exec klera run "$flow" --report "reports/$base.json" || EXIT=1 done for json in reports/*.json; do base=$(basename "$json" .json) pnpm exec klera report "$json" --junit "reports/$base.junit.xml" done exit $EXIT artifacts: when: always paths: [reports/] reports: junit: reports/*.junit.xml

Failed klera steps surface in the MR’s “Test summary” pane; the JSON archive ships in artifacts.paths so reviewers can download it for the HTML report.

CircleCI

CircleCI has store_test_results for the test-insights pane and store_artifacts for the JSON archive — both first-class primitives, no orb installs required.

# Generated by `klera ci circleci`. Adjust to taste. version: 2.1 jobs: klera: docker: [{ image: cimg/node:20.14 }] steps: - checkout - run: name: Install pnpm command: corepack enable && corepack prepare pnpm@9 --activate - run: pnpm install --frozen-lockfile - run: name: klera — gate on prose-cache freshness command: pnpm exec klera compile --all --check - run: name: Run klera flows command: | mkdir -p reports EXIT=0 for flow in flows/*.flow.yaml; do base=$(basename "$flow" .flow.yaml) pnpm exec klera run "$flow" --report "reports/$base.json" || EXIT=1 done exit $EXIT - run: name: Render JUnit XML when: always command: | for json in reports/*.json; do base=$(basename "$json" .json) pnpm exec klera report "$json" --junit "reports/$base.junit.xml" done - store_test_results: { path: reports } - store_artifacts: { path: reports } workflows: klera: jobs: [klera]

Prose-cache CI

Every prose flow is hashed against (prose body + element graph + planner version) and the hash is recorded in the cache’s _meta.combined field. Stale caches mean the prose changed but the IR hasn’t been recompiled.

Two CI behaviours, controlled by the generator’s compileMode option:

check (default) runs klera compile --all --check. The build fails on any stale cache. The PR author regenerates locally with klera compile --all and re-pushes. This is the cheapest mode — no LLM call in CI.

auto runs the same check, then on a stale-cache exit code regenerates the affected caches and writes a Markdown flow-diff to the PR comment thread. Reviewers see the IR step-list before/after side-by-side. Requires a planner transport in CI — ANTHROPIC_API_KEY for the API mode, or the local-CLI transport with claude / codex / gemini available on the runner. See Planner transports.

The compile diff is table-formatted today (no inline screenshots). Pipe characters in step text are escaped via escapeForCell so the table stays well-formed.

klera ci emits the check variant by default because it does not require any planner transport in CI. Adopters with a planner wired up who want regenerate-on-PR can hand-edit the generated YAML — the generator option is a one-line tweak and the comment trail in the emitted file points to the exact lines to flip.

Editing after generation

The generated YAML is yours. Common edits: swap pnpm install for npm ci / yarn install / bun install; matrix on platform to run iOS and Android in parallel (the runner image needs the relevant toolchain); add a schedule: cron to catch backend-data drift overnight.

The generator does not rewrite an existing file at --out. If the path already has content, klera errors out — pass a different path or remove the existing file first.

Last updated on