Configuration#
Katalyst reads a .katalyst/ directory, found by walking upward from the
current working directory to the nearest ancestor that contains one. That
ancestor is the repo root; all relative paths resolve against it.
For why the config is shaped this way, see How collections work. To set one up step by step, see Configure checks for a collection.
Layout#
.katalyst/
config.yaml # optional: listing defaults and discovery settings
schemas/ # one JSON Schema file per named schema
book.json
storage/ # one file per storage instance
local.yaml # an instance + the collections it declares
local/ # optional: one file per collection (escape hatch)
books.yamlBy default, schemas and storage instances are discovered by convention:
every file under schemas/ is a schema whose name is its filename stem
(book.json → book), and every file under storage/ is a
storage instance named for its filename stem
(local.yaml → local). config.yaml is optional; it carries listing:
defaults and can switch a kind to explicit discovery, listing definitions
inline instead of as files.
Schemas#
Each file under .katalyst/schemas/ is a JSON Schema. Its name, the
filename stem, is the stable public handle used by schema get <name>, by
an inline schema: <name> key in a document’s frontmatter, and by a
collection’s schema: shorthand. The path can move; the name should not.
Schemas are stored flat; the check library that compiles a schema is determined
by the referencing check type’s kind (the object check uses JSON Schema).
Storage instances#
A storage instance is one configured backend store, plus the collections it
maps onto the domain model. Each file under
.katalyst/storage/ is one instance, named for its filename stem. There is no
implicit instance; katalyst init writes a default local one.
| Key | Required | Default | Meaning |
|---|---|---|---|
type | no | filesystem | Backend kind: filesystem or sqlite. |
root | no | . | Filesystem instance root directory, relative to the repo root. Collection paths resolve against it. |
path | for sqlite | - | SQLite database path, relative to the repo root. Alias for root on SQLite instances. |
collections | no | - | Map of collection name → definition (see below). |
# .katalyst/storage/local.yaml
type: filesystem
root: .
collections:
books:
path: notes/books
schema: book
checks:
- kind: markdown_title_matches_h1Collection names are unique across the whole project (selectors are
<collection>/<item>, with no instance qualifier).
SQLite instances use one table per collection. Each row is one item:
# .katalyst/storage/db.yaml
type: sqlite
path: content.sqlite
collections:
books:
table: books
id: slug
attributes:
title: title
status: status
author:
columns:
first: author_first
last: author_last
content:
kind: markdown
column: body
checks:
- kind: object_required_field
field: titleCollections#
A collection is a directory of items plus the checks every item must pass.
Collections are declared inside their storage instance, under collections:.
| Key | Required | Default | Meaning |
|---|---|---|---|
path | no | the collection name | Directory, relative to the instance root. |
pattern | no | *.md | Filename glob selecting items in the directory. |
table | for sqlite | - | SQLite table backing the collection. |
id | for sqlite | - | SQLite column that provides item identity. |
attributes | no | all scalar columns except id and content.column | SQLite column captures exposed as item attributes. |
content | no | - | Optional SQLite content mapping, with kind: text or kind: markdown and column: <name>. |
schema | no | - | Schema name; shorthand for a leading object check. |
checks | no | - | List of checks (see below). |
listing | no | - | item list listing defaults for this collection (see listing). |
A collection must configure at least one check: set schema, or provide a
non-empty checks list, or both. Files in the directory that do not match
pattern are reported as errors.
SQLite collections do not support filesystem check types. Configure
structured-object checks against captured attributes. Text and markdown
body-text checks require a compatible content mapping.
attributes accepts shorthand single-column captures and structured
multi-column captures:
attributes:
title: title
author:
columns:
first: author_first
last: author_lastThe structured form above exposes author.first and author.last as fields
inside the author attribute object.
Per-collection files#
An instance whose collections: block grows unwieldy may split collections into
one file each under .katalyst/storage/<instance>/<collection>.yaml, named for
its filename stem. Inline and per-file collections coexist; a name declared both
inline and in a file is an error.
# .katalyst/storage/local/books.yaml
path: notes/books
schema: bookchecks#
Each entry has a kind and the keys that check type requires. Every check
type is documented one per page in the check types reference:
checks:
- kind: object
schema: book
- kind: object_field_type
field: year
type: integer
- kind: markdown_title_matches_h1
- kind: filesystem_name_matches_fieldText rules#
The text_* check types lint the item body as raw text, independent of
markdown structure, and also apply to plain-text items (a .txt file or a
markdown file with no frontmatter). Each is evaluated against a set of spans
chosen by target:
target | Spans |
|---|---|
body (default) | the entire body as one multiline string |
line | each body line |
first-line | the first non-blank body line |
matched-lines | each body line matching select: <regex> |
text_requiresandtext_forbidstake a Gopattern, matched unanchored (it must appear somewhere in a span: unlikefilesystem_name_regex, which anchors with^…$).text_requiresalso takesmatch: any(default, at least one span matches) ormatch: all(every span must match).text_denylisttakesvalues:, a list of literal substrings; regex metacharacters are inert.text_forbidsmay declare afix:: a replacement template ($1,${name}capture syntax) applied to the matched text bykatalyst fix. The fix re-checks its own work and fails rather than writing a file the rule would still reject.text_requiresandtext_denylistare report-only.
variants#
A collection runs its base schema/checks against every item. Variants
let it run extra checks on a subset, chosen by the item’s metadata. Each
entry in a collection’s variants: list has a when discriminator and its own
schema/checks:
pages:
path: docs/content
pattern: "**/*.md"
schema: page # base: every page needs a title
variants:
- when: "bookCollapseSection" # section landing pages have this flag
schema: section_index
- when: "!bookCollapseSection" # every other page is a content page
schema: content_page
checks:
- kind: object_required_field
field: weight
- kind: markdown_requires_h1
useExhaustiveVariants: false # defaultwhen is a list of item list --filter
predicates (field=value, field>=n, field=~regex, !field, …), evaluated
against the item’s frontmatter. All entries must hold (AND). Three shapes are
accepted, the first two desugaring to the third:
when: "kind=section" # one predicate
when: ["kind=section", "w>1"] # a list of predicates
when: { where: ["kind=section"] }Resolution. An item runs the base checks plus the checks of the first
variant (in list order) whose when it satisfies, at most one variant
applies. A variant adds to the base, so a check belongs in a variant exactly
when some page type must skip it: in the example, weight and the H1
requirement apply to content pages but not section indexes. A variant may
declare no checks at all (a deliberate exemption).
An item that matches no variant runs the base checks alone. Set
useExhaustiveVariants: true to instead make an unmatched item a check
failure (matches no variant), so every item is provably accounted for.
Discrimination is by metadata only; selecting items by path or filename is not
supported yet (a page type distinguishable only by location needs a frontmatter
marker). pattern still governs collection membership and which files are
reported as unmatched;
variants only route checks.
listing#
Two item list behaviors have configurable defaults. A listing: block sets
them project-wide in .katalyst/config.yaml, and a collection’s file can
override either key for that collection.
| Key | Values | Default | Meaning |
|---|---|---|---|
filterTypeMismatch | skip · error | skip | A --filter comparison against an incompatible type either skips the item or exits 2. |
sortMissing | last · lowest | last | Where items lacking the --sort key land: at the end (both directions), or below any present value. |
# .katalyst/config.yaml — project default
listing:
filterTypeMismatch: skip
sortMissing: last# under a storage instance's collections: — override for one collection
books:
path: notes/books
schema: book
listing:
filterTypeMismatch: errorResolution is highest-precedence first: the --on-type-mismatch /
--sort-missing flags, then the collection’s listing:, then the project
listing:, then the built-in default. An unset key falls through to the next
level.
Object-schema resolution precedence#
When an item is checked against an object schema, the schema is chosen highest-precedence first:
--schema <path>flag (applies to every selected item).- Inline
schema: <name>key in the item’s frontmatter. - The collection’s
objectcheck (fromschema:or an explicit entry), plus the matched variant’s schema: both apply, additively.
Markdown and filesystem checks always come from the collection (and the matched
variant), even when --schema is used.
See also#
- Check types reference, every check type.
- Storage layer, the storage instance / collection-definition model and its lineage.
- Collections, the config/collection model and rationale: schema resolution, variants, unmatched-as-error.