MDS021: include
Include section content must match the referenced file.
# Marker Syntax
<?include
file: path/to/file.md
strip-frontmatter: "true"
wrap: markdown
heading-level: "absolute"
?>
...included content...
<?/include?>Do not use YAML folded scalars (>, >-) in the YAML
body. Markdown parsers interpret > at the start of a
line as a blockquote marker, which breaks the processing
instruction content. Use literal block scalars (|,
|-, |+) or quoted strings instead. See the
generated-section concept
for details.
# Parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
file | yes | – | Relative path to include |
extract | no | – | Dotted path through the extract projection of a kind-typed file:. Splices one leaf value |
strip-frontmatter | no | "true" | Remove YAML frontmatter (incompatible with extract:) |
wrap | no | – | Wrap in code fence (value = language) |
heading-level | no | – | "absolute" or 1-6: nest under parent, or pin shallowest heading to that level |
heading-offset | no | – | Signed integer -6 to 6: shift every heading by N (incompatible with heading-level, extract:) |
# Link Adjustment
Relative link and image targets in included content are
automatically rewritten so they resolve from the
including file’s directory. Absolute URLs, anchor-only
links (#foo), and protocol links (http://,
https://, mailto:) are not modified.
For example, DEVELOPMENT.md contains
[rules](internal/rules/). When included from
docs/guide.md, the link becomes
[rules](../internal/rules/).
# Heading-Level Adjustment
heading-level takes "absolute" or an integer 1-6.
"absolute" is parent-relative: included headings shift
so the shallowest one becomes a child of the enclosing
section. The marker sits under ## Project (level 2). The
included file has ## Build (2) and ### Sub (3). The
shift is 2 - 2 + 1 = 1: ### Build (3) and #### Sub
(4). At the document root no shift is applied.
An integer pins the shallowest included heading to that
level, whatever the source starts at, so it also works at
the document root. heading-level: "2" renders a source
# Title / ## Sub as ## Title / ### Sub. A deeper
source is promoted; levels are capped at 1-6.
heading-level cannot combine with heading-offset or
extract:.
# Heading-Offset Adjustment
When heading-offset: "N" is set, every heading in the
included content shifts by N levels. N is a signed
integer from -6 to 6. A positive value demotes a heading
(adds a #); a negative value promotes it. Levels are
capped at the 1-6 range.
Unlike heading-level: "absolute", the shift is fixed. It
does not read a preceding heading, so it also applies at
the document root. heading-offset cannot combine with
heading-level or extract:.
# Cycle Detection
Include chains are tracked during check and fix. A diagnostic is emitted when:
- A file includes itself (direct cycle).
- A file includes B which includes A (indirect cycle, detected by scanning nested includes).
- The include chain exceeds 10 levels deep.
The cycle message shows the full chain:
cyclic include: a.md -> b.md -> a.md# Config
rules:
include: trueDisable:
rules:
include: false# Examples
# Basic Include
<?include
file: data.md
?>
Hello world
<?/include?># With Code Fence Wrapping
<?include
file: config.yml
wrap: yaml
?>
```yaml
key: value
```
<?/include?># With Frontmatter Kept
<?include
file: data.md
strip-frontmatter: "false"
?>
---
title: My Doc
---
Content here.
<?/include?># With Heading-Level Shift
Given DEVELOPMENT.md contains ## Build and
### Sub, including under ## Project shifts
headings one level down:
## Project
<?include
file: DEVELOPMENT.md
heading-level: "absolute"
?>
### Build
Steps here.
#### Sub
Details.
<?/include?># With Heading Offset
Demote every heading in the included file by one level, even when no heading precedes the marker:
<?include
file: features.md
heading-offset: "1"
?>
## Was An H1 In The Source
<?/include?># With Link Rewriting
Given DEVELOPMENT.md in the repo root contains
[rules](internal/rules/), including it from
docs/guide.md rewrites the link:
<?include
file: DEVELOPMENT.md
?>
See [rules](../internal/rules/) for details.
<?/include?># Extract a Typed Value
extract: walks the JSON projection
mdsmith extract would produce for a kind-typed
file: target. The value is a dotted path. The
directive splices the leaf:
<?include
file: docs/brand/messaging.md
extract: tagline.text
?>
Markdown, fast.
<?/include?>A wrapper object with one content key (text,
code, items, or rows) resolves to the inner
value. So extract: tagline is the same as
extract: tagline.text. Multi-key wrappers are
ambiguous: the directive surfaces a lint error
listing the keys. Frontmatter scalars sit under the
frontmatter key: extract: frontmatter.title.
extract: is incompatible with strip-frontmatter:,
heading-level:, and heading-offset:. The projection
returns one scalar, so those parameters do not apply.
# Bad — Outdated Content
<?include
file: data.md
?>
Outdated content
<?/include?># Diagnostics
| Condition | Message |
|---|---|
| content mismatch | generated section is out of date |
| missing file | include file “x.md” not found |
| no file param | include directive missing required “file” parameter |
| absolute path | include directive has absolute file path |
| escapes root | include file path escapes project root |
| no root for dotdot | include file path contains “..” but project root is not configured |
| invalid heading-level | include directive “heading-level” must be “absolute” or an integer between 1 and 6 |
| invalid heading-offset | include directive “heading-offset” must be an integer between -6 and 6 |
| offset + level | include directive “heading-offset” cannot be combined with “heading-level” |
| empty extract | include directive “extract” value is empty |
| extract + sfm | include directive “extract” cannot be combined with “strip-frontmatter” |
| extract + hl | include directive “extract” cannot be combined with “heading-level” |
| extract + offset | include directive “extract” cannot be combined with “heading-offset” |
| extract miss | extract: missing key “x” in extract projection |
| extract no kind | extract: “x.md” has no resolved kind; cannot project a typed value |
| extract not conformant | extract: target file does not conform to its schema: … |
| cyclic include | cyclic include: a.md -> b.md -> a.md |
| depth exceeded | include depth exceeds maximum (10) |
# Pattern
The bad pattern is a section duplicated across
two files. The good pattern is one canonical
source plus <?include?> references. The
canonical source files live in
pattern/bad/
and
pattern/good/
; the snippets
below mirror those files for quick reference.
The markdown-audit skill reads the folders
directly.
# Without the directive
README.md:
# Project
## Build
Run `make build` to compile the project. The
binary lands in `dist/`.INSTALL.md:
# Install
## Build
Run `make build` to compile the project. The
binary lands in `dist/`.# With the directive
snippets/build.md:
Run `make build` to compile the project. The
binary lands in `dist/`.README.md:
# Project
## Build
<?include
file: snippets/build.md
?>
Run `make build` to compile the project. The
binary lands in `dist/`.
<?/include?>INSTALL.md follows the same shape.
# Meta-Information
- ID: MDS021
- Name:
include - Status: ready
- Default: enabled
- Fixable: yes
- Implementation: source
- Category: directive
- Concept: generated-section
- Guide: directive guide