Configuration
stylist separates two kinds of configuration:
- Style rules (allowed colors/fonts, banned terms) live in the ruleset: versioned domain data, owned by the brand team.
- Operational config (which ruleset, where the API is, failure behavior) is resolved per invocation as described here.
Precedence
Section titled “Precedence”Highest wins:
- Command-line flags (
-ruleset, …) STYLIST_*environment variables- The project file
.stylist.yaml, found by walking up from the working directory (like.gitignoreor.golangci.yml) - A personal fallback ruleset at
~/.stylist/ruleset.yaml, see below - Built-in defaults
Every check/baseline run prints which ruleset it resolved and where
from (e.g. stylist: using ruleset rules/brand.yaml (from .stylist.yaml))
to stderr — ruleset is the identity a loose asset
(one with no repo attached) is attributed under, so it’s worth knowing
which one actually ran. Hook adapters (hook pre-commit, hook claude-code, hook codex) only print this when resolution fell all the
way to the personal fallback below, so a well-configured project stays
quiet on every commit/write.
Personal fallback ruleset
Section titled “Personal fallback ruleset”~/.stylist/ruleset.yaml is an opt-in default consulted only when no
flag, env var, or .stylist.yaml resolves a ruleset — stylist never
creates this file, it only looks for one. It exists for the loose-asset
case: an agent (Claude Code, Codex) generating a one-off asset with no
project file anywhere in its ancestry. Without it, that case is a hard
error today (no ruleset configured...), which is deliberate — leave the
file absent if you’d rather every unconfigured check fail loudly than
silently apply a personal default.
It only applies in local mode: with api_url set, an unresolved
ruleset: is meant to be a server-side ID, and a local file can’t stand
in for one, so this tier is skipped entirely.
.stylist.yaml
Section titled “.stylist.yaml”Checked into the repo root:
ruleset: rules/brand.yaml # path in local mode; ruleset ID in api_url modeapi_url: https://stylist.internal # optional: check via the core APIbaseline: .stylist-baseline.yaml # optional: auto-discovered if presentfail_open: true # default true, see belowaudience: external # optional: "internal" or "external", see belowtone_agent: claude-cli # optional: run tone: locally via the claude CLI, see rulesets.mdoverrides: # optional: skip specific admin-permitted rules, see below terms: banned: ["legacy-slogan"]Relative ruleset/baseline paths resolve against the config file’s
directory, so checks behave identically from any subdirectory.
Environment variables
Section titled “Environment variables”| Variable | Meaning |
|---|---|
STYLIST_RULESET |
Ruleset path (local) or ID (remote) |
STYLIST_API_URL |
Core API base URL; enables remote mode |
STYLIST_API_TOKEN |
Bearer token for the core API (env-only) |
STYLIST_BASELINE |
Baseline file path |
STYLIST_FAIL_OPEN |
true/false, overrides fail_open |
STYLIST_AUDIENCE |
internal/external, overrides audience |
STYLIST_TONE_AGENT |
claude-cli, overrides tone_agent |
STYLIST_LICENSE |
License key (server side, unlocks the dashboard) |
STYLIST_DASH_TOKEN |
Dashboard sign-in token (server side) |
Secrets (STYLIST_API_TOKEN, STYLIST_LICENSE, STYLIST_DASH_TOKEN)
are deliberately not read from .stylist.yaml: checked-in files
must never carry credentials. A YAML key like api_token: is ignored.
Fail-open
Section titled “Fail-open”fail_open: true (the default) means: when checking is impossible
(the API is unreachable, the ruleset file is missing, config is
broken), hooks warn and allow the operation rather than blocking work.
Actual style violations always block regardless.
Set fail_open: false to invert this for strict environments; the hook
then exits 2 when checking is impossible.
Audience
Section titled “Audience”audience: internal or audience: external declares whether the
assets a project/invocation checks are internal-only or client-facing.
It’s admin-declared, not inferred. An invalid value (anything besides
internal/external) is rejected at load time. Leaving it unset
is a legitimate “not yet classified” state, not an error.
Also settable per invocation with -audience (CLI) or STYLIST_AUDIENCE
(env), same precedence as every other setting. It’s surfaced on the
dashboard’s “By audience” breakdown and in each asset’s check history,
and it can also gate which rules apply. See Audience-scoped
rules. Leaving it unset applies
every rule regardless of audience scoping (the safe default: an
audience-scoped rule only ever gets dropped by declaring the other
audience, never added by leaving it blank).
A loose asset is one-off, agent-generated content with no
.stylist.yaml to declare audience: in (see Loose
assets). For a loose asset, audience can also be
declared in-content instead of via flag/env; see Context
nudges.
Rule overrides
Section titled “Rule overrides”overrides: declares which specific rules of a centrally-governed
ruleset this project asks to skip. It’s a plain disable list: an
override only ever relaxes a rule, it never adds one.
overrides: colors: true # skip the whole colors block fonts: false images: false terms: banned: ["legacy-slogan"] # skip by the term's own text required: [] outdated: logos: ["old-logo-2019"] # skip by name colors: ["#ff0000"] # skip by value fonts: []It only takes effect for rules the ruleset itself marked
overridable: true. See Per-project rule
overrides for how a ruleset
opts a rule into this, and for how requests against unauthorized rules
get reported: never silently swallowed, never an error either. The
check just runs as if the request had never been made.
Unlike audience, overrides is file-only: there’s no
-overrides flag or STYLIST_OVERRIDES env var, since it only ever
makes sense as a per-project, checked-in policy decision, never a
per-invocation one. Like audience, it only matters against a
centrally-governed ruleset (api_url mode). A local ruleset file is
already fully under the project’s own control, so there’s nothing to
relax that editing the file directly wouldn’t already do.
Local vs remote mode
Section titled “Local vs remote mode”With no api_url, checks run in-process against a local ruleset file.
With api_url set, ruleset names a server-side ruleset ID, content
is POSTed to the core API, and the verdict comes back over HTTP. The
CLI, hooks, output, and exit codes are identical in both modes.