Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ row: "- [{summary}](../{filename})"
- [Built-in Markdown conventions, the rule presets each one applies, and how user config layers on top via deep-merge.](../docs/reference/conventions.md)
- [Glob pattern syntax across mdsmith config, directives, and CLI argument expansion, with the supported exclusion semantics for each surface.](../docs/reference/globs.md)
- [Look up exact CLI commands, config glob and schema syntax, the built-in conventions, and the section-schema grammar.](../docs/reference/index.md)
- [Each file under `.mdsmith/kinds/` declares one kind. The basename is the kind name; the file body carries the full `KindBody` — schema, rules, `path-pattern:`, `extends:`. Sits alongside inline `kinds.<name>:` in `.mdsmith.yml`.](../docs/reference/kind-files.md)
- [Named field-type shortcuts for inline schema frontmatter values — the registered names, the canonical CUE each one resolves to, and example usage.](../docs/reference/schema-types.md)
- [Section-schema reference for inline `kinds.<name>.schema:` blocks. Covers the `heading:` discriminator, the `regex:` matcher (a Go RE2 body with `\#(digits)` and `\#(fmvar(...))` helpers), the `repeat: {min, max}` cardinality field, and the matching algorithm. `proto.md` files are parsed into the same shape by the schema package, but MDS020's file-schema check still uses its legacy parser; see the proto.md section below for what is and is not migrated.](../docs/reference/section-schema.md)
- [mdsmith collects no telemetry, no usage analytics, no error reports, and no identifiers. The CLI and the LSP server make no outbound network calls at runtime.](../docs/reference/telemetry.md)
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ row: "- [{summary}]({filename})"
- [Built-in Markdown conventions, the rule presets each one applies, and how user config layers on top via deep-merge.](docs/reference/conventions.md)
- [Glob pattern syntax across mdsmith config, directives, and CLI argument expansion, with the supported exclusion semantics for each surface.](docs/reference/globs.md)
- [Look up exact CLI commands, config glob and schema syntax, the built-in conventions, and the section-schema grammar.](docs/reference/index.md)
- [Each file under `.mdsmith/kinds/` declares one kind. The basename is the kind name; the file body carries the full `KindBody` — schema, rules, `path-pattern:`, `extends:`. Sits alongside inline `kinds.<name>:` in `.mdsmith.yml`.](docs/reference/kind-files.md)
- [Named field-type shortcuts for inline schema frontmatter values — the registered names, the canonical CUE each one resolves to, and example usage.](docs/reference/schema-types.md)
- [Section-schema reference for inline `kinds.<name>.schema:` blocks. Covers the `heading:` discriminator, the `regex:` matcher (a Go RE2 body with `\#(digits)` and `\#(fmvar(...))` helpers), the `repeat: {min, max}` cardinality field, and the matching algorithm. `proto.md` files are parsed into the same shape by the schema package, but MDS020's file-schema check still uses its legacy parser; see the proto.md section below for what is and is not migrated.](docs/reference/section-schema.md)
- [mdsmith collects no telemetry, no usage analytics, no error reports, and no identifiers. The CLI and the LSP server make no outbound network calls at runtime.](docs/reference/telemetry.md)
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ row: "- [{summary}]({filename})"
- [Built-in Markdown conventions, the rule presets each one applies, and how user config layers on top via deep-merge.](docs/reference/conventions.md)
- [Glob pattern syntax across mdsmith config, directives, and CLI argument expansion, with the supported exclusion semantics for each surface.](docs/reference/globs.md)
- [Look up exact CLI commands, config glob and schema syntax, the built-in conventions, and the section-schema grammar.](docs/reference/index.md)
- [Each file under `.mdsmith/kinds/` declares one kind. The basename is the kind name; the file body carries the full `KindBody` — schema, rules, `path-pattern:`, `extends:`. Sits alongside inline `kinds.<name>:` in `.mdsmith.yml`.](docs/reference/kind-files.md)
- [Named field-type shortcuts for inline schema frontmatter values — the registered names, the canonical CUE each one resolves to, and example usage.](docs/reference/schema-types.md)
- [Section-schema reference for inline `kinds.<name>.schema:` blocks. Covers the `heading:` discriminator, the `regex:` matcher (a Go RE2 body with `\#(digits)` and `\#(fmvar(...))` helpers), the `repeat: {min, max}` cardinality field, and the matching algorithm. `proto.md` files are parsed into the same shape by the schema package, but MDS020's file-schema check still uses its legacy parser; see the proto.md section below for what is and is not migrated.](docs/reference/section-schema.md)
- [mdsmith collects no telemetry, no usage analytics, no error reports, and no identifiers. The CLI and the LSP server make no outbound network calls at runtime.](docs/reference/telemetry.md)
Expand Down
3 changes: 2 additions & 1 deletion PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,6 @@ footer: |
| 205 | 🔲 | | [Move extension.ts concerns to wiring.ts](plan/205_arch-fix-extension-ts-srp.md) |
| 206 | 🔲 | | [Document cue/ in architecture layering map](plan/206_arch-fix-cue-types-docs.md) |
| 207 | 🔲 | sonnet | [LSP fix preview via ChangeAnnotation](plan/207_lsp-fix-preview.md) |
| 208 | 🔲 | opus | [Kind-per-file config under `.mdsmith/kinds/`](plan/208_kind-files.md) |
| 208 | 🔳 | opus | [Kind-per-file config under `.mdsmith/kinds/`](plan/208_kind-files.md) |
| 209 | 🔲 | opus | [Convention-per-file config under `.mdsmith/conventions/`](plan/209_convention-files.md) |
<?/catalog?>
1 change: 1 addition & 0 deletions docs/development/architecture/cross-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ doc" column says.
| LSP wire protocol | `internal/lsp` | [CLI reference: `lsp`](../../reference/cli/lsp.md) | VS Code extension, other editors |
| CLI flags + exit codes | `cmd/mdsmith` | [CLI reference](../../reference/cli.md) | shell scripts, CI, git hooks |
| `.mdsmith.yml` schema | `internal/config` | [Conventions](../../reference/conventions.md) | every project using mdsmith |
| `.mdsmith/kinds/` directory | `internal/config` | [Kind files](../../reference/kind-files.md) | every project using mdsmith |
| Generated section markers | `internal/archetype/gensection` | [Generated sections](../../background/concepts/generated-section.md) | every project's Markdown files |
| Claude plugin manifest (published) | `editors/claude-code/.claude-plugin/plugin.json` | [Install: Claude plugin](../../guides/install.md) | end users via Claude Code marketplace |
| Claude plugin manifest (contributors) | `editors/claude-code-dev/.claude-plugin/plugin.json` | [editors/claude-code-dev/README.md](../../../editors/claude-code-dev/README.md) | mdsmith contributors |
Expand Down
37 changes: 37 additions & 0 deletions docs/guides/file-kinds.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,43 @@ effective kinds:
- plan (from kind-assignment[2]: glob plan/*.md AND fields-present id)
```

## Split a kind into its own file

When a project's `kinds:` block has grown large
enough that one kind's edits dirty the same
`.mdsmith.yml` as every other config change, lift
the kind into a standalone file under
`.mdsmith/kinds/<name>.yaml`. The basename is the
kind's name; the body is identical to the inline
`kinds.<name>:` body.

```yaml
# .mdsmith/kinds/audit-log.yaml
schema:
frontmatter:
title: 'string & != ""'
"summary?": 'string'
closed: false
rules:
max-file-length:
max: 600
```

The same name in both a file and inline is a
config error naming both sources, so split a kind
in one move rather than leaving both copies
behind. Names stay stable, so `kind-assignment:`
entries that referenced the inline kind keep
working with no edit.

`mdsmith kinds resolve <file>` and `mdsmith kinds
show <name>` print the defining-source path next
to each kind so a mixed configuration stays
auditable. See the
[kind files reference](../reference/kind-files.md)
for the full layout, basename rule, and JSON
shape.

## Schema inheritance with `extends`

A kind can build on another kind's schema via the `extends:` key.
Expand Down
1 change: 1 addition & 0 deletions docs/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ row: "- [{summary}]({filename})"
- [Print the mdsmith build version and exit.](cli/version.md)
- [Built-in Markdown conventions, the rule presets each one applies, and how user config layers on top via deep-merge.](conventions.md)
- [Glob pattern syntax across mdsmith config, directives, and CLI argument expansion, with the supported exclusion semantics for each surface.](globs.md)
- [Each file under `.mdsmith/kinds/` declares one kind. The basename is the kind name; the file body carries the full `KindBody` — schema, rules, `path-pattern:`, `extends:`. Sits alongside inline `kinds.<name>:` in `.mdsmith.yml`.](kind-files.md)
- [Named field-type shortcuts for inline schema frontmatter values — the registered names, the canonical CUE each one resolves to, and example usage.](schema-types.md)
- [Section-schema reference for inline `kinds.<name>.schema:` blocks. Covers the `heading:` discriminator, the `regex:` matcher (a Go RE2 body with `\#(digits)` and `\#(fmvar(...))` helpers), the `repeat: {min, max}` cardinality field, and the matching algorithm. `proto.md` files are parsed into the same shape by the schema package, but MDS020's file-schema check still uses its legacy parser; see the proto.md section below for what is and is not migrated.](section-schema.md)
- [mdsmith collects no telemetry, no usage analytics, no error reports, and no identifiers. The CLI and the LSP server make no outbound network calls at runtime.](telemetry.md)
Expand Down
139 changes: 139 additions & 0 deletions docs/reference/kind-files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
title: Kind files under `.mdsmith/kinds/`
weight: 25
summary: >-
Each file under `.mdsmith/kinds/` declares one
kind. The basename is the kind name; the file body
carries the full `KindBody` — schema, rules,
`path-pattern:`, `extends:`. Sits alongside inline
`kinds.<name>:` in `.mdsmith.yml`.
---
# Kind files under `.mdsmith/kinds/`

A **kind file** is a YAML file under
`.mdsmith/kinds/` whose basename is the kind's name
and whose body is the full kind definition. One file
per kind, no nesting. The directory sits next to
`.mdsmith.yml` at the workspace root.

```text
.mdsmith.yml # unchanged
.mdsmith/
kinds/
audit-log.yaml
secret-rotation.yaml
architecture-doc.yaml
```

Use kind files when the `kinds:` block has grown
large. Each rule edit dirties the same
`.mdsmith.yml` as every other config change.
Splitting kinds into one file each isolates the
history. The read path shortens too: open
`audit-log.yaml` to see the whole `audit-log`
kind.

## File shape

The file body matches the inline
`kinds.<name>:` body. It accepts every
[`KindBody`](../guides/file-kinds.md) key:
`extends:`, `path-pattern:`, `categories:`,
`schema:`, `rules:`. A key outside that set is
a config error.

```yaml
# .mdsmith/kinds/audit-log.yaml
schema:
frontmatter:
title: 'string & != ""'
"summary?": 'string'
audit-from: '=~"^[0-9a-f]{7,40}$"'
filename: "architecture-audit.md"
closed: false
sections:
- heading: null
- heading:
regex: '.+'
repeat: { min: 0 }
rules:
max-file-length:
max: 600
```

## Basename rule

The kind's name is the basename minus extension.
The basename must match `[a-z][a-z0-9-]*` — lower
case, starting with a letter, with optional
hyphen-separated segments. The rule applies only to
filenames (OS case folding, path safety); inline
`kinds.<name>:` keys stay unvalidated.

Both `*.yaml` and `*.yml` are scanned. Two kind
files with the same basename across the two
extensions is a config error naming both files.

Subdirectories under `.mdsmith/kinds/` are
rejected. One kind per file, flat layout.

## Composition with `.mdsmith.yml`

`kinds.<name>:` blocks inside `.mdsmith.yml`
remain a first-class source. A project can mix
inline and file-defined kinds freely.

The same kind name declared in **both** a file and
inline is a config error naming both sources. The
two sources do **not** merge — a merged kind would
defeat the "read one file to know one kind"
property kind files ship.

`kind-assignment:`, `overrides:`, and `ignore:` are
glob-keyed and stay in `.mdsmith.yml`. A
kind-assignment entry references a kind by name —
inline or file kind — with no extra wiring.

## Schema sources

A kind file accepts the same three schema sources
as an inline kind, and they remain mutually
exclusive (acceptance criterion #6 of plan 208):

- inline `schema:` block
- `rules.required-structure.schema:` path to a
`proto.md`
- legacy `rules.required-structure.inline-schema:`
map

Setting two on the same kind errors at config load
with both source names.

A schema shared across kinds is shared via
`extends:` — the inheritance chain works
seamlessly across sources. A file kind may extend
an inline kind and the reverse. The cycle detector
runs on the merged kinds map.

## Audit

`mdsmith kinds resolve <file>` prints the
defining-source path next to each kind it
reports. A mixed resolution shows the path each
kind came from. You can jump straight to the
right file:

```text
file: docs/audit.md
effective kinds:
- audit-log (from kind-assignment[3]: glob docs/**/*.md) defined-in .mdsmith/kinds/audit-log.yaml
```

`mdsmith kinds show <name>` adds a `defined-in:`
line to the body output, so the same info is
available without going through a target file.

The JSON shape (`--json`) carries a
`source-path:` key on each kind body and on every
resolved-kind entry so editor integrations can
key off a stable field.
8 changes: 8 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ type KindBody struct {
// sections. A cycle in the extends chain (single- or multi-hop)
// is reported by ValidateKinds. See plan 135.
Extends string `yaml:"extends,omitempty"`

// SourcePath is the workspace-absolute path of the file that
// defined this kind — either `.mdsmith.yml` for inline kinds
// or `.mdsmith/kinds/<name>.{yaml,yml}` for file-defined kinds
// (plan 208). Not serialized to YAML; populated by Load.
// Provenance surfaces it as `kinds.<name> (<path>)`; CLI
// resolve/show prints it next to each kind.
Comment on lines +159 to +164
SourcePath string `yaml:"-"`
}

// KindAssignmentEntry assigns one or more kinds to files matching the
Expand Down
Loading
Loading