| id | 92 |
|---|---|
| title | File kinds — config schema, assignment, merge |
| status | ✅ |
| model | sonnet |
| summary | Add user-declared file `kinds` that share the existing rule-config syntax. A file's effective kind list is built from front-matter `kinds:` plus `kind-assignment:` globs; merging follows override semantics with list-driven order. |
Introduce a kind: a named bundle of rule settings
that can be applied to a set of files. Kinds reuse the
existing override-merge plumbing — no new bespoke
schema. The term aligns with Hugo's type (front-matter
content category), not Hugo's archetype.
This plan delivers only the kind machinery. Placeholder grammar (plan 93), lint-once for embeds (plan 94), troubleshooting CLI (plan 95), and adoption in this repo (plan 96) build on it.
Today files are linted under a single global rule set
plus glob overrides:. Files that share a purpose
(schema, template, fragment, prompt, …) tend to need
the same per-rule tuning. Without a named grouping,
that tuning has to be repeated across overrides: or
managed by ad-hoc ignore: entries.
A kind names the grouping. Files of the same kind share the kind's rule body. Kind names belong to the project — mdsmith ships no built-in kinds.
A kind is a named block under kinds:. Its body has
the same shape as an override entry (rules:,
front-matter:, etc.) — minus files:, since files
are bound to kinds separately. The kind name (plan
below) is whatever the project picks. A kind that sets
rules.required-structure.schema: in its body attaches
that CUE schema to every file of that kind:
kinds:
plan: # project-chosen name
rules:
required-structure:
schema: plan/proto.md # CUE schema for this
# kind
paragraph-readability: false
proto:
rules:
paragraph-readability: false
front-matter: falseKinds merge with the same rules as overrides. If
multiple kinds configure the same rule, the kind
appearing later in the file's resolved effective
kind list replaces that rule's entire config block
— nested settings do not deep-merge across kinds, the
same way overrides: already work today.
A file's effective kind list is built from two sources, concatenated in this order:
- front-matter
kinds:field (a YAML list; a single-kind file still uses a one-item list); - matching entries in
kind-assignment:(config order; each entry's kinds in the order listed).
Duplicate names are dropped after their first occurrence. Referencing an undeclared kind is a config error.
- Kind merge follows override merge. Kinds apply
in effective-list order: front-matter first, then
kind-assignment:matches in config order; duplicates dropped after first occurrence. The file's own glob overrides apply last. Order is list-driven, so it is stable across runs. - No kind names in rule code. Rules read their own settings; they never branch on a kind name. New kinds cannot regress existing behavior.
Two kinds can disagree about a setting. Layers
deep-merge rule by rule. The layer chain runs
default → kinds in effective-list order → matching
overrides:. Each leaf is replaced by the later layer.
Sibling keys set earlier are preserved.
List-typed settings replace by default. Rules opt into
append per setting via rule.ListMerger. See plan 97
for the implementation and docs/reference/cli.md for
the user-facing description.
Kind names below (
plan,proto,tip,worksheet) are fictional — projects pick their own.
---
kinds: [plan]
id: 92
status: 🔲
---
# File kindsA file with multiple kinds uses a multi-element list:
kinds: [tip, worksheet]. Merge order matches list
order.
kind-assignment: is a list of entries with the
same YAML shape as overrides: entries. The files:
list uses the same glob matcher as overrides: and
ignore: — no !-negation syntax. To exclude a
narrower path, write a glob that doesn't match it.
For example, plan/[0-9]*_*.md excludes
plan/proto.md naturally. Alternatively, assign the
more specific kind in a later entry; the file carries
both kinds and the later one wins on conflict.
kind-assignment:
- files: ["plan/[0-9]*_*.md"] # excludes proto.md
kinds: [plan]
- files: ["**/proto.md"]
kinds: [proto]
- files: [".github/PULL_REQUEST_TEMPLATE.md"]
kinds: [worksheet]
- files: ["docs/_partials/**"]
kinds: [tip]- Add the
kinds:andkind-assignment:config keys to the config schema; reuse the existing override merge to apply a kind's body. - Add the front-matter
kinds:list field; resolve each file's effective kind list per Kind assignment above (front-matter first, thenkind-assignment:matches in config order; dedup by first occurrence). - Wire kind-resolved rule config into the engine so each file is linted with its merged settings.
- Emit a clear config error when kind assignment or front matter references an undeclared kind name.
- Grep test asserts the linter's core contains no
if kind == "..."branches and no hardcoded kind names. -
mdsmith help kindsprints a short concept page covering declaration, assignment, and merge order (links to the user guide once plan 96 lands). -
mdsmith initproduces a config that acceptskinds:andkind-assignment:when added manually. Both fields areomitemptyso they do not appear in the init output by default; adding them to the generated.mdsmith.ymlmust not causecheck .to fail.
- A kind declaration parses with the same syntax
as an override entry (minus
files:); override merge is reused for kind merge (verified by test). - Two project-declared kinds compose correctly with each other and with file-glob overrides (covered by test).
- A file declaring multiple kinds via
kinds: [a, b]in front matter merges them in list order (covered by test). - Conflicting settings between kinds resolve by
block replacement — the later kind in the
effective list replaces the earlier kind's
entire rule config (matching today's
overrides:behavior, covered by test). - Files of a kind that sets
rules.required-structure.schema:are validated against that schema (covered by test). - Referencing an undeclared kind name produces a clear config error.
- No kind name is referenced by mdsmith's core (enforced by grep test).
-
mdsmith help kindsprints a concept page. -
mdsmith initfollowed bymdsmith check .on a fresh directory exits 0; the generated config acceptskinds:andkind-assignment:keys without error. - All tests pass:
go test ./... -
go tool golangci-lint runreports no issues