xera Configuration Reference
Every project has a single root config: xera.config.ts. This file is committed to your repo. Secrets live in .env (gitignored). The XERA_AUTH_KEY environment variable is auto-generated by xera init — do not regenerate it, or your cached auth state becomes unreadable.
xera init flags
xera init is interactive by default. Pass flags to skip prompts — useful for CI pipelines or scripted onboarding.
Flags (all optional):
-y, --yes Accept all defaults for any unflagged field
--shape web|api|mixed Project shape (default: web)
--tracker jira|github Issue tracker (default: jira)
--editor <list> Editor(s) to scaffold: claude,cursor,codex or "all" (default: auto-detect or all)
Jira (used when --tracker jira):
--ju, --jira-base-url <url> Jira workspace URL
--pk, --project-keys <keys> Project key(s), comma-separated (e.g. PROJ,OPS)
--sf, --story-field <field> Jira field id for user story (default: description)
--ac, --ac-field <field> Jira field id for acceptance criteria
GitHub (used when --tracker github):
--gr, --github-repo <owner/repo> GitHub repository (e.g. xera-ai/xera)
Web (shape: web | mixed):
--su, --staging-url <url> Web app staging URL
--auth-enabled / --no-auth-enabled Whether login is required (default: true)
--ro, --roles <roles> Test user roles, comma-separated (default: admin,regular)
HTTP (shape: api | mixed):
--au, --api-base-url <url> API base URL
--op, --openapi-path <path> OpenAPI spec path or URL (default: ./openapi.yaml)
--as, --auth-strategy <strategy> bearer | apiKey | basic | oauth-cc | none (default: bearer)
--hr, --http-roles <roles> HTTP roles, comma-separated (default: user)Any unflagged field still prompts interactively unless --yes is set.
Examples:
# Fully interactive
xera init
# Non-interactive web project with defaults
xera init -y --shape web --pk MYPROJ --ju https://myco.atlassian.net --su https://staging.example.com
# Non-interactive API project
xera init -y --shape api --pk MYPROJ --ju https://myco.atlassian.net --au https://api.staging.example.com --as bearer
# Mixed project
xera init -y --shape mixed --pk MYPROJ --ju https://myco.atlassian.net \
--su https://staging.example.com \
--au https://api.staging.example.com --as bearer
# GitHub Issues instead of Jira (uses gh CLI or the GitHub MCP — no token needed)
xera init -y --shape web --tracker github --gr xera-ai/xera --su https://staging.example.comIssue tracker: jira vs github
Exactly one of jira or github must be configured in xera.config.ts.
Jira (default)
jira: {
baseUrl: 'https://myco.atlassian.net',
projectKeys: ['PROJ'],
fields: {
story: 'description',
acceptanceCriteria: 'customfield_10100', // optional
},
}xera uses the Atlassian MCP when available in your editor session, and falls back to the REST API when JIRA_EMAIL + JIRA_API_TOKEN are set in .env. Ticket keys: PROJ-123.
GitHub Issues
github: { repo: 'xera-ai/xera' }No token env vars needed. xera uses the GitHub MCP when available (the skill calls mcp__github__get_issue / mcp__github__add_issue_comment) and falls back to the gh CLI, which carries your existing auth. Ticket keys: GH-<number> — e.g. /xera-fetch GH-42 resolves issue 42 in the configured repo. Acceptance criteria are extracted from the issue body (no separate AC field exists on GitHub).
Precedence (highest to lowest)
- CLI flag (e.g.
--env=prod) - ENV var (e.g.
XERA_ENV=prod) .envfile in project rootxera.config.ts- Built-in defaults
Full schema
import { defineConfig } from '@xera-ai/core';
export default defineConfig({
jira: {
baseUrl: 'https://thanhtrinity.atlassian.net',
projectKeys: ['JIRA', 'XERA'],
fields: {
story: 'description',
acceptanceCriteria: 'customfield_10001',
},
},
web: {
baseUrl: {
local: 'http://localhost:3000',
staging: 'https://staging.example.com',
prod: 'https://example.com',
},
defaultEnv: 'staging',
auth: {
strategy: 'storageState',
ttl: '8h',
refreshBuffer: '30m',
setupScript: './shared/auth-setup.ts',
roles: {
admin: { envEmail: 'TEST_ADMIN_EMAIL', envPassword: 'TEST_ADMIN_PWD' },
regular: { envEmail: 'TEST_USER_EMAIL', envPassword: 'TEST_USER_PWD' },
},
},
testData: {
users: {
admin: { fromAuth: 'admin' },
regular: { fromAuth: 'regular' },
},
},
},
// v0.7 — http adapter (optional; at least one of web/http required)
http: {
baseUrl: {
dev: 'https://api.dev.example.com',
staging: 'https://api.staging.example.com',
},
defaultEnv: 'dev',
spec: './openapi.yaml', // path or URL; optional (enables CONTRACT_DRIFT)
auth: {
strategy: 'bearer', // 'bearer' | 'apiKey' | 'basic' | 'oauth-cc' | 'custom' | 'none'
ttl: '8h',
refreshBuffer: '30m',
roles: {
user: { tokenEnv: 'USER_BEARER_TOKEN' },
admin: { tokenEnv: 'ADMIN_BEARER_TOKEN' },
},
},
},
ai: {
livePageSnapshot: true,
confidenceThreshold: 'medium',
maxRetries: { typecheck: 2, lint: 2, validateFeature: 2 },
},
reporting: {
language: 'en',
postComment: true,
transition: { onPass: null, onFail: null },
artifactLinks: 'git',
},
adapters: ['web'],
});Field-by-field
jira
baseUrl: your Atlassian Cloud workspace URL.projectKeys: prefixes valid for ticket keys, e.g.['JIRA']matchesJIRA-123.fields.story: Jira field id holding the user story. Defaultdescription. Usexera initto detect.fields.acceptanceCriteria: optional. If unset, xera reads AC from the story body.
web
baseUrl: map of environment name → URL. Must includedefaultEnv.defaultEnv: which environment xera targets by default.spec: optional path or URL to an OpenAPI 3 document. When set (and thexeraNetworkrecorder is active),/xera-reportmatches the network calls a web test made against the contract and emitsCONTRACT_DRIFTon a mismatch. Mixed projects can configurehttp.speconce and it applies to web too (http.specwins). Web drift is scoped to documented endpoints (status/schema mismatch) — page/asset loads are ignored.auth.strategy:storageState(browser login form),apiToken(Bearer), ornone.auth.ttl: how long cached auth state is valid (8h,30m, etc.).auth.refreshBuffer: refresh proactively this far before expiry.auth.setupScript: path to yourdefineAuthSetup-exported function.auth.roles: declares which env vars hold credentials for each role.
Web CONTRACT_DRIFT recorder (opt-in). Detecting
CONTRACT_DRIFTon web tests needs the network calls captured.@xera-ai/webexportsattachNetworkRecorder(page, { logPath: process.env.XERA_NETWORK_LOG, scenario, baseUrl })(and thexeraNetworkfixture) — attach it in your base Playwrighttest. It's a no-op unlessXERA_NETWORK_LOGis set, whichxera:execdoes automatically, so plainplaywright testruns are unaffected. Bodies are scrubbed at capture.
ai
livePageSnapshot: probe staging via Playwright MCP during POM generation. Disable for offline workflows.confidenceThreshold: minimum confidence for classifier to commit a verdict.maxRetries: per-gate retry caps in skills.
reporting
language: tracker comment language.enorvi.postComment: master switch that gates publishing the comment to whichever tracker (jiraorgithub) is configured. Legacy aliaspostToJirais accepted for backwards-compat.transition: optional Jira status transitions on pass/fail. Default disabled. GitHub tracker ignores this — GitHub has no equivalent.artifactLinks: where tracker links should point.git(committed paths in repo) orlocal(filesystem).
http (v0.7+)
baseUrl: map of environment name → URL for the API target. Must includedefaultEnv.defaultEnv: which environment xera targets by default.spec: optional path or URL to an OpenAPI 3 document. When set, AI generation uses the schema to derive request bodies and the classifier emitsCONTRACT_DRIFTon schema mismatches. It also powers/xera-feature <KEY> --from-spec(v0.18) — generate Gherkin directly from the spec with no fetched ticket, with--tag/--operation/--pathfilters; the synthetic ticket then flows through/xera-script→/xera-exec→/xera-reportunchanged. When unset, xera still works — schema-derived edge cases,CONTRACT_DRIFTdetection, and--from-specare disabled (doctor warns).auth.strategy: which preset to apply.bearerreadstokenEnvand prefixesAuthorization: Bearer ....apiKeyreadstokenEnvand attachesX-API-Key.basicbase64-encodesuserEnv:passEnv.oauth-ccperforms an OAuth client_credentials handshake againsttokenUrl.customdefers to the body of yourdefineHttpAuthSetupfunction (e.g. a login endpoint that returns a session token).nonedisables auth.auth.ttl/auth.refreshBuffer: same semantics asweb.auth.auth.roles.<name>: per-role env-var references. Fields used depend on strategy:tokenEnv(bearer / apiKey),userEnv+passEnv(basic),tokenUrl+clientIdEnv+clientSecretEnv+optionalscope(oauth-cc).
adapters
- Array of adapter ids to enable.
['web'],['http'], or['web', 'http'](mixed). At least one of the corresponding config blocks must be present. The first element is the default adapter for new tickets whenmeta.json.adapteris absent.
Environment variables
JIRA_EMAIL=
JIRA_API_TOKEN=
TEST_<ROLE>_EMAIL=
TEST_<ROLE>_PWD=
XERA_AUTH_KEY= # 64-char hex, generated by `xera init`
XERA_ENV=staging # optional overridecoverage (v0.8.0+)
Configure the coverage gap report.
| Field | Type | Default | Description |
|---|---|---|---|
staleAfterDays | number | 30 | Window for both "recent activity" (risk formula) and "PASS recency" (STALE detection). Single threshold prevents two-knob mismatch. |
criticalAreas | string[] | [] | Area slugs treated as critical. risk(area) = recent_tickets × 2 + recent_bugs when area ∈ criticalAreas (otherwise multiplier = 1). Use to flag business-critical areas that may not have high ticket activity. |
autoSnapshotOnCoverage | boolean | true | Emit a coverage.snapshot event each time /xera-coverage runs. Powers the Trend tab in the HTML viewer (v0.8.1+). Disable if you don't want to track history. |
Example:
import { defineConfig } from '@xera-ai/core';
export default defineConfig({
adapters: ['web', 'http'],
coverage: {
staleAfterDays: 30,
criticalAreas: ['checkout', 'auth', 'billing'],
autoSnapshotOnCoverage: true,
},
// ...
});Risk formula reference
risk(area) = recent_tickets × critical_boost + recent_bugs
recent_tickets = #tickets with modifies→area, fetchedAt within staleAfterDays
critical_boost = 2 if area ∈ criticalAreas, else 1
recent_bugs = #run.classified events for scenarios touching area,
classification ∈ {REAL_BUG, TEST_OUTDATED}, within staleAfterDaysWeight constants are not user-configurable in v0.8 (see packages/core/src/coverage/risk.ts RISK_WEIGHTS). Tracked for future config knob.
xera doctor
Validates everything above and prints what is missing. Run after any config change.
Arities (since v0.16.1, see #149 / #153):
xera doctor— warn-only; exits 0 even if checks fail.xera doctor --strict— env + config + auth checks; exits non-zero on any failure. Used by/xera-runStep 0 before any ticket artifact exists.xera doctor --strict <TICKET>— adds per-ticket checks (artifact dir,graph-input.json, story.md acceptanceCriteria). Used by/xera-runStep 1.6 after fetch materializes.xera/<TICKET>/.xera doctor --logs <TICKET>— pretty-print.xera/<TICKET>/xera.log.xera doctor --auto-enrich— cron-friendly: backfill graph data for unprocessed tickets without prompting.
Graph (v0.6+)
The project knowledge graph stores event-sourced records under .xera/graph/events/. Configuration:
// xera.config.ts
export default defineConfig({
graph: {
redactionRules: 'default', // 'default' | 'strict' | 'off' — applies to ticket text in events
},
cost: {
dailyCapUsd: 5, // soft warning threshold; doctor flags when exceeded
},
});Files
.xera/graph/events/<yyyy-mm>/*.jsonl— committed event log, one file per skill invocation.xera/graph/snapshot.json— gitignored, regenerable in < 1s.xera/cost-log.jsonl— gitignored, per-machine LLM cost log
TEST_OUTDATED classifier (v0.6.1+)
The TEST_OUTDATED classifier overrides the existing 4-bucket classification when the graph indicates a recent ticket has modified the scenario's SUT area and an LLM judges the failure is intentional.
This classifier is currently not user-configurable — confidence threshold and notification routing use built-in defaults. Tuning hooks (testOutdated.threshold, report.testOutdatedNotify) are tracked for a future release; the schema rejects them today so misconfiguration surfaces immediately rather than silently.
Auto-impact analysis from /xera-run (v0.6.2+)
When /xera-run <TICKET> runs, it can auto-call /xera-impact <TICKET> after fetch and prompt to re-run high-risk scenarios before generating new code.
// xera.config.ts
export default defineConfig({
run: {
autoImpact: {
enabled: true, // set false to disable the auto-trigger
threshold: 6.0, // minimum risk score to count a scenario as "high-risk"
},
},
});The risk score formula:
score = priority_weight × 3
+ edge_type_weight
+ edge_confidence × 2
− days_since_last_pass × 0.1with P0=3, P1=2, P2=1; modifies-same-area edge weight 5; jira-linked.blocks weight 4. Tune threshold to the noise level you can tolerate.
HTML viewer (v0.6.3+)
Generate a single self-contained HTML file visualizing the project knowledge graph:
npx xera-internal graph-render # full snapshot
npx xera-internal graph-render --since 90d # filter to recent activity
npx xera-internal graph-render --ticket ABC-200 --depth 2 # ego-graph centered on one ticket
npx xera-internal graph-render --out custom-path.html # custom output locationThe viewer is a single self-contained HTML file (~700 KB total — vendored vis-network is the bulk). Open it in any browser; works offline. The file is automatically gitignored.
Performance modes (auto-selected based on graph size):
< 500 nodes: full mode — all node types visible500–2000 nodes: ticket-only — scenarios, POMs, and areas hidden> 2000 nodes: text-fallback — writes a placeholder text file
CI publishing: xera init scaffolds .github/workflows/xera-graph.yml which renders the viewer on every PR, uploads it as an artifact, and posts a sticky comment with the artifact link. Reviewers click → open in browser, no clone required.
v0.6.4 changes
run.autoImpact.threshold default raised 6.0 → 8.0. This means /xera-run only prompts to re-run impacted scenarios when at least one has a risk score ≥ 8.0 (i.e. P0 scenario in a heavily-shared SUT area). Below threshold, the step is silent. Set threshold: 6.0 to restore the v0.6.2/v0.6.3 chatty behavior.
xera-internal disputes lists classification.disputed events for review by the QA lead:
npx xera-internal disputes # all disputes, text format
npx xera-internal disputes --since 7d # past week only
npx xera-internal disputes --format json # machine-readablexera doctor --auto-enrich runs non-interactive backfill of unbackfilled tickets, intended for CI:
npx xera-internal doctor --auto-enrich # cron-friendly