This document describes the test strategy, test types, and how to run and configure tests for Loki Mode.
Loki Mode is a polyglot codebase. Tests span three languages and runtimes:
- Bash (orchestration engine, CLI, councils, hooks)
- Python (dashboard API, memory system, benchmarks, MCP server)
- TypeScript / JavaScript (the Bun runner under
loki-ts/, the Deno/Node API underapi/, dashboard UI checks)
Because the product's central promise is verified completion ("Loki does not lie about done"), the test suite is treated as a trust layer, not a formality. Trust-surface code (completion councils, quality gates, evidence receipts, the Bun runner, memory integrity) is held to a higher bar: mutation testing, parity checks, and adversarial detectors back the standard unit and integration tests.
- Test the trust surface hardest. Completion detection, override councils, the verified-completion evidence gate, and the RARV-C closure loop are the highest-risk paths. They get unit tests plus mutation testing plus cross-route parity checks.
- Cross-route parity is a first-class concern. Behavior must match between the legacy Bash route and the Bun (
loki-ts) route. Thebun-parityandparity-driftworkflows exist specifically to catch divergence (for example, doctor-output text drift). - Prefer real verification over mocked assertions. Dedicated detectors (
tests/detect-mock-problems.sh,tests/detect-semantic-test-problems.sh,tests/detect-test-mutations.sh,tests/detect-invariant-violations.sh) flag tests that pass without exercising real logic. - Local CI is the canonical pre-push gate.
scripts/local-ci.shmirrors the GitHub Actions workflows so failures are discovered on the developer machine, not after push. GitHub Actions is the post-push verifier. - Run tests in matrices. Python runs across 3.10 through 3.13; the Node suite runs across the supported Node versions. Version-specific breakage (for example, a Python 3.13-only inode race) must be caught by the matrix.
- Bash unit tests live under
tests/(for exampletests/cli/,tests/council/,tests/memory/) and are orchestrated bytests/run-all-tests.sh. They cover bootstrap, task queue, circuit breaker, agent timeout, state recovery, and the memory engine, among others. Several use function extraction: a helper is sourced out ofrun.shand exercised in isolation. When a helper gains a new dependency, the extraction test must extract that dependency too, or the logic is silently skipped. - Python unit tests are
tests/test_*.pyfiles (120+ modules) covering the classifier, composer, calibration, embeddings edge cases, event bus, compose port selection, app-runner PID recycling, and more. They run underpytest. - TypeScript unit tests live under
loki-ts/tests/(70+*.test.tsfiles) and run underbun test. They cover the runner internals: state, prompt building, providers, budget, checkpoint, and shell utilities. - Node/Deno unit tests are
*_test.tsand*.test.jsfiles underapi/andtests/(protocols, observability, policies, audit, integrations), run withnode --test.
tests/integration/holds the integration suite, run viabash tests/integration/run_integration_suite.sh(also exposed asnpm run test:integration).tests/integrations/covers external system integrations (GitHub, Jira, Slack), run withnode --test.- Managed-memory integration is gated behind flags and covered by
tests/managed_memory/(flag matrix, SDK isolation, kill switch, shadow-write, retrieve).
tests/e2e/andtests/live/contain end-to-end and live-path scenarios.- Dashboard E2E uses Playwright from
dashboard-ui/(npx playwright test), requiring the dashboard running on port 57374. - Docker E2E lives under
tests/docker/with dedicated images (Dockerfile.test-runner,Dockerfile.sandbox). - Post-release distribution E2E is run after a release ships: install from the npm tarball, pull the Docker image, and exercise both the Bun and legacy-Bash routes on each channel.
- Mutation testing (
loki-ts): Stryker mutates trust-surface modules (src/runner/state.ts,build_prompt.ts,providers.ts,budget.ts,checkpoint.ts,src/util/shell.ts). Config:loki-ts/stryker.config.json. Thresholds: break 50%, low 60%, high 80%. - Parity tests:
dashboard-ui/scripts/check-parity.js(npm run test:parity) plus thebun-parityandparity-driftworkflows verify the Bun and Bash routes agree. - Visual regression:
dashboard-ui/tests/visual-regression.test.jsvia Jest (npm run test:visual). - Integrity and quality detectors: shell scripts under
tests/that detect mock-only tests, semantic test problems, test mutations, and invariant violations.
# Full default suite (bash syntax checks + node --test suites + managed-memory)
npm test
# Pre-push gate: mirrors every CI workflow. Run this before every git push.
bash scripts/local-ci.shIf local-ci.sh reports "DO NOT PUSH", do not push. Fix the failures and re-run.
# Bash shell test suite
bash tests/run-all-tests.sh
# Python tests (all)
python3 -m pytest
# Python tests (single module)
python3 -m pytest tests/test_classifier.py -v
# TypeScript / Bun runner tests
cd loki-ts && bun test
# Bun runner type check
cd loki-ts && bun run typecheck
# Integration suite
npm run test:integration
# or
bash tests/integration/run_integration_suite.sh
# Dashboard checks (visual + parity)
npm run test:dashboard
npm run test:parity # parity only
npm run test:visual # visual regression only
# Dashboard E2E (needs dashboard on port 57374)
cd dashboard-ui && npx playwright testcd loki-ts && npx stryker runbash -n autonomy/run.sh
bash -n autonomy/loki
bash -n autonomy/completion-council.sh
python3 -c "import ast, os; [ast.parse(open(f'dashboard/{f}').read()) for f in os.listdir('dashboard') if f.endswith('.py')]"Spawned processes and temp files must be cleaned up after any run that starts a server or test runner:
lsof -ti:57374 | xargs kill -9 2>/dev/null || true
pkill -f "loki-run-" 2>/dev/null || true
rm -rf /tmp/loki-* /tmp/test-* /tmp/package /tmp/*.tgz 2>/dev/null || true
# Verify
ps -ef | grep -E "(loki|test)" | grep -v grep || echo "Clean"Note: never run rm -rf /tmp/loki-* while a live loki run is in progress; the orchestrator stages its run script at /tmp/loki-run-*.sh. Check pgrep -f loki-run- first.
| Area | Tool | Config / entry point |
|---|---|---|
| Default test command | mixed (bash, node) | package.json test script |
| Bash suite | bash | tests/run-all-tests.sh |
| Python | pytest | invoked via python3 -m pytest; deps: pytest, pytest-asyncio |
| Bun runner | bun test | loki-ts/package.json, loki-ts/bunfig.toml |
| Node/Deno | node --test |
*_test.ts, *.test.js |
| Mutation | Stryker | loki-ts/stryker.config.json |
| Coverage | bun test --coverage | .github/workflows/coverage.yml |
| Dashboard E2E | Playwright | dashboard-ui/ |
| Visual regression | Jest | dashboard-ui/tests/visual-regression.test.js |
| Pre-push gate | bash | scripts/local-ci.sh |
Python test dependencies commonly required: fastapi, httpx, pydantic, sqlalchemy[asyncio], aiosqlite, uvicorn. Install with pip before running the Python suite.
- Coverage is collected for the
loki-tsBun runner viabun test --coverage(text + lcov reporters). The lcov artifact is uploaded by the coverage workflow. - A minimum line-coverage gate of 70% is enforced for
loki-ts(MIN_LINE_PCT=70in.github/workflows/coverage.yml). If the coverage summary cannot be parsed, the gate fails closed rather than silently passing. - Mutation-score thresholds for trust-surface modules: break at 50%, low at 60%, high at 80% (
loki-ts/stryker.config.json). - The Bash and Python suites do not currently enforce a numeric coverage percentage. Coverage of trust-surface logic is asserted through targeted unit tests, parity checks, and the mock/semantic/mutation detectors rather than a global percentage.
GitHub Actions workflows (under .github/workflows/) run on push and pull request:
| Workflow | File | What it runs |
|---|---|---|
| Tests | test.yml |
Node suite (npm test), Python suite (pytest) across 3.10-3.13, shell suite (tests/run-all-tests.sh), Helm lint |
| Coverage | coverage.yml |
bun test --coverage for loki-ts, enforces 70% line minimum, uploads lcov |
| Bun parity | bun-parity.yml |
Bun-route vs Bash-route behavior parity |
| Parity drift | parity-drift.yml |
Detects output/behavior drift between routes |
| Mutation testing | mutation-testing.yml |
Stryker on trust-surface modules (currently workflow_dispatch; cron disabled) |
| Integrity audit | integrity-audit.yml |
Repository and artifact integrity checks |
| Post-release smoke | post-release-smoke.yml |
Smoke tests against published artifacts after a release |
| Security audit | security-audit.yml |
Dependency and security scanning |
| SBOM | sbom.yml |
Software bill of materials generation |
| ARM64 runtime | arm64-runtime.yml |
Runtime checks on arm64 |
scripts/local-ci.sh is the canonical pre-push gate. It mirrors every GitHub Actions workflow: bun typecheck/test, the full tests/run-all-tests.sh shell suite (matching CI exactly, not a cherry-picked subset), the bun-parity matrix, npm pack contents, SBOM, license audit, npm audit, shellcheck, YAML parse, the no-emoji check, the no-git add -A check, and a cleanup probe.
Run it before every push. The Mac (developer machine) is the discovery channel; GitHub Actions is the post-push verifier. Distinguish real failures from local-environment false alarms (for example a missing pytest-asyncio in a Homebrew Python) before acting on a red result.
Before a release is committed, the npm tarball is verified to contain the built dashboard and web-app artifacts, and a fresh global install is exercised end to end (loki --version, loki web, /api/status). After a release ships, post-release distribution validation runs across npm, Docker, and Homebrew on both the Bun and legacy-Bash routes.