dotmd-parser 0.2.1__tar.gz → 0.6.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. dotmd_parser-0.6.2/PKG-INFO +424 -0
  2. dotmd_parser-0.6.2/README.md +387 -0
  3. {dotmd_parser-0.2.1 → dotmd_parser-0.6.2}/pyproject.toml +17 -2
  4. dotmd_parser-0.6.2/src/dotmd_parser/__init__.py +131 -0
  5. dotmd_parser-0.6.2/src/dotmd_parser/analyze.py +676 -0
  6. dotmd_parser-0.6.2/src/dotmd_parser/cli.py +585 -0
  7. dotmd_parser-0.6.2/src/dotmd_parser/digest.py +193 -0
  8. dotmd_parser-0.6.2/src/dotmd_parser/index.py +300 -0
  9. dotmd_parser-0.6.2/src/dotmd_parser/index_md.py +716 -0
  10. dotmd_parser-0.6.2/src/dotmd_parser/inventory.py +302 -0
  11. dotmd_parser-0.6.2/src/dotmd_parser/openrag.py +150 -0
  12. {dotmd_parser-0.2.1 → dotmd_parser-0.6.2}/src/dotmd_parser/parser.py +108 -39
  13. dotmd_parser-0.6.2/src/dotmd_parser/templates/SKILL.md +212 -0
  14. dotmd_parser-0.6.2/src/dotmd_parser/templates/__init__.py +1 -0
  15. dotmd_parser-0.6.2/src/dotmd_parser/templates/dotmd_index/SKILL.md +254 -0
  16. dotmd_parser-0.6.2/src/dotmd_parser/templates/dotmd_index/__init__.py +0 -0
  17. dotmd_parser-0.6.2/src/dotmd_parser/templates/prompts/__init__.py +1 -0
  18. dotmd_parser-0.6.2/src/dotmd_parser/templates/prompts/analyze-dependencies.md +42 -0
  19. dotmd_parser-0.6.2/src/dotmd_parser.egg-info/PKG-INFO +424 -0
  20. dotmd_parser-0.6.2/src/dotmd_parser.egg-info/SOURCES.txt +37 -0
  21. dotmd_parser-0.6.2/src/dotmd_parser.egg-info/requires.txt +14 -0
  22. dotmd_parser-0.6.2/tests/test_aggregate.py +149 -0
  23. dotmd_parser-0.6.2/tests/test_analyze.py +254 -0
  24. dotmd_parser-0.6.2/tests/test_cli_dotmd_index.py +133 -0
  25. dotmd_parser-0.6.2/tests/test_cost_estimate.py +148 -0
  26. dotmd_parser-0.6.2/tests/test_empty_warnings.py +116 -0
  27. dotmd_parser-0.6.2/tests/test_host_agent_plan.py +174 -0
  28. dotmd_parser-0.6.2/tests/test_index_md.py +233 -0
  29. dotmd_parser-0.6.2/tests/test_index_scope.py +195 -0
  30. dotmd_parser-0.6.2/tests/test_inventory.py +195 -0
  31. dotmd_parser-0.6.2/tests/test_openrag_push.py +199 -0
  32. {dotmd_parser-0.2.1 → dotmd_parser-0.6.2}/tests/test_parser.py +98 -97
  33. dotmd_parser-0.6.2/tests/test_skill_integration.py +315 -0
  34. dotmd_parser-0.6.2/tests/test_token_savings.py +263 -0
  35. dotmd_parser-0.2.1/PKG-INFO +0 -195
  36. dotmd_parser-0.2.1/README.md +0 -168
  37. dotmd_parser-0.2.1/src/dotmd_parser/__init__.py +0 -33
  38. dotmd_parser-0.2.1/src/dotmd_parser.egg-info/PKG-INFO +0 -195
  39. dotmd_parser-0.2.1/src/dotmd_parser.egg-info/SOURCES.txt +0 -11
  40. {dotmd_parser-0.2.1 → dotmd_parser-0.6.2}/LICENSE +0 -0
  41. {dotmd_parser-0.2.1 → dotmd_parser-0.6.2}/setup.cfg +0 -0
  42. {dotmd_parser-0.2.1 → dotmd_parser-0.6.2}/src/dotmd_parser.egg-info/dependency_links.txt +0 -0
  43. {dotmd_parser-0.2.1 → dotmd_parser-0.6.2}/src/dotmd_parser.egg-info/entry_points.txt +0 -0
  44. {dotmd_parser-0.2.1 → dotmd_parser-0.6.2}/src/dotmd_parser.egg-info/top_level.txt +0 -0
@@ -0,0 +1,424 @@
1
+ Metadata-Version: 2.4
2
+ Name: dotmd-parser
3
+ Version: 0.6.2
4
+ Summary: Dependency graph parser, single-file folder index (dotmd-index.md), and AI analyzer for .md skill files — parse @include/@delegate/@ref directives, build graphs, resolve templates, generate RAG-friendly overviews, and ingest into OpenRAG
5
+ Author: dotmd-projects
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/dotmd-projects/dotmd-parser
8
+ Project-URL: Repository, https://github.com/dotmd-projects/dotmd-parser
9
+ Project-URL: Issues, https://github.com/dotmd-projects/dotmd-parser/issues
10
+ Keywords: claude-code,ai-agent,skill-management,prompt-engineering,dependency-graph,SKILL.md,markdown,parser,dotmd,llm
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Software Development :: Code Generators
21
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: openrag
27
+ Requires-Dist: openrag-sdk>=0.3.1; extra == "openrag"
28
+ Provides-Extra: pdf
29
+ Requires-Dist: pdfplumber>=0.10; extra == "pdf"
30
+ Provides-Extra: docx
31
+ Requires-Dist: python-docx>=1.0; extra == "docx"
32
+ Provides-Extra: all
33
+ Requires-Dist: openrag-sdk>=0.3.1; extra == "all"
34
+ Requires-Dist: pdfplumber>=0.10; extra == "all"
35
+ Requires-Dist: python-docx>=1.0; extra == "all"
36
+ Dynamic: license-file
37
+
38
+ # dotmd-parser
39
+
40
+ [![PyPI version](https://img.shields.io/pypi/v/dotmd-parser)](https://pypi.org/project/dotmd-parser/)
41
+ [![Python](https://img.shields.io/pypi/pyversions/dotmd-parser)](https://pypi.org/project/dotmd-parser/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
43
+
44
+ ![dotmd-parser demo](assets/demo.gif)
45
+
46
+ > [日本語版 README はこちら](README.ja.md)
47
+
48
+ Dependency graph parser for `.md` skill files — built for AI agent prompt engineering with [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and similar tools.
49
+
50
+ ## Why dotmd-parser?
51
+
52
+ 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:
53
+
54
+ - *"Which files break if I edit `shared/role.md`?"*
55
+ - *"Is there a circular reference hiding in my skill tree?"*
56
+ - *"What `{{variables}}` are still unresolved after expansion?"*
57
+
58
+ **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.
59
+
60
+ ## Token savings — measured
61
+
62
+ The single biggest reason to reach for `dotmd-parser` in an agent loop is
63
+ that it lets Claude understand a folder **without reading every file**.
64
+ The numbers below are produced by `tests/test_token_savings.py` (run
65
+ with `DOTMD_TOKEN_REPORT=1 pytest -s`) using `tiktoken`'s `cl100k_base`
66
+ encoding (a close proxy for Claude's tokenizer family):
67
+
68
+ | Folder profile | Files | Naive read of every `.md` | `dotmd-index.md` | `digest` |
69
+ |---|---:|---:|---:|---:|
70
+ | Small skill (each file ~2 KB) | 4 | 1,610 tokens | **605 (0.38×)** | 174 (0.11×) |
71
+ | Medium docs (each file ~2 KB) | 31 | 15,855 tokens | **2,837 (0.18× → 5.6× cheaper)** | 1,285 (0.08×) |
72
+ | Large docs (each file ~2 KB) | 111 | 58,171 tokens | **9,535 (0.16× → 6.3× cheaper)** | 4,606 (0.08×) |
73
+
74
+ **Takeaway**: at ~30 files dotmd-parser already cuts Claude's reading
75
+ cost by **~5.6×**, and the savings *grow* with folder size. At 100+
76
+ files the same context window now fits **6× more conversation**, or
77
+ serves the same prompt at **1/6 the input-token spend**.
78
+
79
+ The persistent `dotmd-index.md` artifact pays a fixed frontmatter
80
+ overhead, so for *very small* folders (a handful of files of a few
81
+ hundred bytes) the naive read can still win. The `digest` output is
82
+ even more compact (~12× cheaper at scale) but isn't persisted on disk —
83
+ use `dotmd-index.md` for stable navigation, `digest` for one-shot
84
+ summaries.
85
+
86
+ The break-even is around **4 files × 1 KB each**: above that,
87
+ `dotmd-index.md` is always cheaper than reading the folder by hand.
88
+
89
+ ## Comparison
90
+
91
+ | Capability | Manual / grep | dotmd-parser |
92
+ |---|---|---|
93
+ | Find `@include` / `@delegate` / `@ref` references | `grep -r "@include"` — flat list, no context | Structured graph with node types and edge metadata |
94
+ | Detect circular references | Hope you notice before the agent loops | Automatic detection with full cycle path in warnings |
95
+ | Reverse dependency ("what breaks?") | Manually trace every file | `dependents_of(graph, "shared/role.md")` — one call |
96
+ | Expand `@include` to final text | Copy-paste by hand | `resolve("SKILL.md", variables={...})` — recursive expansion |
97
+ | Find unresolved `{{variables}}` | `grep "{{" *.md` — noisy, no dedup | Deduplicated list per node and after expansion |
98
+ | Missing file detection | Runtime failure | Warnings at parse time with exact paths |
99
+ | Detect **implicit** deps (no directives yet) | Read every file, draw by hand | `analyze` subcommand asks Claude, emits `@include` / `deps.yml` |
100
+ | Feed Claude Code with compact context | Read 20 files × 5 KB each | `digest` outputs one ~2 KB summary; `.claude/dotmd-index.json` cache |
101
+
102
+ ## Installation
103
+
104
+ ```bash
105
+ pip install dotmd-parser
106
+ ```
107
+
108
+ ## Quick Start
109
+
110
+ ```python
111
+ from dotmd_parser import build_graph, resolve, dependents_of, summary
112
+ ```
113
+
114
+ ### build_graph — Build a dependency graph
115
+
116
+ ```python
117
+ graph = build_graph("./my-skill/")
118
+ # or from a specific file
119
+ graph = build_graph("./my-skill/SKILL.md")
120
+ ```
121
+
122
+ Returns:
123
+
124
+ ```json
125
+ {
126
+ "nodes": [
127
+ {"id": "/abs/path/to/SKILL.md", "type": "skill", "missing": false, "placeholders": []}
128
+ ],
129
+ "edges": [
130
+ {"from": "...", "to": "...", "type": "include", "parallel": false}
131
+ ],
132
+ "warnings": []
133
+ }
134
+ ```
135
+
136
+ **Custom node type mapping:**
137
+
138
+ By default, node types are inferred from path keywords (`agent`, `shared`, `prompt`, `reference`, `asset`, `template`). You can override this with the `type_map` parameter:
139
+
140
+ ```python
141
+ graph = build_graph("./my-skill/", type_map=[
142
+ ("helper", "utility"),
143
+ ("core", "foundation"),
144
+ ])
145
+ ```
146
+
147
+ **deps.yml support:**
148
+
149
+ If a `deps.yml` file exists in the root directory, its dependencies are automatically merged into the graph:
150
+
151
+ ```yaml
152
+ - path: agents/planner.md
153
+ includes:
154
+ - shared/role.md
155
+ - shared/tools.md
156
+ ```
157
+
158
+ ### resolve — Expand @include directives
159
+
160
+ Recursively expands `@include` directives into final text. `@delegate` and `@ref` lines are left as-is.
161
+
162
+ ```python
163
+ result = resolve("./prompts/main.md", variables={"name": "Alice"})
164
+
165
+ print(result["content"]) # Fully expanded text
166
+ print(result["placeholders"]) # Unresolved {{variable}} names
167
+ print(result["warnings"]) # Circular refs, missing files, etc.
168
+ ```
169
+
170
+ ### dependents_of — Reverse dependency query
171
+
172
+ ```python
173
+ # "If I change shared/role.md, what else breaks?"
174
+ affected = dependents_of(graph, "/abs/path/to/shared/role.md")
175
+ ```
176
+
177
+ ### summary — Human-readable overview
178
+
179
+ ```python
180
+ print(summary(graph))
181
+ # Nodes: 5 (agent:1, prompt:1, shared:2, skill:1)
182
+ # Edges: 4 (include:2, ref:1, read-ref:1)
183
+ # Warnings: 0
184
+ # Placeholders: name, role
185
+ ```
186
+
187
+ ## Supported Directives
188
+
189
+ | Directive | Edge type | Expanded by `resolve()`? | Description |
190
+ |---|---|---|---|
191
+ | `@include path/to/file.md` | `include` | Yes | Inline expansion — file content is inserted at this position |
192
+ | `@delegate path/to/agent.md` | `delegate` | No | Agent delegation — recorded in graph, left as-is in output |
193
+ | `@delegate path/to/agent.md --parallel` | `delegate` | No | Parallel delegation with `--parallel` flag |
194
+ | `@ref path/to/file.md` | `ref` | No | Runtime reference — recorded in graph, left as-is in output |
195
+ | `` Read `path/to/file.md` `` | `read-ref` | No | Legacy runtime reference (same as `@ref`, kept for backward compatibility) |
196
+
197
+ ## Utility Functions
198
+
199
+ Lower-level parsing functions are also exported for custom use:
200
+
201
+ ```python
202
+ from dotmd_parser import parse_directives, parse_read_refs, parse_placeholders, parse_deps_yml
203
+ ```
204
+
205
+ | Function | Description |
206
+ |---|---|
207
+ | `parse_directives(content)` | Extract `@include` / `@delegate` / `@ref` directives from text |
208
+ | `parse_read_refs(content)` | Extract legacy `Read`/`See`/list-style `.md` references (deduplicated) |
209
+ | `parse_placeholders(content)` | Extract `{{variable}}` placeholder names (deduplicated) |
210
+ | `parse_deps_yml(content)` | Parse `deps.yml` text into `{path: [includes]}` dict (no PyYAML required) |
211
+
212
+ ## CLI
213
+
214
+ `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.
215
+
216
+ | Command | Purpose |
217
+ |---|---|
218
+ | `dotmd-parser inventory <path>` | **API-free**: extension counts, markdown/binary ratio, largest files |
219
+ | `dotmd-parser dotmd-index <path>` | **API-free**: generate `<path>/dotmd-index.md` (single-file folder overview) |
220
+ | `dotmd-parser dotmd-index <path> --aggregate` | Roll up nested `dotmd-index.md` files into the parent (Sub-Indexes section) |
221
+ | `dotmd-parser dotmd-index <path> --push-openrag` | After writing, ingest into OpenRAG (`pip install dotmd-parser[openrag]`) |
222
+ | `dotmd-parser index <path>` | Build and save `.claude/dotmd-index.json` |
223
+ | `dotmd-parser index <path> --scope <subdir>` | Incrementally re-index one subfolder, merge into the existing index |
224
+ | `dotmd-parser check <path>` | Exit non-zero on cycles / missing refs (CI-friendly) |
225
+ | `dotmd-parser affects <path> <file>` | Reverse dependencies of `<file>` |
226
+ | `dotmd-parser deps <path> <file>` | Direct dependencies of `<file>` |
227
+ | `dotmd-parser digest <path>` | Token-efficient text summary for LLM context |
228
+ | `dotmd-parser tree <path>` | ASCII dependency tree |
229
+ | `dotmd-parser resolve <file> [--var k=v]` | Recursively expand `@include` |
230
+ | `dotmd-parser analyze <path>` | AI dependency detection (requires `ANTHROPIC_API_KEY`) |
231
+ | `dotmd-parser analyze <path> --dry-run` | **API-free**: estimate tokens and USD cost |
232
+ | `dotmd-parser analyze <path> --plan` | **API-free**: emit a host-agent prompt pack for Claude Code to execute |
233
+ | `dotmd-parser analyze <path> --apply-from <json>` | Apply a pre-computed analysis JSON |
234
+ | `dotmd-parser init [--skill dotmd-index]` | Install a bundled SKILL.md into `.claude/skills/<id>/` |
235
+ | `dotmd-parser show <path>` | Summary + full JSON graph (legacy default) |
236
+
237
+ ```bash
238
+ # Typical Claude Code workflow
239
+ dotmd-parser inventory ./my-skill/ # start here if you've never seen the folder
240
+ dotmd-parser dotmd-index ./my-skill/ # write ./my-skill/dotmd-index.md (Claude reads ONLY this file)
241
+ dotmd-parser index ./my-skill/ # one-off; cached until files change
242
+ dotmd-parser digest ./my-skill/ # compact summary for the LLM
243
+ dotmd-parser affects ./my-skill/ shared/role.md
244
+ ```
245
+
246
+ ## `dotmd-index.md` — folder overview in a single file
247
+
248
+ `dotmd-parser dotmd-index <path>` writes `<path>/dotmd-index.md`, a
249
+ self-contained Markdown file that combines `inventory()` and
250
+ `build_index()` into one artifact Claude can read **instead of**
251
+ grep-scanning every file in the folder.
252
+
253
+ The file contains:
254
+
255
+ - YAML frontmatter (`schema`, `content_hash`, `stats`, RAG `chunks[]`)
256
+ - `## Summary` (file counts, total size, health)
257
+ - `## Folder Map` (depth-limited ASCII tree)
258
+ - `## Files` (markdown: title + desc + deps; other: kind + size)
259
+ - `## Dependency Tree` (`@include` / `@delegate` / `@ref` graph as ASCII)
260
+ - `## Placeholders` (unresolved `{{...}}` variables)
261
+ - `<!-- chunk:id -->` HTML markers so any RAG tool can split deterministically
262
+
263
+ Re-running on an unchanged folder writes nothing (`content_hash` matches).
264
+ The command refuses to overwrite a hand-written `dotmd-index.md` unless
265
+ `--force` is passed.
266
+
267
+ ### Aggregating across nested folders
268
+
269
+ Run with `--aggregate` to roll up descendant artifacts:
270
+
271
+ ```bash
272
+ dotmd-parser dotmd-index ./project/ --aggregate
273
+ # project/dotmd-index.md now references project/docs/dotmd-index.md and
274
+ # project/src/dotmd-index.md without duplicating their file listings.
275
+ ```
276
+
277
+ Each child is discovered, its frontmatter is read, and a one-line
278
+ summary (file count, edges, health) appears under `## Sub-Indexes`.
279
+ The `aggregates[]` frontmatter array records each child's `content_hash`
280
+ so staleness is easy to detect. User-authored `dotmd-index.md` files
281
+ that lack `generated_by: dotmd-parser` are silently skipped.
282
+
283
+ This is a **reference**, not a merge — Claude reads the parent to learn
284
+ which subfolders exist, then drills into the relevant child file. The
285
+ parent stays token-efficient even with deeply nested trees.
286
+
287
+ ### OpenRAG integration
288
+
289
+ [OpenRAG](https://github.com/langflow-ai/openrag) is a self-hosted RAG
290
+ platform built on Langflow + Docling + OpenSearch. dotmd-parser can ship
291
+ the artifact straight into it:
292
+
293
+ ```bash
294
+ pip install dotmd-parser[openrag] # adds openrag-sdk
295
+ export OPENRAG_URL=http://localhost:3000
296
+ export OPENRAG_API_KEY=... # if your instance requires auth
297
+
298
+ dotmd-parser dotmd-index ./docs/ --push-openrag
299
+ # 1. Writes ./docs/dotmd-index.md
300
+ # 2. Calls OpenRAGClient.documents.ingest(file_path=...)
301
+ # 3. Records {document_id, base_url, pushed_at} in exports.openrag
302
+ ```
303
+
304
+ `dotmd-index.md` is the **map** (one-shot overview); OpenRAG is the
305
+ **search index** (full-content semantic retrieval). Register OpenRAG's
306
+ MCP server with Claude Code to use both surfaces from the same client.
307
+
308
+ ## `analyze` — AI-assisted dependency detection
309
+
310
+ Use when a folder of markdown has **no explicit directives yet**. `analyze`
311
+ asks Claude to infer dependencies and can apply the result in one step:
312
+
313
+ ```bash
314
+ export ANTHROPIC_API_KEY=... # or put it in ./.env
315
+
316
+ dotmd-parser analyze ./docs/ # runs the proposal (uses API)
317
+ dotmd-parser analyze ./docs/ --apply # inject @include, write deps.yml
318
+ dotmd-parser analyze ./docs/ --json # machine-readable
319
+ dotmd-parser analyze ./docs/ --ext md --ext pdf --ext docx
320
+ ```
321
+
322
+ ### No-API-key workflows
323
+
324
+ ```bash
325
+ # Estimate cost before spending any API credit
326
+ dotmd-parser analyze ./docs/ --dry-run
327
+
328
+ # Delegate the analysis to Claude Code itself — no API key needed
329
+ dotmd-parser analyze ./docs/ --plan > plan.md
330
+ # 1. Claude Code reads plan.md and executes the task locally
331
+ # 2. It writes the result to analysis.json
332
+ # 3. Apply it:
333
+ dotmd-parser analyze ./docs/ --apply-from analysis.json
334
+ ```
335
+
336
+ - Text files (`.md`, `.txt`, etc.) get `@include` lines prepended.
337
+ - Binary sources (`.pdf`, `.docx`) are recorded in `deps.yml` — they can't
338
+ be edited in-place, so the parser reads them from there.
339
+ - PDF / DOCX support is optional: `pip install pdfplumber python-docx`.
340
+ - Re-run with `--apply` over time as the repo grows — existing directives
341
+ are preserved.
342
+
343
+ The bundled prompt lives at
344
+ `src/dotmd_parser/templates/prompts/analyze-dependencies.md` and is shipped
345
+ inside the wheel; no network access is needed except for the Claude API
346
+ call itself.
347
+
348
+ ### Programmatic use
349
+
350
+ ```python
351
+ from dotmd_parser import analyze_dependencies, apply_analysis
352
+
353
+ proposal = analyze_dependencies("./docs/")
354
+ print(proposal["edges"])
355
+ apply_analysis("./docs/", proposal)
356
+ ```
357
+
358
+ For offline tests, pass a `caller=...` kwarg that returns a stub JSON string.
359
+
360
+ ## Claude Code Skill integration
361
+
362
+ A ready-to-use Claude Code Skill is bundled with the package at
363
+ `src/dotmd_parser/templates/SKILL.md`. Three install paths:
364
+
365
+ ```bash
366
+ # via pip — installs the CLI and drops SKILL.md into the current project
367
+ pip install dotmd-parser
368
+ dotmd-parser init .
369
+
370
+ # via Release archive — no pip required
371
+ mkdir -p .claude/skills
372
+ curl -L https://github.com/dotmd-projects/dotmd-parser/releases/latest/download/skill.tar.gz \
373
+ | tar -xz -C .claude/skills/
374
+
375
+ # manual
376
+ cp src/dotmd_parser/templates/SKILL.md \
377
+ /path/to/project/.claude/skills/dotmd-parser/
378
+ ```
379
+
380
+ Once installed, Claude will consult the skill whenever it encounters
381
+ `SKILL.md`, `deps.yml`, a `.claude/skills/` tree, or is asked about
382
+ dependencies of a markdown file.
383
+
384
+ **Why this saves tokens.** Without the skill, Claude typically `grep -r`s
385
+ for `@include`/`@ref` and `cat`s every referenced file to trace a graph.
386
+ With the skill it reads `.claude/dotmd-index.json` (compact, relative paths,
387
+ first-paragraph descriptions) or the `digest` output once, then queries
388
+ `affects` / `deps` by name — never touching the raw markdown until an edit
389
+ is actually needed.
390
+
391
+ ### Auto-refresh via Hook (optional)
392
+
393
+ Add to `~/.claude/settings.json` to keep `.claude/dotmd-index.json` fresh
394
+ after every markdown edit:
395
+
396
+ ```json
397
+ {
398
+ "hooks": {
399
+ "PostToolUse": [
400
+ {
401
+ "matcher": "Edit|Write",
402
+ "command": "dotmd-parser index \"$CLAUDE_PROJECT_DIR\" >/dev/null 2>&1 || true"
403
+ }
404
+ ]
405
+ }
406
+ }
407
+ ```
408
+
409
+ The command is idempotent (SHA-256 cache) and exits fast when nothing
410
+ changed.
411
+
412
+ ## Development
413
+
414
+ ```bash
415
+ git clone https://github.com/dotmd-projects/dotmd-parser.git
416
+ cd dotmd-parser
417
+ pip install -e .
418
+ pip install pytest
419
+ pytest tests/ -v
420
+ ```
421
+
422
+ ## License
423
+
424
+ MIT