CLI Commands
Commands for working with QMD.md files via the qmdc CLI.
Parse [[cmd_parse: Command]]CommandCLI CLI 91%Guide McpResource 82%Parse Workspace ExtCommand 82%
Converts QMD.md to JSON.
- parser: Python Parser, TypeScript Parser, Rust Parser
Description [[description: text]]textRun SQL Query ExtCommand 92%Query Your Markdown Graph HowTo 88%Query SQL McpTool 83%
Reads QMD.md from a file or stdin and outputs JSON objects to a file or stdout. Supports different output formats (minimal, standard, full) and optional metadata exclusion (__comments,__syntax).
Output formats:
- minimal — user fields only
- standard — system fields + user fields (default)
- full — standard + __references,__comments, __types
Round-trip guarantee: parse → rebuild restores the original document, and re-parsing the result always yields the same graph. Text placement has known limits: content that follows a heading-declared array's own table is re-anchored on the field before that heading, so it can move above the array heading on rebuild, and a top-level array is rebuilt as a wrapper heading plus a nested array heading. The graph is unchanged in both cases; only the layout is.
Exit code: 0 if the document parsed cleanly, 1 if the result holds any __ParsingError
object. The JSON is written in full either way, errors included, so the exit code only says
whether something was rejected; qmdc parse -i file.qmd.md > /dev/null || exit 1 is a syntax
check.
Syntax [[syntax: text]]textCLI CLI 86%Validate a Document HowTo 84%Validate Workspace ExtCommand 83%
qmdc parse -i <file>
qmdc parse -i <file> -o <output>
echo '## Test [[test]]' | qmdc parse
Options [[options: text]]textRun SQL Query ExtCommand 73%Dynamic Block SyntaxConcept 72%Workspace Workspace 68%
| Option | Short | Description | Type | Required |
|---|---|---|---|---|
--input |
-i |
Input file (reads from stdin if omitted) | path | no |
--output |
-o |
Output file (writes to stdout if omitted) | path | no |
--format |
Output format: minimal, standard (default), full (Rust only) | enum | no | |
--no-comments |
Exclude __comments field from output |
boolean | no | |
--no-syntax |
Exclude __syntax field from output |
boolean | no | |
--no-pretty |
Compact JSON without formatting | boolean | no |
Examples [[examples: text]]textCLI CLI 93%Validate Workspace ExtCommand 86%Validate a Document HowTo 83%
# File → stdout
qmdc parse -i doc.qmd.md
# Stdin → stdout
echo "## Test [[test]]" | qmdc parse
# File → file
qmdc parse -i doc.qmd.md -o output.json
# Without metadata
qmdc parse -i doc.qmd.md --no-comments --no-syntax
# Compact JSON
qmdc parse -i doc.qmd.md --no-pretty
# Rust: choose format (minimal, standard, full)
qmdc parse -i doc.qmd.md --format full
Rebuild [[cmd_rebuild: Command]]CommandCLI CLI 94%Parse Workspace ExtCommand 72%Object SyntaxConcept 69%
Converts JSON back to QMD.md.
- parser: Python Parser, TypeScript Parser, Rust Parser
Description [[description: text]]text
Reads JSON objects from a file or stdin and outputs QMD.md to a file or stdout. Round-trips a parsed document back to QMD.md — see the round-trip note above for the two shapes where the layout can shift while the graph stays identical. Preserves object and field order. Restores hierarchy from __parent and__level.
Syntax [[syntax: text]]text
qmdc rebuild -i <file>
qmdc rebuild -i <file> -o <output>
Options [[options: text]]text
| Option | Short | Description | Type | Required |
|---|---|---|---|---|
--input |
-i |
Input JSON file (reads from stdin if omitted) | path | no |
--output |
-o |
Output QMD.md file (writes to stdout if omitted) | path | no |
Examples [[examples: text]]text
# File → stdout
qmdc rebuild -i data.json
# Stdin → stdout
echo '[{"__id":"test","name":"Test"}]' | qmdc rebuild
# File → file
qmdc rebuild -i data.json -o doc.qmd.md
Tip: canonical formatting [[cmd_rebuild_formatting: text]]textCLI CLI 93%Formatting LSPFeature 78%Validation Validation 72%
There is no separate lint command. To format a QMD.md file to canonical form (the equivalent of ruff/prettier), pipe it through parse → rebuild — a lossless round-trip that normalizes whitespace and field/array formatting:
qmdc parse -i doc.qmd.md | qmdc rebuild
Query [[cmd_query: Command]]Command
Executes SQL queries against workspace via SQLite.
- parser: Python Parser, TypeScript Parser, Rust Parser
Description [[description: text]]text
Automatically:
- Recursively finds all QMDC workspaces in the specified directory
- Parses all files and loads objects into SQLite
- Extracts graph edges from references between objects
- Executes the SQL query against the database
Available tables:
objects— all objects with fields__id,__kind,__label,__file,__line,data(JSON)edges— all graph edges with fieldssource_id,source_field,target_id,edge_type,__workspace
Syntax [[syntax: text]]text
qmdc query <path> "<sql>"
qmdc query <path> "#query_id"
qmdc query -w <path> -w <path> "<sql>"
Options [[options: text]]text
| Option | Short | Description | Type | Required |
|---|---|---|---|---|
<path> |
Path to workspace directory | path | no | |
<query> |
SQL query or reference to a Query object (#query_id) |
string | yes | |
--with |
-w |
Compose this workspace explicitly; repeatable. Mutually exclusive with <path> |
path | no |
--format |
Output format: table (default), json | enum | no |
Examples [[examples: text]]text
# SQL query against workspace
qmdc query ./my-project "SELECT __id, __kind, __label FROM objects WHERE __kind = 'Service'"
# JSON output format
qmdc query ./my-project "SELECT * FROM objects LIMIT 10" --format json
# Query via Query object (reference to [[id:Query]] in workspace)
qmdc query ./my-project "#all_services"
# Compose workspaces at unrelated paths (the only positional left is the query)
qmdc query -w ~/checkouts/repo_a -w /srv/repo_b "SELECT * FROM edges"
# Count objects and edges
qmdc query ./my-project "SELECT COUNT(*) as total FROM objects"
qmdc query ./my-project "SELECT COUNT(*) as edges FROM edges"
Workspace Parse [[cmd_workspace_parse: Command]]CommandCLI CLI 84%Parse Workspace ExtCommand 74%Workspace In Wrong File ValidationError 70%
Parses an entire workspace (multiple linked QMD.md files) to JSON.
- parser: Python Parser, TypeScript Parser, Rust Parser
Description [[description: text]]text
Finds the workspace root by locating a file with [[id:__Workspace]]. Recursively finds all .qmd.md files, respects .qmdcignore for exclusions. Parses each file and adds metadata (__file,__workspace, __namespace). Returns all objects + file list + validation errors.
Syntax [[syntax: text]]text
qmdc workspace parse <path>
qmdc workspace parse <path> -o <output>
qmdc workspace parse -w <path> -w <path>
Options [[options: text]]text
| Option | Short | Description | Type | Required |
|---|---|---|---|---|
<path> |
Path to workspace directory (defaults to .) |
path | no | |
--with |
-w |
Compose this workspace explicitly; repeatable. Mutually exclusive with <path> |
path | no |
--output |
-o |
Output JSON file (Python/TypeScript) | path | no |
--format |
Output format: minimal, standard, full (Rust only) | enum | no |
Examples [[examples: text]]text
# Python/TypeScript
qmdc workspace parse ./my-project -o workspace.json
# Rust (output to stdout)
qmdc workspace parse ./my-project > workspace.json
# Compose two workspaces that do not share a parent directory
qmdc workspace parse -w ~/checkouts/repo_a -w /srv/repo_b
# With format selection (Rust only)
qmdc workspace parse ./my-project --format full
Output [[output: text]]textCLI CLI 75%Workspace SyntaxConcept 75%Workspace In Wrong File ValidationError 72%
One JSON object of the same shape for every invocation:
{
"root": "/home/me/checkouts/shop",
"workspaces": [
{"id": "shop", "root": "/home/me/checkouts/shop", "path": ""}
],
"files": ["readme.qmd.md", "storage/tables.qmd.md"],
"objects": [],
"errors": []
}
workspacesis always present: every workspace in the result, ordered bypath.rootis where the workspace is on disk, as a canonical absolute path;pathis where its files sit in__file.rootat the top level is the directory every__fileis relative to, ornullwhen the invocation composed-wpaths. Those need not share any directory, so each workspace sits at its own id instead:-w ~/a/shop -w /srv/billinggives__filevalues likeshop/storage/tables.qmd.mdandbilling/readme.qmd.md, the same wherever the checkouts are.- To open the file an object came from: take the entry whose
pathis the longest leading directory of the object's__file(repo_aleadsrepo_a/x.qmd.md, notrepo_ab/x.qmd.md), and join that entry'srootwith the rest of__file. For a single workspacepathis"", which leads every file, so this is simplyroot+__file.
Given a directory holding several workspaces, each sits at its directory under that container
(path: "repo_a"), and a file outside every workspace has no entry: it is located from the
top-level root.
An entry of errors carries type, message, file, line, objectId, fieldName, reference,
candidates and severity, omitting whatever is absent — the SAME key names workspace validate
uses, so one error reads the same whichever command produced it. The names object and field were
this envelope's own spelling before QMD-77 and are gone.
Workspace Validate [[cmd_workspace_validate: Command]]CommandValidate Workspace ExtCommand 88%Validation Errors SyntaxConcept 86%CLI CLI 85%
Validates workspace: checks for broken links, duplicate IDs, ambiguous references.
- parser: Python Parser, TypeScript Parser, Rust Parser
Description [[description: text]]text
Returns only a JSON array of errors (empty array [] if no errors).
Available in all three parsers (Python, TypeScript, Rust).
Error types:
broken_link— reference[[#id]]to a non-existent object (after both__idand__local_idfallback lookups fail)duplicate_id— two objects with the same full hierarchical__idambiguous_reference— reference that could point to multiple objects (by__idKind collision or by multiple__local_idmatches)broken_parent— dot-ID declaration ([[parent.child]]) whose parent object does not exist in the workspaceambiguous_field_reference— dot-path resolves both as an object__idand as a field on the prefix objectnested_workspace— workspace inside another workspace (forbidden)workspace_in_wrong_file— workspace declaration in wrong file
Parse-stage errors (invalid_id_character, mixed_field_keys, nested_subitems, ...) surface as __ParsingError objects — the full catalog is in the validation-errors reference. In one parse result their ids are error_0, error_1, … in the order the errors are raised, unique within the result; they carry no __global_id and cannot be referenced.
Resolution order: for each reference, the validator tries: (1) exact __id match, (2) __local_id fallback. A broken_link is only produced when both fail. An ambiguous_reference is produced when multiple candidates match at any step.
Error object fields: type, message, file, line, objectId, fieldName, reference, candidates, severity
Exit code: 0 if no errors, 1 if errors exist, 2 if the invocation itself was refused
(a usage error, e.g. a positional path together with --with).
Syntax [[syntax: text]]text
qmdc workspace validate <path>
qmdc workspace validate -w <path> -w <path>
Options [[options: text]]text
| Option | Short | Description | Type | Required |
|---|---|---|---|---|
<path> |
Path to workspace directory (defaults to .) |
path | no | |
--with |
-w |
Compose this workspace explicitly; repeatable. Mutually exclusive with <path> |
path | no |
Examples [[examples: text]]text
# Validate workspace (returns JSON array of errors)
qmdc workspace validate ./my-project
# Validate a project made of workspaces at unrelated paths
qmdc workspace validate -w ~/checkouts/repo_a -w /srv/repo_b
# If no errors — returns empty array
[]
# If errors exist — returns array of error objects
[
{
"type": "broken_link",
"message": "Object 'xyz' not found",
"file": "file.qmd.md",
"line": 5,
"objectId": "abc",
"reference": "[[#xyz]]",
"severity": "error"
}
]