How mdt Works
mdt finds named blocks in your project, matches each consumer to its provider, and rewrites the consumer's content when it differs.
The pipeline
1. Find the project root and load its configuration (optional)
└── Read [data] sources (package.json, Cargo.toml, scripts, ...)
2. Scan the project
├── *.t.md files → provider blocks
├── markdown files → consumer and inline blocks
└── source files (.rs, ...) → consumer and inline blocks in comments
3. Validate: unclosed, unmatched, nested, and invalid tags, unknown
transformers, duplicate providers, orphan consumers, unused providers
4. For each consumer:
├── Find the provider with the same name
├── Render template variables ({{ pkg.version }}) when [data] is set
├── Apply the consumer's transformers (|trim|linePrefix:"/// ":true)
└── Replace the content between the tags if it differsmdt update writes the result. mdt check runs the same steps without writing and fails if any file would change.
Project root
With --path <dir>, that directory is the project root. Without it, every command except mdt init walks up from the current directory, inside the enclosing git repository, to the nearest directory containing mdt.toml, .mdt.toml, or .config/mdt.toml, so you can run mdt from any subdirectory. Outside a git repository, or with no config found, the current directory is the root.
Tag anatomy
Every tag is an HTML comment, so readers of the rendered markdown never see it.
<!-- {=name|trim|codeBlock:"sh"} -->
││ └─────────────────── optional transformers, separated by |
│└──────────────────────── block name
└───────────────────────── sigil: @ provider, = consumer, ~ inline, / closeThe sigil must directly follow {. See the Template Syntax Reference for the full rules.
File roles
| Files | Role |
|---|---|
*.t.md | Template files: the only place providers are read from |
*.md, *.mdx, *.markdown | Scanned for consumer and inline blocks |
Source files (*.rs, *.ts, *.py, ...) | Scanned for consumer and inline blocks inside comments |
A provider tag outside a *.t.md file is ignored with a mdt::provider_outside_template warning. See Source File Support for the full extension list.
What gets skipped
- Files ignored by git:
.gitignorefiles in the project and its parent directories up to the repository root, plus.git/info/exclude. Outside a git repository, only the.gitignorefiles inside the project apply.disable_gitignore = trueturns this off. - Hidden directories other than
.templates/, plusnode_modules/andtarget/, even withdisable_gitignore. List other directories under[templates] pathsto read their providers. - Subdirectories with their own
mdt.toml,.mdt.toml, or.config/mdt.toml. Each is a separate project. - Files with extensions mdt does not scan, unless added with
[include]. - Files matching
[exclude] patterns, and blocks named in[exclude] blocks(their consumers are never filled or checked). - Tags inside fenced code blocks and inline code spans in markdown, and inside fenced code blocks in source comments when
[exclude] markdown_codeblocksis set.
Matching rules
Provider names are unique across the project. Two providers with the same name are a
mdt::duplicate_providererror.A consumer whose name matches no provider is an orphan.
mdt checkfails and lists it with a suggestion;mdt updatewarns and leaves it unchanged:consumer `instalGuide` at readme.md:3:1 has no provider (did you mean `installGuide`?)A provider with no consumers is a
mdt::unused_providerwarning.Any number of consumers can use one provider, and a file can contain any number of consumers. Each consumer applies its own transformers.