Dependency graph parser and AI analyzer for .md skill files — parse @include/@delegate/@ref directives, build graphs, resolve templates, and detect implicit dependencies via Claude for AI agent prompt engineering
Project description
dotmd-parser
Dependency graph parser for .md skill files — built for AI agent prompt engineering with Claude Code and similar tools.
Why dotmd-parser?
As AI agent projects grow, .md files start referencing each other via @include, @delegate, and @ref directives. Without tooling, you're left manually tracing dependencies to answer basic questions:
- "Which files break if I edit
shared/role.md?" - "Is there a circular reference hiding in my skill tree?"
- "What
{{variables}}are still unresolved after expansion?"
dotmd-parser solves this by parsing your .md files into a dependency graph — automatically detecting directives, runtime references, and template placeholders. One function call gives you the full picture.
Comparison
| Capability | Manual / grep | dotmd-parser |
|---|---|---|
Find @include / @delegate / @ref references |
grep -r "@include" — flat list, no context |
Structured graph with node types and edge metadata |
| Detect circular references | Hope you notice before the agent loops | Automatic detection with full cycle path in warnings |
| Reverse dependency ("what breaks?") | Manually trace every file | dependents_of(graph, "shared/role.md") — one call |
Expand @include to final text |
Copy-paste by hand | resolve("SKILL.md", variables={...}) — recursive expansion |
Find unresolved {{variables}} |
grep "{{" *.md — noisy, no dedup |
Deduplicated list per node and after expansion |
| Missing file detection | Runtime failure | Warnings at parse time with exact paths |
| Detect implicit deps (no directives yet) | Read every file, draw by hand | analyze subcommand asks Claude, emits @include / deps.yml |
| Feed Claude Code with compact context | Read 20 files × 5 KB each | digest outputs one ~2 KB summary; .claude/dotmd-index.json cache |
Installation
pip install dotmd-parser
Quick Start
from dotmd_parser import build_graph, resolve, dependents_of, summary
build_graph — Build a dependency graph
graph = build_graph("./my-skill/")
# or from a specific file
graph = build_graph("./my-skill/SKILL.md")
Returns:
{
"nodes": [
{"id": "/abs/path/to/SKILL.md", "type": "skill", "missing": false, "placeholders": []}
],
"edges": [
{"from": "...", "to": "...", "type": "include", "parallel": false}
],
"warnings": []
}
Custom node type mapping:
By default, node types are inferred from path keywords (agent, shared, prompt, reference, asset, template). You can override this with the type_map parameter:
graph = build_graph("./my-skill/", type_map=[
("helper", "utility"),
("core", "foundation"),
])
deps.yml support:
If a deps.yml file exists in the root directory, its dependencies are automatically merged into the graph:
- path: agents/planner.md
includes:
- shared/role.md
- shared/tools.md
resolve — Expand @include directives
Recursively expands @include directives into final text. @delegate and @ref lines are left as-is.
result = resolve("./prompts/main.md", variables={"name": "Alice"})
print(result["content"]) # Fully expanded text
print(result["placeholders"]) # Unresolved {{variable}} names
print(result["warnings"]) # Circular refs, missing files, etc.
dependents_of — Reverse dependency query
# "If I change shared/role.md, what else breaks?"
affected = dependents_of(graph, "/abs/path/to/shared/role.md")
summary — Human-readable overview
print(summary(graph))
# Nodes: 5 (agent:1, prompt:1, shared:2, skill:1)
# Edges: 4 (include:2, ref:1, read-ref:1)
# Warnings: 0
# Placeholders: name, role
Supported Directives
| Directive | Edge type | Expanded by resolve()? |
Description |
|---|---|---|---|
@include path/to/file.md |
include |
Yes | Inline expansion — file content is inserted at this position |
@delegate path/to/agent.md |
delegate |
No | Agent delegation — recorded in graph, left as-is in output |
@delegate path/to/agent.md --parallel |
delegate |
No | Parallel delegation with --parallel flag |
@ref path/to/file.md |
ref |
No | Runtime reference — recorded in graph, left as-is in output |
Read `path/to/file.md` |
read-ref |
No | Legacy runtime reference (same as @ref, kept for backward compatibility) |
Utility Functions
Lower-level parsing functions are also exported for custom use:
from dotmd_parser import parse_directives, parse_read_refs, parse_placeholders, parse_deps_yml
| Function | Description |
|---|---|
parse_directives(content) |
Extract @include / @delegate / @ref directives from text |
parse_read_refs(content) |
Extract legacy Read/See/list-style .md references (deduplicated) |
parse_placeholders(content) |
Extract {{variable}} placeholder names (deduplicated) |
parse_deps_yml(content) |
Parse deps.yml text into {path: [includes]} dict (no PyYAML required) |
CLI
dotmd-parser ships with sub-commands tuned for Claude Code and CI use. Running dotmd-parser <path> with no sub-command still works and falls through to show for backward compatibility.
| Command | Purpose |
|---|---|
dotmd-parser inventory <path> |
API-free: extension counts, markdown/binary ratio, largest files |
dotmd-parser index <path> |
Build and save .claude/dotmd-index.json |
dotmd-parser index <path> --scope <subdir> |
Incrementally re-index one subfolder, merge into the existing index |
dotmd-parser check <path> |
Exit non-zero on cycles / missing refs (CI-friendly) |
dotmd-parser affects <path> <file> |
Reverse dependencies of <file> |
dotmd-parser deps <path> <file> |
Direct dependencies of <file> |
dotmd-parser digest <path> |
Token-efficient text summary for LLM context |
dotmd-parser tree <path> |
ASCII dependency tree |
dotmd-parser resolve <file> [--var k=v] |
Recursively expand @include |
dotmd-parser analyze <path> |
AI dependency detection (requires ANTHROPIC_API_KEY) |
dotmd-parser analyze <path> --dry-run |
API-free: estimate tokens and USD cost |
dotmd-parser analyze <path> --plan |
API-free: emit a host-agent prompt pack for Claude Code to execute |
dotmd-parser analyze <path> --apply-from <json> |
Apply a pre-computed analysis JSON |
dotmd-parser show <path> |
Summary + full JSON graph (legacy default) |
# Typical Claude Code workflow
dotmd-parser inventory ./my-skill/ # start here if you've never seen the folder
dotmd-parser index ./my-skill/ # one-off; cached until files change
dotmd-parser digest ./my-skill/ # compact summary for the LLM
dotmd-parser affects ./my-skill/ shared/role.md
analyze — AI-assisted dependency detection
Use when a folder of markdown has no explicit directives yet. analyze
asks Claude to infer dependencies and can apply the result in one step:
export ANTHROPIC_API_KEY=... # or put it in ./.env
dotmd-parser analyze ./docs/ # runs the proposal (uses API)
dotmd-parser analyze ./docs/ --apply # inject @include, write deps.yml
dotmd-parser analyze ./docs/ --json # machine-readable
dotmd-parser analyze ./docs/ --ext md --ext pdf --ext docx
No-API-key workflows
# Estimate cost before spending any API credit
dotmd-parser analyze ./docs/ --dry-run
# Delegate the analysis to Claude Code itself — no API key needed
dotmd-parser analyze ./docs/ --plan > plan.md
# 1. Claude Code reads plan.md and executes the task locally
# 2. It writes the result to analysis.json
# 3. Apply it:
dotmd-parser analyze ./docs/ --apply-from analysis.json
- Text files (
.md,.txt, etc.) get@includelines prepended. - Binary sources (
.pdf,.docx) are recorded indeps.yml— they can't be edited in-place, so the parser reads them from there. - PDF / DOCX support is optional:
pip install pdfplumber python-docx. - Re-run with
--applyover time as the repo grows — existing directives are preserved.
The bundled prompt lives at
src/dotmd_parser/templates/prompts/analyze-dependencies.md and is shipped
inside the wheel; no network access is needed except for the Claude API
call itself.
Programmatic use
from dotmd_parser import analyze_dependencies, apply_analysis
proposal = analyze_dependencies("./docs/")
print(proposal["edges"])
apply_analysis("./docs/", proposal)
For offline tests, pass a caller=... kwarg that returns a stub JSON string.
Claude Code Skill integration
A ready-to-use Claude Code Skill is bundled with the package at
src/dotmd_parser/templates/SKILL.md. Three install paths:
# via pip — installs the CLI and drops SKILL.md into the current project
pip install dotmd-parser
dotmd-parser init .
# via Release archive — no pip required
mkdir -p .claude/skills
curl -L https://github.com/dotmd-projects/dotmd-parser/releases/latest/download/skill.tar.gz \
| tar -xz -C .claude/skills/
# manual
cp src/dotmd_parser/templates/SKILL.md \
/path/to/project/.claude/skills/dotmd-parser/
Once installed, Claude will consult the skill whenever it encounters
SKILL.md, deps.yml, a .claude/skills/ tree, or is asked about
dependencies of a markdown file.
Why this saves tokens. Without the skill, Claude typically grep -rs
for @include/@ref and cats every referenced file to trace a graph.
With the skill it reads .claude/dotmd-index.json (compact, relative paths,
first-paragraph descriptions) or the digest output once, then queries
affects / deps by name — never touching the raw markdown until an edit
is actually needed.
Auto-refresh via Hook (optional)
Add to ~/.claude/settings.json to keep .claude/dotmd-index.json fresh
after every markdown edit:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"command": "dotmd-parser index \"$CLAUDE_PROJECT_DIR\" >/dev/null 2>&1 || true"
}
]
}
}
The command is idempotent (SHA-256 cache) and exits fast when nothing changed.
Development
git clone https://github.com/dotmd-projects/dotmd-parser.git
cd dotmd-parser
pip install -e .
pip install pytest
pytest tests/ -v
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dotmd_parser-0.5.0.tar.gz.
File metadata
- Download URL: dotmd_parser-0.5.0.tar.gz
- Upload date:
- Size: 53.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00316fa1718a2f5fdda6a52cd578dcd8ea9b41a70acd66d24cd13be23fe8cac3
|
|
| MD5 |
1c012364bbd51c8652538872a29c3d69
|
|
| BLAKE2b-256 |
94925d301dca5f16080b2cf7c083034017b38075020dd14e0245936fb699b6b1
|
File details
Details for the file dotmd_parser-0.5.0-py3-none-any.whl.
File metadata
- Download URL: dotmd_parser-0.5.0-py3-none-any.whl
- Upload date:
- Size: 39.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ef6dd72847c5330b0f116cb8162eaa721cdd4428420b5db15a8e94f8d2b8d633
|
|
| MD5 |
9a1eb0261009a1777af3a44b796b1ae1
|
|
| BLAKE2b-256 |
961357c85c37a1ddea5717c4b499d8af5488ee0cea47e39a7bdc9d1d1c29d7be
|