Configuration (jaunt.toml)
Every jaunt.toml section and key, and how strict validation behaves.
Jaunt finds the project root by walking upward from the current directory until
it hits a jaunt.toml. jaunt init scaffolds a small, opinionated starter. Version
1 keeps Python paths under [paths]; version 2 moves language-local settings under
[target.py] and [target.ts]. Both accepted shapes are documented below.
A minimal config sets only paths:
version = 1
[paths]
source_roots = ["src"]
test_roots = ["tests"]
generated_dir = "__generated__"Version 2 language targets
Version 2 keeps shared Codex, daemon, context, and semantic-gate settings at the top level and moves language-specific paths under targets. A root may configure Python, TypeScript, or both:
version = 2
[target.py]
source_roots = ["services/api/src"]
test_roots = ["services/api/tests"]
generated_dir = "__generated__"
infer_deps = true
test_infer_deps = true
emit_stubs = true
ty_retry_attempts = 1
async_runner = "asyncio"
check_generated_imports = true
generated_import_allowlist = []
pytest_args = ["-q"]
auto_class_tests = false
contract_battery_dir = "tests/contract"
[target.ts]
source_roots = ["packages/*/src"]
test_roots = ["packages/*/tests"]
projects = ["tsconfig.json"]
test_projects = ["tsconfig.test.json"]
tool_owner = "."
generated_dir = "__generated__"
test_runner = "vitest"
vitest_config = ""
vitest_args = [] # reserved; the protected runner requires this to remain empty
auto_class_tests = false
fast_check_runs = 50
contract_battery_dir = "tests/contract"
[build]
jobs = 8
include_target_tests = false
instructions = []
[test]
jobs = 4
[prompts.py]
build_system = ""
build_preamble = ""
build_module = ""
test_system = ""
test_module = ""
project_overview_system = ""
project_overview_user = ""
[prompts.ts]
build_system = ""
build_module = ""
test_system = ""
test_module = ""
design_system = ""
design_user = ""
[contract]
derive = ["examples", "errors"]
strength = true
property_max_examples = 50[target.ts].projects and test_projects are explicit; Jaunt does not guess the
nearest tsconfig.json. tool_owner must directly declare @usejaunt/ts and a
supported project-local TypeScript compiler. Version 2 cannot also contain [paths].
The other shared sections (llm, agent, codex, daemon, skills,
semantic_gate, and context) use the same keys and defaults shown in the version-1
schema below. In version 2, contract.battery_dir moves to each target as
contract_battery_dir; the remaining [contract] keys stay shared. Version-1 files
keep their existing behavior and JSON output.
Use jaunt migrate --config-v2 to preview a deterministic version-1 rewrite and
jaunt migrate --config-v2 --apply to commit it. The migration is model-free and
refuses a dirty worktree unless --force is supplied.
Version 1 full schema
Every key outside version is optional and shown with its default.
version = 1
[paths]
source_roots = ["src"] # dirs scanned for specs (package parent, e.g. "src")
test_roots = ["tests"] # dirs scanned for @jaunt.test specs
generated_dir = "__generated__" # output dir for generated code
[llm]
# Retained for back-compat and ignored by the Codex engine. The model is set in
# [codex], and Codex authenticates via `codex login` / CODEX_API_KEY, not
# api_key_env. The provider and anthropic_* keys below are pre-Codex leftovers
# with no effect today; they stay in the schema so old configs still validate.
provider = "openai"
model = "gpt-5.2"
api_key_env = "OPENAI_API_KEY"
max_cost_per_build = 5.0
reasoning_effort = "high" # informational under Codex
anthropic_thinking_budget_tokens = 2048
prompt_cache = false
prompt_cache_key = ""
[build]
jobs = 8 # parallel build workers
infer_deps = true # AST-infer dependency edges in addition to deps=
ty_retry_attempts = 1 # ty-driven regeneration retries
async_runner = "asyncio" # asyncio | anyio
include_target_tests = false # keep target test source out of build prompts
check_generated_imports = true # reject undeclared imports in generated code
generated_import_allowlist = [] # extra top-level imports to permit in generated code
instructions = [] # persistent extra build-generation instructions
emit_stubs = true # emit provenance-headed .pyi stubs (opt-out)
[test]
jobs = 4
infer_deps = true
pytest_args = ["-q"]
auto_class_tests = false # auto-generate pytest coverage for class specs
[prompts]
# Override packaged prompt templates with project-local files (empty = default).
build_system = ""
build_preamble = ""
build_module = ""
test_system = ""
test_module = ""
project_overview_system = ""
project_overview_user = ""
[agent]
engine = "codex" # the only supported engine
[codex]
model = "gpt-5.6-sol"
reasoning_effort = "medium" # low | medium | high
sandbox = "workspace-write"
# Opt-in: embed `codex --version` in freshness fingerprints. Couples `jaunt
# check` to environments that have the codex binary installed.
fingerprint_cli_version = false
features = []
# Raw passthrough to the `codex` CLI (advanced); keys here are not validated.
[codex.config]
[daemon]
poll_interval = 2.0 # seconds between HEAD polls
max_jobs = 0 # 0 -> build.jobs
notify_command = "" # optional shell command run on job completion
auto_commit = false # false parks green jobs as proposals (land later)
[skills]
auto = true # auto-generate PyPI helper skills for imports
max_chars_per_skill = 8000 # retained for back-compat (unused by Codex builder)
inject_user_skills = [] # retained for back-compat (unused by Codex builder)
builtin = true # seed Jaunt's bundled builtin skills
builtin_skills = ["pytest", "ruff", "ty", "uv"] # override to trim/extend the default set
[contract]
battery_dir = "tests/contract" # where derived contract batteries are written
derive = ["examples", "errors"] # case kinds derived from docstring prose
strength = true # run mutation-based strength scoring at reconcile
[semantic_gate]
enabled = true # gate behaviorally-equivalent edits before a rebuild
model = "gpt-5.6-luna" # small model that judges contract equivalence
reasoning_effort = "medium" # low | medium | high
[context]
repo_map = true # maintain treedocs.yaml + inject a repo map
repo_map_file = "treedocs.yaml"
enrich = false # LLM-enrich descriptions (else AST-only, offline)
max_chars = 6000 # cap the injected repo-map block
overview = false # inject a model-written architecture overview
[context.search] # colgrep semantic retrieval (opt-in)
enabled = false # requires the `colgrep` binary on PATH
internal_retrieval = true # seed _context/relevant_*.py from colgrep hits
max_hits = 8Notes on a few keys:
paths.source_rootsaccepts literal directories and globs. Jaunt expands globs in lexical order and routes each module through the most-specific matching import root. An unmatched glob is a config error; a missing literal is tolerated when another source root exists.paths.test_rootsuses the same glob rules. Test roots are grouped by their nearestpyproject.toml, and generated tests go to the first configured root for that owner.prompts.*are treated as file paths read at runtime, not inline text. Copy a default fromsrc/jaunt/prompts/and point at your edited copy.[llm]is informational under the Codex engine. Codex handles its own authentication;llm.api_key_envdoes not configure generation.build.async_runnerselects the pytest marker for generated async tests.[codex.config]is raw-c key=valuepassthrough tocodex exec; keys under it are not validated by Jaunt.
Deeper coverage lives with each feature: [context],
[semantic_gate],
[contract], and
[daemon]. Version-to-version defaults changes are
collected in Upgrading.
The generated-import allowlist
[build] check_generated_imports (on by default) rejects any top-level import
in generated code that is not the standard library, a first-party module, or a
declared project dependency. It catches the model reaching for a package you
never installed. generated_import_allowlist is the escape hatch: distribution
names on this list are permitted even when no pyproject declares them.
[build]
generated_import_allowlist = ["whenever"]Before 1.5.1 you needed this more often than you should have. In a uv workspace
with jaunt.toml at the repo root, the validator resolved declared dependencies
only from the root pyproject. A dependency declared in the owning package's
pyproject — the normal place to declare it — was invisible, so a correct
import whenever was rejected as undeclared and the allowlist was the only way
through. 1.5.1 fixes the resolution: Jaunt now reads the pyproject that owns the
spec's own source file as well as the config-root pyproject, so a dependency
declared at either level resolves on its own.
So you rarely need the allowlist now. Reach for it only when a real import belongs to a distribution that no pyproject declares — a namespace package, a vendored module, an implicit transitive dep you import on purpose. The error message names it as one of the fixes when generation trips the check.
Strict validation
Jaunt validates jaunt.toml against the schema above before doing any work. An
unknown section or key is an error, not a silent no-op, and Jaunt suggests the
closest real name. This catches typos that would otherwise leave a setting
quietly at its default.
A misspelled key:
[build]
infer_dep = trueerror: unknown key 'infer_dep' in [build] — did you mean 'infer_deps'?A misspelled section:
error: unknown key 'buld' in jaunt.toml — did you mean 'build'?Both exit 2. Under --json the same message goes to stderr and stdout carries
{"command": "...", "ok": false, "error": "..."}.
Before a jaunt.toml exists, jaunt instructions prints this whole schema, so
you can see the accepted keys without guessing.
Next: JSON Output.