QMDC: Guide for AI Agents [[qmdc_guide]]
Practical guide to the QMD.md format for AI agents
- version: 1.0.2
⚠️ Important: In QMD.md, field and object order is strictly preserved as written (insertion order). All parsers guarantee this.
Metamodel [[metamodel: Metamodel]]MetamodelMetamodel Metamodel 89%Describe Metamodel McpTool 88%Discovery Category 80%
Anchor Kinds [[anchor_kinds: text]]textMarkdown to Graph Explanation 79%Object SyntaxConcept 78%Quickstart Tutorial 78%
| kind | definition |
|---|---|
| Syntax | How QMD.md text maps to structure — objects, fields, arrays, text blocks, data types |
| Reference | The linking mechanism — [[#id]] and its variants |
| Workspace | Multi-file project structure — namespaces, cross-file resolution |
| Validation | Error detection, error types, pre-commit checks |
| CLI | The qmdc commands — parse, rebuild, lint, query, validate |
Link Roles [[link_roles: text]]textMetamodel Metamodel 83%
| role | meaning |
|---|---|
about |
This section describes or explains X |
depends |
This anchor relies on another anchor at runtime |
What Good QMD.md Looks Like [[good_qmdc: NarrativeDoc]]NarrativeDocGuide McpResource 85%Content Generator ContentGenerator 83%Write Your First QMD.md File Tutorial 82%
Principles [[principles: text]]textObject SyntaxConcept 74%Human Text, Machine Structure Explanation 73%Content Generator ContentGenerator 70%
A good QMD.md document starts with real things in your domain — not document structure. Your headings should be Parsers, Contracts, Modules — not Sections, Examples, Pages. Those real things are your anchors. Everything else — explanations, examples, walkthroughs — is narrative that links back to anchors via about.
Keep anchor kinds coarse. 5–15 is the sweet spot. If you have 30 kinds, you're modeling the document, not the domain. If you have 3, you probably merged things that deserve separate identity.
Use depends between anchors to show the dependency chain. Use about on narrative sections to say what they explain. If a section isn't about any anchor, ask yourself why it exists.
IDs should be guessable — rust_parser, not parser_001. Types should be PascalCase domain nouns — Module, not ModuleSection. Text fields for anything longer than a line.
Identity comes from Kind + position in the hierarchy, not from a bespoke globally-unique name. Top-level anchors get descriptive ids; nested children get simple, repeatable local ids (description, constraints) scoped by their parent's dot-path; homogeneous collections become [Kind] arrays instead of hand-named items.
When you're unsure which things are anchors, what the right granularity is, or whether two concepts should be merged or split — stop and ask. Propose your anchor list and dependency chain to the human, get explicit approval before writing the document. A wrong metamodel is worse than no metamodel: it creates structure that actively misleads. Iterate on the anchors first, write content second.
Syntax [[syntax: Syntax]]SyntaxInvalid Reference Syntax DiagnosticRule 67%Components NarrativeDoc 67%Guide McpResource 67%
Overview [[syntax_overview: text]]textHuman Text, Machine Structure Explanation 84%QMD.md Format Specification Namespace 83%Why QMDC Explanation 82%
QMD.md is a format for writing structured data in Markdown. You write regular Markdown with headings and lists, and the parser turns it into a graph of objects with references.
Core idea:
- Headings (
##,###) = objects - Lists (
- key: value) = object fields [[id]]in a heading = object identifier[[#id]]in a value = reference to another object
Creating Objects [[creating_objects: text]]textField Key CompletionContext 74%Heading SyntaxConcept 73%Write Your First QMD.md File Tutorial 72%
## User [[alice]]
- name: Alice
- age: 30
- active: true
- score: 95.5
Result: object with __id: "alice", fields name, age, active, score.
Heading variants:
## Title [[id]]— explicit ID## Title [[id: Kind]]— with type (for validation)## Title [[:Kind]]— Kind only, ID auto-generated## Title— auto-generate ID from Title (lowercase, spaces →_)## [[id]] Title— ID before Title (also valid)
Data Types [[data_types: text]]textData Type SyntaxConcept 73%Array SyntaxConcept 67%Field Key CompletionContext 67%
## Config [[config]]
- text: Hello World # string
- number: 42 # number
- float: 3.14 # number
- bool: true # boolean (lowercase only)
- empty: null # null
- array: [a, b, c] # array (YAML notation)
Important: true/false/null must be lowercase only. True, FALSE, yes, no are strings!
Text Fields (Multiline Content) [[text_fields: text]]textField SyntaxConcept 74%Object SyntaxConcept 73%Comment SyntaxConcept 68%
## Article [[article]]
### Content [[content]]
This is **multi-line** content.
You can use Markdown formatting here:
- Lists work
- Bold, italic, etc.
Result: field content with multiline text (Markdown preserved).
References in text fields: References [[#id]] inside text fields are validated for existence (broken links detected), but remain part of the text — they are not resolved into objects.
When to Use Heading-Syntax Text Fields [[heading_syntax_rule: text]]textField SyntaxConcept 79%Dangling Field ValidationError 73%Object SyntaxConcept 72%
Rule of thumb: any field that could plausibly be more than a short scalar should use ### FieldName [[field: text]] heading syntax instead of - key: value.
Use heading-syntax (### Field [[field: text]]) for:
- descriptions, responsibilities, rationale
- assumptions, deltas, deferred items
- public surfaces, summaries, explanations
- anything that might span multiple lines or contain markdown formatting
Keep as inline fields (- key: value) for:
- status, language, repo, version
- references (
- ref: [[#something]]) - short scalars (names, IDs, booleans, numbers)
Example:
## Feature [[feature1: Feature]]
- status: planned
- priority: high
### Description [[description: text]]
This feature adds support for semantic search across
the entire workspace. It combines keyword matching with
dense vector embeddings for hybrid retrieval.
### Rationale [[rationale: text]]
Users need to find relevant objects without knowing
exact file paths or object IDs.
YAML Multiline Strings [[yaml_multiline: text]]textField SyntaxConcept 95%YAML Block SyntaxConcept 71%Array SyntaxConcept 69%
## Config [[config]]
- description: |
This is a multiline string
using YAML pipe syntax.
References like [[#something]] are NOT parsed here.
Important: References [[#id]] inside YAML pipe | blocks are NOT parsed by the QMDC parser — content remains plain text. This means they won't create edges in the graph or trigger broken_link errors in the target document.
Nested Objects [[nested_objects: text]]textObject SyntaxConcept 84%Broken Parent ValidationError 80%Reference SyntaxConcept 79%
## User [[user]]
- name: Alice
### Address [[address]]
- street: Main St
- city: NYC
Result: two objects:
userwith fieldaddress: "[[#user.address]]"(reference to the child's full ID)- an object with
__id: "user.address",__local_id: "address", fieldsstreet,city, and__parent: "[[#user]]"
Key distinction: QMD.md has no nested JSON objects! Everything is flat, connections via references.
Hierarchical ID formation: a child's effective __id is composed from its parent:
- Single nested child →
parent.child(e.g.user.address) - Object-array element →
parent.field.child(e.g.team.members.alice) - Exception: children of
__Workspaceand__Namespaceobjects keep flat IDs
Reuse simple local ids under different parents. Because a child's identity is parent-scoped, the same local id may — and should — repeat across parents. Prefer [[description: text]] and [[constraints]] under every object over hand-made globally-unique names:
## Run [[schema_run: KindSchema]]
### Description [[description: text]] ← id resolves as schema_run.description
## Task [[schema_task: KindSchema]]
### Description [[description: text]] ← id resolves as schema_task.description
Do NOT write run_desc/task_desc or prefix children with the parent name (schema_run_constraints) — the dot-path already carries the parent. Parent-scoped children never trigger duplicate_id (which fires only on identical FULL hierarchical __ids — see Error Types).
Dot-ID Declarations [[dot_id_declarations: text]]textInvalid ID Character ValidationError 85%Object SyntaxConcept 83%Broken Parent ValidationError 79%
A TOP-LEVEL heading may declare a hierarchical ID directly — attaching the object to a parent defined elsewhere (even in another file or namespace):
# Operations [[my_service.operations]]
- count: 12
The workspace loader finds the parent by the ID prefix up to the last dot (my_service), injects __parent: "[[#my_service]]", and the object behaves exactly like a nested child. If no object with the parent ID exists anywhere in the workspace, validation reports broken_parent.
Rules:
- Dot-IDs are legal only on top-level headings. A dot in a NESTED child's explicit ID is a parse error (
invalid_id_character) — nested children get their dot-path composed automatically. - Only the prefix up to the LAST dot must exist as an object (
a.b.crequiresa.b, nota). - Works cross-file and cross-namespace.
Arrays [[arrays: text]]textArray SyntaxConcept 81%Write Your First QMD.md File Tutorial 77%Nested Subitems ValidationError 70%
Compact form (YAML):
- tags: [react, nodejs, typescript]
- ports: [8080, 8081, 8082]
Multiline YAML form:
- long_list: [
first_very_long_item,
second_very_long_item,
third_very_long_item
]
Expanded form (lists):
### Members [[members]]
- Alice
- Bob
- Charlie
Comma-separated references (no outer brackets):
- deps: [[#auth]], [[#db]], [[#cache]]
Syntax choice is preserved in __syntax for lossless round-trip.
Object Arrays via Subheadings [[object_arrays_subheadings: text]]textBroken Parent ValidationError 79%Reference SyntaxConcept 78%Array SyntaxConcept 77%
## Team [[team]]
### Members [[members: [User]]]
#### Alice [[alice]]
- role: admin
- email: alice@ex.com
#### Bob [[bob]]
- role: dev
- email: bob@ex.com
Result:
- Object
teamwith fieldmembers: ["[[#team.members.alice]]", "[[#team.members.bob]]"] - Two objects with
__id: "team.members.alice"/"team.members.bob",__parent: "[[#team]]"and__parent_field: "members"
Prefer [Kind] arrays for homogeneous collections. When many items share a Kind and don't need to be referenced individually (lint rules, steps, columns), use a [Kind] array with a table — ids are auto-generated; do NOT hand-name each item as a separate top-level object (rule_a, rule_b, ...). If items WILL be referenced, name them with simple local names: an array child's name is just the last dot-path segment (svc_a.rules.timeout_rule), so use [[timeout_rule]] — never a parent-prefixed [[svc_a_timeout_rule]]. The same local name under different parents is fine.
Object Arrays via Tables [[object_arrays_tables: text]]textArray SyntaxConcept 76%Broken Parent ValidationError 71%Write Your First QMD.md File Tutorial 68%
## Team [[team]]
### Members [[members: [User]]]
| name | role | email |
| ------- | --------- | -------------- |
| Alice | admin | alice@ex.com |
| Bob | developer | bob@ex.com |
| Charlie | designer | charlie@ex.com |
Result: same thing — 3 objects with auto-generated positional IDs (team.members.members_0, members_1, members_2). These are the IDs to use when referencing a table row: [[#team.members.members_0]]. Positional ids shift if rows are reordered — name the item explicitly (subheading form) if it will be referenced.
When to use tables:
- ✅ Many homogeneous objects with simple fields
- ✅ Tabular data (configurations, lists)
- ❌ Objects with nested structures
- ❌ Objects with multiline fields
Embedded YAML Blocks [[yaml_blocks: text]]textYAML Block SyntaxConcept 87%Field SyntaxConcept 67%Quickstart Tutorial 67%
QMD.md supports embedded YAML blocks for migrating existing configurations.
## Server [[server]]
### Configuration [[config: yaml]]
```yaml
database:
host: localhost
port: 5432
replicas: 3
```
Result: field config contains parsed YAML as a nested object. Data accessible directly: server.config.database.host.
If YAML is invalid — field is saved as text with __parse_error, graph continues loading.
Auto-Detection Rules [[auto_detection: text]]textField SyntaxConcept 86%Multiple Definitions In Heading ValidationError 80%Array SyntaxConcept 74%
When the parser encounters a heading-syntax field, it determines the type:
- Has child subheadings with
[[field_id]]? → Object array - Has lists with
- key: value(valid keys)? → Nested object - Has lists WITHOUT colons
- value? → Primitive array - Has text but no valid lists? → Text field
When in doubt — explicitly specify the type:
### Description [[desc: text]] # text field
### Config [[config: object]] # nested object
### Items [[items: [Item]]] # object array
### Tags [[tags: array]] # primitive array
System Types [[system_types: text]]textObject SyntaxConcept 85%Explicit System Type ValidationError 76%Dump Index Command 75%
- about: Workspace
QMD.md uses system types with the __ prefix:
| Type | Description |
|---|---|
__Workspace |
Root project container |
__Namespace |
Logical grouping (subfolder) |
__Document |
Document container (auto-created if text blocks exist) |
__TextBlock |
Unstructured text (heading without [[id]] and without fields) |
User types: User, Config, Table, etc. (PascalCase).
Filtering: objects.filter(obj => !obj.__kind?.startsWith("__")) — business objects only.
__Document and __TextBlock are created by the parser automatically only. Explicit declaration [[id: __Document]] or [[id: __TextBlock]] is an error.
Lossless Round-Trip [[round_trip: text]]textObject SyntaxConcept 95%Parse Command 81%Parsing Algorithm Algorithm 78%
QMD.md supports lossless round-trip: QMD.md → JSON → QMD.md without data loss.
The parser stores metadata in system fields:
| Field | Description |
|---|---|
__types |
Field data types (string, number, boolean, null, array) |
__syntax |
Notation syntax (yaml_array, markdown_list, table, headers, yaml_multiline, yaml_object) |
__level |
Heading level (1-6) |
__has_explicit_id |
false if ID was auto-generated (absent if ID is explicit) |
__comments |
Comments with semantic binding to fields |
__labels |
Original heading labels for fields |
__code_fences |
Metadata about fenced code blocks in TextBlock |
__comments format: array of {after: "anchor", content: "raw markdown"}.
after: "__self"— comment on the object itselfafter: "field_name"— comment after a fieldcontent— raw markdown slice, parser does not interpret contents
__syntax values and rebuild:
| __syntax | Heading on rebuild |
|---|---|
multiline_text |
### Label [[field: text]] |
markdown_list |
### Label [[field: array]] |
yaml_object |
### Label [[field: yaml]] |
headers |
### Label [[field: [Kind]]] |
table |
### Label [[field: [Kind]]] |
Reference [[reference: Reference]]ReferenceReference SyntaxConcept 86%Show References ExtCommand 78%Invalid Reference Syntax DiagnosticRule 76%
- depends: Syntax
Overview [[reference_overview: text]]textReference SyntaxConcept 83%Write Your First QMD.md File Tutorial 76%Why QMDC Explanation 73%
References connect objects in the graph. They start with # inside [[...]].
Basic References [[basic_refs: text]]textReference SyntaxConcept 66%Validation Errors SyntaxConcept 65%
## Order [[order1]]
- customer: [[#alice]]
- product: [[#laptop]]
Array References [[array_refs: text]]textArray SyntaxConcept 76%Reference SyntaxConcept 70%Write Your First QMD.md File Tutorial 69%
YAML reference arrays and expanded form:
## User [[alice]]
- roles: [[[#admin]], [[#dev]]]
## User [[bob]]
### Roles [[roles]]
- [[#dev]]
- [[#viewer]]
Field References [[field_refs: text]]textReference SyntaxConcept 90%Broken Parent ValidationError 79%Object SyntaxConcept 77%
References use full hierarchical dot-paths to target child objects and their fields:
## Team [[team]]
### Members [[members: [User]]]
#### Alice [[alice]]
- role: admin
## Report [[report]]
- author: [[#team.members.alice]]
- author_role: [[#team.members.alice.role]]
Syntax: [[#parent.field.child]] — hierarchical dot-path to a child object. [[#parent.field.child.field_name]] — references a field on that object.
How resolution actually works:
- An object dot-path resolves by exact whole-string match against an object's full hierarchical
__id. Intermediate segments are not walked or validated —[[#a.b.c]]resolves iff an object with__idexactlya.b.cexists. Depth is unlimited. - A field reference splits on the last dot only: everything before it must be a full object
__id, and exactly one trailing segment names the field. Multi-segment field paths ([[#obj.nested.deep_field]]whereobj.nestedis not an object id) do not resolve. - System fields (
__id,__file, ...) cannot be referenced. - Namespace-qualified field references (
[[#ns:obj.field]]) are not supported — only local field refs. - If a dot-path resolves both as an object
__idAND as a field on the prefix object, validation reportsambiguous_field_reference(see Error Types).
There is no filter or wildcard syntax — forms like [[#items[key=value]]] are not part of the format and will not resolve. To reference a table row, use its auto-generated ID (parent.field.field_<n>, e.g. [[#users.columns.columns_0]]), or give the row an explicit name if it will be referenced.
Kind-Qualified References [[kind_refs: text]]textAmbiguous Reference DiagnosticRule 82%Hover LSPFeature 74%Reference SyntaxConcept 74%
When two objects have the same ID but different Kind, the reference must specify Kind:
## Users [[users: Table]]
- name: users
## Users [[users: Entity]]
- type: domain_model
- table_ref: [[#Table:users]] # reference to table
- entity_ref: [[#Entity:users]] # reference to entity
- ambiguous: [[#users]] # ❌ ERROR!
Cross-Namespace References [[namespace_refs: text]]textReference SyntaxConcept 84%Reference Resolution Algorithm 77%Ambiguous Reference ValidationError 77%
- about: Workspace
## API Service [[api_service: Service]]
- database: [[#storage:users]] # different namespace
- user_table: [[#storage:Table:users]] # namespace + Kind
Full format: [[#workspace:namespace:Kind:id]]
All components are optional except id:
[[#id]]— local reference (current namespace)[[#Kind:id]]— with type (collision resolution)[[#namespace:id]]— different namespace[[#namespace:Kind:id]]— full form for cross-namespace
Resolution Order and __local_id [[resolution_order: text]]textReference Resolution Algorithm 86%Ambiguous Reference DiagnosticRule 82%Reference SyntaxConcept 81%
Every object carries __local_id — the segment after the last dot of its hierarchical __id (for flat ids, the id itself). References resolve in this order:
- Exact match on the full
__id(including hierarchical dot-paths) - Field reference (last-dot split, prefix is a full
__id) - Fallback match on
__local_id, filtered by namespace
The __local_id fallback means [[#alice]] can resolve to team.members.alice when exactly one object with that local id exists in the namespace. If multiple objects share the local id, the reference is ambiguous_reference — fix it by using the full hierarchical ID ([[#team.members.alice]]).
Rename Cascade [[rename_cascade: text]]textRename LSPFeature 82%Rename Object McpTool 80%Reference SyntaxConcept 72%
Renaming an object rewrites references to it AND to all its descendants: renaming team → squad rewrites [[#team]], [[#team.config]], [[#team.members.alice]], and field refs like [[#team.config.timeout]]. Rewrites are token-bounded — [[#teamwork]] is never touched by renaming team. Descendant definitions themselves are not edited (they are anchored by __local_id).
Reference Philosophy [[ref_philosophy: text]]textWrite Your First QMD.md File Tutorial 79%Broken Link ValidationError 78%Reference SyntaxConcept 77%
- about: Validation
Reference problems are reported with severity: error, but they are non-fatal: unlike syntax errors, they never prevent the graph from being built.
- Object not found → the object still loads; the reference remains a plain string;
broken_linkerror reported - ID collision without Kind → unresolved reference;
ambiguous_referenceerror reported - Broken links don't break the entire graph — validation collects them into a report instead of aborting
Where References Are Not Parsed [[refs_not_parsed: text]]textReference SyntaxConcept 81%Field SyntaxConcept 78%Write Your First QMD.md File Tutorial 73%
References [[#id]] inside text fields are validated for existence (broken links detected), but remain part of the text — they are not resolved into objects.
References [[#id]] inside YAML pipe | blocks are NOT parsed — content remains plain text. They won't create edges in the graph or trigger broken_link errors.
References inside inline code (`<span class="broken-link">[[#ref]]</span>`) are NOT parsed.
References inside fenced code blocks with example modifier are NOT parsed.
Typed Edges [[typed_edges: text]]textReference SyntaxConcept 97%Markdown to Graph Explanation 77%QMDC for Agents Explanation 75%
Every edge in the graph carries an edge_type — a user-defined string describing the relationship.
For inline fields, edge_type equals the field name:
## Payment [[payment: Service]]
- depends: [[#auth]]
- database: [[#payments_db]]
Creates edges: (payment, depends, auth, edge_type="depends") and (payment, database, payments_db, edge_type="database").
For text field preambles, edge_type comes from the preamble key, while source_field is the text field name:
### Rationale [[rationale: text]]
- about: [[#checkout_flow]]
- depends: [[#order_svc]], [[#payment_svc]]
This service handles payments...
Creates edges:
(payment, source_field="rationale", checkout_flow, edge_type="about")(payment, source_field="rationale", order_svc, edge_type="depends")(payment, source_field="rationale", payment_svc, edge_type="depends")
Preamble rules:
- Must start at the beginning of the text field value
- ALL list items must be valid
- key: [[#ref]]fields (all-or-nothing) - Separated from the rest of the text by a blank line
- Preamble lines stay in the raw text value — no stripping
Edges table schema:
edges (source_id, source_field, target_id, edge_type, __workspace)
UNIQUE(source_id, source_field, target_id, edge_type)
Querying typed edges:
# All "depends" relationships
qmdc query . "SELECT s.__id, t.__id FROM edges e JOIN objects s ON e.source_id = s.__global_id JOIN objects t ON e.target_id = t.__global_id WHERE e.edge_type = 'depends'"
# Preamble edges (where edge_type differs from source_field)
qmdc query . "SELECT s.__id, e.source_field, t.__id, e.edge_type FROM edges e JOIN objects s ON e.source_id = s.__global_id JOIN objects t ON e.target_id = t.__global_id WHERE e.edge_type != e.source_field"
Workspace [[workspace: Workspace]]WorkspaceWorkspace SyntaxConcept 82%Content Generator ContentGenerator 82%Guide McpResource 81%
- depends: Reference
Structure [[workspace_structure: text]]textWorkspace SyntaxConcept 93%Content Generator ContentGenerator 82%Build a Small Workspace HowTo 80%
A workspace is a folder with multiple QMD.md files that can reference each other.
my-project/
├── readme.qmd.md # Workspace root (__Workspace)
├── users.qmd.md
├── storage/
│ ├── readme.qmd.md # Namespace "storage" (__Namespace)
│ ├── tables.qmd.md
│ └── indexes.qmd.md
└── api/
├── readme.qmd.md # Namespace "api" (__Namespace)
└── endpoints.qmd.md
Defining a Workspace [[defining_workspace: text]]textWorkspace In Wrong File DiagnosticRule 80%Workspace SyntaxConcept 80%Content Generator ContentGenerator 71%
File readme.qmd.md in project root:
# My Project [[myproject:__Workspace]]
- description: Project description
- version: 1.0
__Workspace in Kind indicates this is the workspace root.
Defining a Namespace [[defining_namespace: text]]textWorkspace SyntaxConcept 79%Build a Small Workspace HowTo 69%Semantic Storage Schema NarrativeDoc 66%
File storage/readme.qmd.md:
# Storage Layer [[storage:__Namespace]]
- description: Database schema and storage
__Namespace in Kind indicates this folder is a namespace.
All files in the folder automatically inherit workspace and namespace.
Cross-File References [[cross_file_refs: text]]textWorkspace SyntaxConcept 80%Markdown to Graph Explanation 74%Build a Small Workspace HowTo 74%
- about: Reference
File api/endpoints.qmd.md:
## Get Users [[get_users: Endpoint]]
- method: GET
- path: /api/users
- returns: [[#storage:Table:users]]
File storage/tables.qmd.md:
## Users [[users: Table]]
- columns: [[[#id_col]], [[#email_col]]]
The parser automatically:
- Finds all objects in all files
- Indexes them by
namespace:Kind:id - Validates all references
- Reports broken links
Object Metadata [[object_metadata: text]]textWorkspace SyntaxConcept 90%Build a Small Workspace HowTo 79%Dump Index Command 78%
When parsing a workspace, each object gets:
{
"__id": "users",
"__kind": "Table",
"__file": "storage/tables.qmd.md",
"__line": 5,
"__workspace": "[[#myproject]]",
"__namespace": "[[#storage]]",
"name": "users"
}
__file— file path__line— line number__workspace— reference to__Workspaceobject__namespace— reference to__Namespaceobject (ornullfor root)
Dynamic Blocks [[dynamic_blocks: text]]textDynamic Block SyntaxConcept 94%Query Your Markdown Graph HowTo 85%Run SQL Query ExtCommand 84%
- about: CLI
Dynamic blocks — a mechanism for executing SQL queries against workspace data and displaying results in documents.
Reusable query object:
## Get Tables [[get_tables: Query]]
- sql: SELECT __id, __label FROM objects WHERE __kind = 'Table'
Reference to Query:
Use a table code block with query: [[#get_tables]] to reference a Query object.
Inline SQL:
Use a table code block with sql: SELECT __id, __kind FROM objects for inline queries.
Scope parameter:
scope: workspace(default) — filter by current workspacescope: all— data from all workspaces
Block types: table (HTML table), diagram (D2/Mermaid, future), chart (charts, future).
Validation [[validation: Validation]]ValidationContent Generator ContentGenerator 86%Validate Workspace ExtCommand 77%Workspace Validate Command 74%
Commands [[validation_commands: text]]textWorkspace Validate Command 88%Validate a Document HowTo 88%Content Generator ContentGenerator 87%
- about: CLI
# 1. Check file syntax
qmdc parse -i file.qmd.md > /dev/null || exit 1
# 2. Validate workspace (returns JSON array of errors)
qmdc workspace validate ./my-project
# If no errors — returns []
# If errors exist — returns array of error objects, exit 1
# 3. Check all files in a directory
for file in $(find ./docs -name "*.qmd.md"); do
qmdc parse -i "$file" > /dev/null || echo "❌ $file"
done
Error Format [[validation_error_format: text]]textWorkspace Validate Command 92%Validate Workspace ExtCommand 86%Validation Errors SyntaxConcept 86%
qmdc workspace validate returns a JSON array:
[
{
"type": "broken_link",
"message": "Object 'xyz' not found",
"file": "file.qmd.md",
"line": 5,
"objectId": "abc",
"reference": "[[#xyz]]",
"severity": "error"
}
]
Error Types [[validation_error_types: text]]textValidation Errors SyntaxConcept 87%Workspace Validate Command 84%Validate a Document HowTo 82%
| Code | Description |
|---|---|
broken_link |
Reference [[#id]] to a non-existent object |
duplicate_id |
Two objects with the same FULL hierarchical __id (parent-scoped children with the same local id never collide) |
ambiguous_reference |
Reference [[#id]] could point to multiple objects |
broken_parent |
Parent object not found for a dot-ID declaration (see Dot-ID Declarations) |
ambiguous_field_reference |
Dot-path resolves both as an object __id and as a field on the prefix object |
nested_workspace |
Workspace inside another workspace (forbidden) |
workspace_in_wrong_file |
__Workspace/__Namespace declared outside readme.qmd.md |
structured_in_textblock |
Structured element inside __TextBlock |
multiple_definitions |
Heading contains more than one [[...]] |
ordered_list_in_array |
Numbered list in heading-syntax array (bullet lists only) |
nested_subitems |
Nested lists - key:\n - item (forbidden) |
explicit_system_type |
Explicit declaration of [[id: __Document]] or [[id: __TextBlock]] |
mixed_field_keys |
Mix of valid and invalid keys in one object |
invalid_id_character |
Dot in a NESTED heading's explicit ID (dot-IDs are legal only on top-level headings) |
dangling_field |
Heading-syntax field (text, array, yaml, ...) with no parent object at a higher heading level |
invalid_map_entry |
List item inside [[field: map]] that is not a valid key: value pair |
invalid_map_content |
Content inside [[field: map]] that is not a bullet list of key: value items |
Pre-Commit Checklist [[pre_commit_checklist: text]]textContent Generator ContentGenerator 82%Object SyntaxConcept 78%Write Your First QMD.md File Tutorial 76%
Before saving a QMD.md file:
- ✅ All objects have
[[id]](or are explicitly auto-generated) - ✅ All field keys are valid (
[a-zA-Z][a-zA-Z0-9_]*) - ✅
true/false/nullin lowercase - ✅ No patterns like
- key:\n - item(use YAML or heading-syntax) - ✅ No numbered lists in heading-syntax arrays
- ✅ Verified via
qmdc parse -i file.qmd.md - ✅ If workspace — verified via
qmdc workspace validate . - ✅ All references exist
- ✅ No ambiguous references (Kind specified on collision)
What Is Normal [[validation_normals: text]]textWrite Your First QMD.md File Tutorial 71%Validate a Document HowTo 69%Content Generator ContentGenerator 68%
- ⚠️ Broken links in examples (users, orders, User, Table) — normal
- ⚠️ Broken links in types (text, id, Structure, Method) — normal
- ⚠️ References NOT parsed in inline code (
`<span class="broken-link">[[#ref]]</span>`) — normal - ⚠️ References NOT parsed in fenced code blocks with
examplemodifier — normal - ⚠️ References NOT parsed in YAML multiline
|blocks — normal
The example modifier for code blocks:
To show QMD.md code examples in documentation, use the example modifier:
{
"__id": "user",
"__label": "User",
"name": "Alice",
"profile": "[[#user_profile]]" // ← this reference is not parsed
}
In json example blocks, [[#id]] references remain text and don't create broken_link errors.
CLI [[cli: CLI]]CLI
- depends: Workspace, Validation
Parse — QMD.md → JSON [[cmd_parse: text]]textParse Command 93%Rebuild Command 83%Python Parser Parser 78%
Converts QMD.md to JSON.
# File → stdout
qmdc parse -i file.qmd.md
# Syntax check (exit 1 on error)
qmdc parse -i file.qmd.md > /dev/null
# Stdin → stdout
echo "## Test [[test]]" | qmdc parse
# Without metadata
qmdc parse -i file.qmd.md --no-comments --no-syntax
# Rust: output format (minimal, standard, full)
qmdc parse -i file.qmd.md --format full
Rebuild — JSON → QMD.md [[cmd_rebuild: text]]textRebuild Command 94%Parse Command 86%Python Parser Parser 80%
Converts JSON back to QMD.md (lossless round-trip).
qmdc rebuild -i data.json
qmdc rebuild -i data.json -o doc.qmd.md
Formatting (parse → rebuild) [[cmd_lint: text]]textRebuild Command 93%Formatting LSPFeature 81%Parse Command 73%
There is no separate lint command. Pipe through parse → rebuild to format QMD.md to canonical form:
# Canonical formatting (lossless round-trip)
qmdc parse -i doc.qmd.md | qmdc rebuild
Workspace Parse [[cmd_workspace_parse: text]]textWorkspace Parse Command 89%Parse Workspace ExtCommand 84%Get Tree McpTool 80%
- about: Workspace
Parses entire workspace to JSON.
qmdc workspace parse ./my-project -o workspace.json
qmdc workspace parse ./my-project --format full
Workspace Validate [[cmd_workspace_validate: text]]textWorkspace Validate Command 95%Validate Workspace ExtCommand 89%Diagnostics McpResource 84%
- about: Workspace, Validation
Validates workspace — broken links, duplicate IDs, ambiguous references.
# Returns JSON array of errors ([] if all ok)
qmdc workspace validate ./my-project
# Exit code: 0 if no errors, 1 if errors exist
Query — SQL Queries [[cmd_query: text]]textQuery Command 93%Run SQL Query ExtCommand 93%Query Your Markdown Graph HowTo 89%
- about: Workspace
SQL queries against workspace via SQLite.
# Find all objects of type Table
qmdc query ./my-project "SELECT __id, __kind, __file FROM objects WHERE __kind = 'Table'"
# Find all references to an object
qmdc query ./my-project "SELECT source_id, field_name FROM edges WHERE target_id = 'users'"
# Count objects by type
qmdc query ./my-project "SELECT __kind, COUNT(*) as count FROM objects GROUP BY __kind"
# Query via Query object (reference to [[id:Query]] in workspace)
qmdc query ./my-project "#all_services"
# JSON output format
qmdc query ./my-project "SELECT * FROM objects LIMIT 10" --format json
Available tables:
objects— all objects (__id,__kind,__label,__file,__line,dataJSON)edges— all graph edges (source_id,source_field,target_id,edge_type,__workspace)
Parsers [[parsers: text]]textComponents NarrativeDoc 88%Overview NarrativeDoc 88%Parsers Namespace 80%
Three parser implementations, all providing the same qmdc CLI:
- Python (
qmdc-py) — reference implementation,uv pip install -e ./qmdc-py - TypeScript (
qmdc-ts) — for Node.js/browser,npm install - Rust (
qmdc-rs) — production, LSP server,cargo build --release
Agent Workflow [[agent_workflow: text]]textParse Workspace ExtCommand 82%Content Generator ContentGenerator 80%Guide McpResource 79%
- about: Validation
# 1. Create/edit file
echo "## User [[user]]
- name: Alice" > users.qmd.md
# 2. Check syntax
qmdc parse -i users.qmd.md > /dev/null
if [ $? -ne 0 ]; then
echo "Syntax error!"
exit 1
fi
# 3. If workspace — validate references
qmdc workspace validate .
if [ $? -ne 0 ]; then
echo "Validation error!"
exit 1
fi
# 4. Format (optional)
qmdc lint -i users.qmd.md -o users.qmd.md
Common Errors [[common_errors: NarrativeDoc]]NarrativeDocError Envelope NarrativeDoc 68%Validate a Document HowTo 67%Content Generator ContentGenerator 66%
- about: Validation, Syntax
Missing Explicit ID [[err_missing_id: text]]textHeading SyntaxConcept 76%Invalid ID Character ValidationError 74%Broken Parent ValidationError 74%
- about: Syntax
## User
- name: Alice
ID is auto-generated from Title → __id: "user", but [[id]] is not explicitly set. Rebuild format may change. Always use explicit IDs:
## User [[user]]
- name: Alice
Invalid Field Key [[err_invalid_key: text]]textField SyntaxConcept 87%Mixed Field Keys ValidationError 80%Invalid Map Entry ValidationError 74%
- about: Syntax
## User [[user]]
- First Name: Alice # space in key → markdown text!
- my-key: value # hyphen in key → markdown text!
- 2name: value # starts with digit → markdown text!
Valid key: [a-zA-Z][a-zA-Z0-9_]* (starts with letter, only letters/digits/underscore).
If an object mixes valid and invalid keys — error mixed_field_keys is generated.
Correct:
## User [[user]]
- first_name: Alice
- firstName: Alice
- my_key: value
Case-Sensitive Types [[err_case_types: text]]textData Type SyntaxConcept 77%Write Your First QMD.md File Tutorial 73%
- about: Syntax
- active: True # will be string "True"!
- empty: NULL # will be string "NULL"!
Must be lowercase:
- active: true # boolean
- empty: null # null
Broken Link [[err_broken_link: text]]textBroken Link DiagnosticRule 80%Validate a Document HowTo 77%Write Your First QMD.md File Tutorial 72%
- about: Validation, Reference
## Order [[order1]]
- user: [[#nonexistent]] # object doesn't exist!
How to find:
qmdc workspace validate .
# Returns JSON with broken_link error
Check object existence before creating a reference:
qmdc query . "SELECT __id FROM objects WHERE __id = 'nonexistent'"
# → empty result = object not found
Ambiguous Reference [[err_ambiguous_ref: text]]textAmbiguous Reference DiagnosticRule 78%Validate a Document HowTo 70%Hover LSPFeature 70%
- about: Validation, Reference
## Users [[users: Table]]
...
## Users [[users: Entity]]
...
## Order [[order]]
- ref: [[#users]] # ambiguous!
Fix by specifying Kind:
- table_ref: [[#Table:users]]
- entity_ref: [[#Entity:users]]
Nested Lists in Fields [[err_nested_lists: text]]textNested Subitems ValidationError 85%Array SyntaxConcept 81%Write Your First QMD.md File Tutorial 77%
- about: Syntax
## Config [[config]]
- items:
- first
- second
Fix with YAML array or heading-syntax:
## Config [[config]]
- items: [first, second]
Or:
## Config [[config]]
### Items [[items: array]]
- first
- second
Numbered Lists in Arrays [[err_numbered_lists: text]]textOrdered List In Array ValidationError 90%Nested Subitems ValidationError 75%Array SyntaxConcept 66%
- about: Syntax
### Steps [[steps: array]]
1. First step
2. Second step
Only bullet lists are allowed:
### Steps [[steps: array]]
- First step
- Second step
Practical Examples [[examples: NarrativeDoc]]NarrativeDocContent Generator ContentGenerator 74%Guide McpResource 72%Recording Demos NarrativeDoc 69%
Database Schema [[uc_database: text]]textHover LSPFeature 68%Get Workspace Tree Command 66%Dynamic Block SyntaxConcept 66%
Tables with columns, indexes, and references:
## Users [[users: Table]]
### Columns [[columns: [Column]]]
| name | type | nullable |
| ----- | ------- | -------- |
| id | bigint | false |
| email | varchar | false |
| age | int | true |
### Indexes [[indexes: [Index]]]
#### Email Index [[email_idx]]
- columns: [[[#users.columns.columns_1]]]
- unique: true
Table rows get positional auto-IDs (columns_0, columns_1, ...). To make row references robust, use the subheading form and name each column instead.
Microservice Architecture [[uc_microservices: text]]text
API Gateway with routes and dependencies:
## API Gateway [[gateway: Service]]
- port: 8080
- dependencies: [[[#user_service]], [[#order_service]]]
### Routes [[routes: [Route]]]
#### Users [[users_route]]
- path: /api/users/*
- target: [[#user_service]]
- methods: [GET, POST, PUT]
Configuration [[uc_config: text]]text
- about: Syntax
Database and cache settings:
## Production [[prod: Config]]
### Database [[db]]
- host: db.prod.example.com
- port: 5432
- ssl: true
### Cache [[cache]]
- provider: redis
- nodes: [redis1.prod, redis2.prod, redis3.prod]
- ttl: 3600