Jaunt
Guides

Contract Mode

Pin committed Python or TypeScript behavior with a derived, reviewable test battery.

You have code you want to keep hand-written — an adopted legacy function, a hot path you've tuned, anything the generator should not touch — but you still want its behavior pinned by a contract. That is contract mode. It is how Jaunt pins its own build-critical core: fifteen framework modules run committed Python contract batteries derived this way. TypeScript uses the same lifecycle with exported functions or concrete classes and committed Vitest batteries.

Jaunt picks a mode per symbol from its marker:

  • Magic mode: Python uses @jaunt.magic / @jaunt.test; TypeScript uses private *.jaunt.ts[x] / *.jaunt-test.ts[x] inputs. The behavioral prose is canonical and Jaunt generates the implementation or test battery.
  • Contract mode: the committed code is canonical. Python uses the runtime no-op @jaunt.contract decorator and derives pytest. TypeScript adopt adds an @jauntContract TSDoc marker and derives Vitest without moving the source behind a generated facade.

Python contract mode covers top-level functions (sync or async) and whole classes. TypeScript covers exported functions and concrete exported classes.

Write a Python contract

import re

import jaunt

_NON_ALNUM = re.compile(r"[^a-z0-9]+")


@jaunt.contract
def slugify(title: str) -> str:
    """
    Convert a human title into a URL-safe slug.

    Examples:
    - "  Hello, World!  " -> "hello-world"
    - "C++ > Java" -> "c-java"
    - "already-slug" -> "already-slug"

    Raises:
    - "" raises ValueError
    - "!!!" raises ValueError
    """
    cleaned = _NON_ALNUM.sub("-", title.strip().lower()).strip("-")
    if not cleaned:
        raise ValueError("title is empty after cleaning")
    return cleaned

The Examples: and Raises: prose is what Jaunt derives test cases from (configurable via [contract] derive = ["examples", "errors"]). The battery is written to tests/contract/ (configurable via [contract] battery_dir) and is committed — it's plain pytest that runs with or without Jaunt installed.

Python workflow

jaunt adopt my_app.slug:slugify   # add @jaunt.contract to existing code + derive its battery
jaunt reconcile                   # derive/refresh committed batteries (calls the model)
jaunt check                       # verify batteries deterministically (CI gate, no model, no API key)
jaunt status                      # per-function drift state + strength score
jaunt eject my_app.slug:slugify   # remove contract tracking; leave plain Python + plain pytest
jaunt eject --all

For Python, jaunt reconcile is the only contract command that can call the model; adopt derives supported structured cases deterministically. jaunt check is fully deterministic and offline, which makes it the CI gate: it exits 4 on any blocking drift state (unbuilt / stale-prose / signature-drift / behavior-drift).

Structured docstring prose (call-form Examples: like the one above) can often be derived deterministically too, so reconcile doesn't always need the model.

TypeScript workflow

Adopt a committed export by path and symbol:

jaunt adopt src/slug.ts#slugify --language ts
jaunt reconcile --language ts
jaunt check --language ts --contracts-only
jaunt status --language ts
jaunt eject src/slug.ts#slugify --language ts

adopt adds one @jauntContract tag to the export's TSDoc and derives its first battery. The worker projects the declaration without executing the module, and the battery imports the ordinary committed source. reconcile refreshes stale batteries; adopt and reconcile can call the test-generation model, while check, status, and eject do not. check typechecks and runs the committed batteries. eject removes the marker and its battery while leaving the implementation untouched.

TypeScript batteries live under [target.ts].contract_battery_dir and are ordinary committed Vitest files. The source declaration and TSDoc are the contract. See the TypeScript target guide for mutation strength, package-output checks, and magic-module ejection.

Python async functions and fixtures

Contract mode covers async def functions. The derived test functions are async def and await the contract function (with pytest-asyncio auto mode). See examples/contract_async/ in the repo.

Derived cases may declare pytest fixtures in the docstring (Fixtures: db), resolved from tests/contract/conftest.py. That's the seam for contracts that need setup — a database handle, a temp dir, a fake clock — without giving up deterministic derivation.

Whole classes

A class-level Python @jaunt.contract pins the class's public behavior. TypeScript accepts a concrete exported class and derives cases from its declaration and TSDoc; abstract classes and authored private or protected members are outside the alpha.

Strength scoring

With [contract] strength = true (the default), jaunt reconcile runs mutation-based strength scoring: it estimates how well your pinned examples actually constrain the implementation. A function with one weak example gets a low score, and jaunt status shows the gap so you know which contracts need more pinned cases. TypeScript runs supported mutants in disposable overlays and blocks the transaction when a required mutant survives.

Configuration

[contract]
battery_dir = "tests/contract"     # where derived batteries are written
derive = ["examples", "errors"]    # case kinds derived from docstring prose
strength = true                    # mutation-based strength scoring at reconcile

Version-2 workspaces move the language-local battery path under each target. Strength is shared; Python's derive list continues to select its Python case parsers:

[target.ts]
contract_battery_dir = "tests/contract"

[contract]
strength = true

Next: Background Daemon.

On this page