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. Discovery resolves symlinks on both the root and the input path, because on macOS $TMPDIR lives behind /var to /private/var and relative-path resolution would otherwise produce garbage.

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
  bases/               # one file per base
    local.yaml         # a base + the collections it declares
    local/             # optional: one file per collection (escape hatch)
      books.yaml

By default, schemas and bases are discovered by convention: every file under schemas/ is a schema whose name is its filename stem (book.jsonbook), and every file under bases/ is a base named for its filename stem (local.yamllocal). config.yaml is optional; it carries listing: defaults and can switch a kind to explicit discovery, listing definitions inline instead of as files.

config.yaml is YAML; schema and base files default to YAML/JSON, and the accepted format is set per kind there.

Legacy projects that still use storage: in config.yaml or .katalyst/storage/ continue to load. Do not mix legacy and new forms in the same project; move legacy base files to .katalyst/bases/ when you edit them.

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).

Bases#

A base is one configured backend store, plus the collections it maps onto the domain model. Each file under .katalyst/bases/ is one base, named for its filename stem. There is no implicit base; katalyst init writes a default local one.

KeyRequiredDefaultMeaning
typenofilesystemBackend kind: filesystem or sqlite.
rootno.Base root directory, relative to the repo root. Collection paths resolve against it.
pathfor sqlite-SQLite database path, relative to the repo root. Alias for root on SQLite bases.
collectionsno-Map of collection name → definition (see below).
# .katalyst/bases/local.yaml
type: filesystem
root: .
collections:
  books:
    path: notes/books
    schema: book
    checks:
      - kind: markdown_title_matches_h1

Collection names are unique across the whole project (selectors are <collection>/<item>, with no base qualifier).

SQLite instances use one table per collection. Each row is one item:

# .katalyst/bases/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: title

Collections#

A collection is a directory of items plus the checks every item must pass. Collections are declared inside their base, under collections:.

KeyRequiredDefaultMeaning
pathnothe collection nameDirectory, relative to the base root.
patternno*.mdFilename glob selecting items in the directory.
tablefor sqlite-SQLite table backing the collection.
idfor sqlite-SQLite column that provides item identity.
attributesnoall scalar columns except id and content.columnSQLite column captures exposed as item attributes.
contentno-Optional SQLite content mapping, with kind: text or kind: markdown and column: <name>.
schemano-Schema name; shorthand for a leading object check.
checksno-List of checks (see below).
listingno-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_last

The structured form above exposes author.first and author.last as fields inside the author attribute object.

Per-collection files#

A base whose collections: block grows unwieldy may split collections into one file each under .katalyst/bases/<base>/<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/bases/local/books.yaml
path: notes/books
schema: book

checks#

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_field

Text 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:

targetSpans
body (default)the entire body as one multiline string
lineeach body line
first-linethe first non-blank body line
matched-lineseach body line matching select: <regex>
  • text_requires and text_forbids take a Go pattern, matched unanchored (it must appear somewhere in a span: unlike filesystem_name_regex, which anchors with ^…$). text_requires also takes match: any (default, at least one span matches) or match: all (every span must match).
  • text_denylist takes values:, a list of literal substrings; regex metacharacters are inert.
  • text_forbids may declare a fix:: a replacement template ($1, ${name} capture syntax) applied to the matched text by katalyst fix. The fix re-checks its own work and fails rather than writing a file the rule would still reject. text_requires and text_denylist are 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   # default

when 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.

KeyValuesDefaultMeaning
filterTypeMismatchskip · errorskipA --filter comparison against an incompatible type either skips the item or exits 2.
sortMissinglast · lowestlastWhere 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 base's collections: override for one collection
books:
  path: notes/books
  schema: book
  listing:
    filterTypeMismatch: error

Resolution 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:

  1. --schema <path> flag (applies to every selected item).
  2. Inline schema: <name> key in the item’s frontmatter.
  3. The collection’s object check (from schema: 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.
  • Bases, the base / collection-mapping model and its lineage.
  • Collections, the config/collection model and rationale: schema resolution, variants, unmatched-as-error.