Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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