Agent skill

Writing Md2hd Maps

by evan-steinhilb in evan-steinhilb/md2hd

A skill your agent uses when authoring or editing markdown that will be rendered by md2hd as a graph or mind map — writing node frontmatter, linking nodes with rel: or [[wikilinks]], setting up the…

MITAuto-check passedDocuments & Office

Install Writing Md2hd Maps

skills CLI
$ npx skills add evan-steinhilb/md2hd --skill writing-md2hd-maps -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install evan-steinhilb/md2hd writing-md2hd-maps --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/evan-steinhilb/md2hd.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/writing-md2hd-maps .claude/skills/writing-md2hd-maps && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
writing-md2hd-maps
GitHub stars
137
Token cost
~3.8k tokens
SKILL.md length
1,829 words
Files
1
Skills in repo
1
Repo updated
First seen
Licence
MIT

At a glance

A skill your agent uses when authoring or editing markdown that will be rendered by md2hd as a graph or mind map — writing node frontmatter, linking nodes with rel: or [[wikilinks]], setting up the…

  • Works in 5 steps: every edge is turned to face one way → edges collapse by meaning → the second voice is recovered → …
  • Editing markdown that will be rendered by md2hd as a graph
  • SKILL.md covers The shape of a file, Write it in this order, Node fields and The map block, plus 5 more sections
  • Calls npx

What it does

Writing Md2hd Maps is an agent skill from evan-steinhilb/md2hd. Use when authoring or editing markdown that will be rendered by md2hd as a graph or mind map — writing node frontmatter, linking nodes with rel: or [[wikilinks]], setting up the type:map configuration block, converting a folder of existing notes into a map, or diagnosing a map that parses cleanly but draws the wrong thing (nodes missing, arrows reversed, dashed placeholder nodes, unlabelled grey lines).

Its SKILL.md is about 3.8k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Documents & Office, covering Markdown. The repository describes itself as: Markdown, mapped - the MD2HD CLI. Pull the repo, tell your agent to create an MD2HD map, visually comprehend complex topics and relationships. The licence is MIT.

When your agent uses it

  • Editing markdown that will be rendered by md2hd as a graph
  • Mind map — writing node frontmatter
  • Linking nodes with rel:
  • Setting up the type:map configuration block

Example prompts

  • “/writing-md2hd-maps”

Workflow steps

5 steps, taken from the step headings in SKILL.md.

  1. every edge is turned to face one way
  2. edges collapse by meaning
  3. the second voice is recovered
  4. mirrors fold
  5. deferring mentions are dropped

What it can do on your machine

Read from SKILL.md and the folder at commit 2c0921c. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • npx

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md. Its commands use npx, which can reach the network depending on how they are called.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Writing Md2hd Maps loads about 3.8k tokens when it runs. Until then it costs about 106 tokens; SKILL.md has 1,829 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~106
When it runs · the whole SKILL.md, loaded when a task matches
~3.8k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from evan-steinhilb/md2hd at commit 2c0921c, republished under its MIT licence (© evan-steinhilb). 1,829 words, ~3,816 tokens.

Download SKILL.mdSave it as .claude/skills/writing-md2hd-maps/SKILL.md (or your agent's skills folder).
name
writing-md2hd-maps
description
Use when authoring or editing markdown that will be rendered by md2hd as a graph or mind map — writing node frontmatter, linking nodes with rel: or [[wikilinks]], setting up the type:map configuration block, converting a folder of existing notes into a map, or diagnosing a map that parses cleanly but draws the wrong thing (nodes missing, arrows reversed, dashed placeholder nodes, unlabelled grey lines).

Writing markdown for md2hd

md2hd turns a folder of markdown into a graph. Every YAML frontmatter block is a node, rel: entries and [[wikilinks]] are edges, the prose under a block is that node's notes, and one block with type: map configures the whole map.

The governing fact: the parser never rejects anything. Malformed YAML, a misspelled setting, a block that never opened — all of it parses to something and draws something. There is no error state. Every mistake below is silent, so the job is not "does it parse" but "does it draw what I meant".

Everything in this document was verified against the parser (src/lib/parse.ts in the md2hd repo). Where a friendlier user guide disagrees, this wins.


The shape of a file

One file may hold the whole map. A --- line opens a new block, and the body runs until the next line that opens one.

markdown
---
type: map
title: Partnerships
---

---
id: riverside-council
type: org
title: Riverside City Council
---

Prose here is this node's notes.

---
id: dana-whitfield
type: person
title: Dana Whitfield
---

And this is Dana's.
  • Blank lines around blocks are optional. Tight --- runs parse identically.
  • Prose before the first block is dropped. It belongs to no node.
  • A --- followed by prose stays a horizontal rule and remains inside the current node's body. Only a --- whose next non-blank line looks like a YAML key opens a block, which is why existing notes full of rules still work.
  • *** and ___ never open a block.
The rule that decides it

The line after --- must match /^[A-Za-z_][\w .-]*\s*:(\s|$)/.

Line after ---Opens a block?Why
id: acmeyesletter, colon, space
id:acmenoneeds whitespace or end-of-line after the colon
rel:yescolon at end of line is fine
_id: acmeyesleading underscore allowed
2026: thingnomust start [A-Za-z_]
my key.v: xyesspaces, dots, hyphens legal inside a key

A block that fails to open is not an error — the --- becomes a rule and the entire node silently disappears from the map. id:acme with no space is the single easiest way to lose a node.


Write it in this order

1. The map block. Exactly one, and it is configuration — it never draws as a node and never links to anything.

markdown
---
type: map
title: Partnerships
layout: LR
types:
  org: { label: Organization, color: "#4A9BFF" }
  person: "#3FC8D4"
inverse:
  works_at: employs
symmetric: [knows]
---

2. Nodes with an explicit id and title. Both have fallbacks and both fallbacks surprise people (see below). Two extra lines removes the whole class.

3. Name every relationship. Use rel: for structure. A bare [[wikilink]] draws an unlabelled grey mentions line — use it only when you mean "this came up here". A map full of them says nothing.

4. Declare inverse: and symmetric: deliberately. Both are global per label, not per node pair. See the normalisation section — this is the most common way to get a graph that is subtly wrong.

5. Read back every link as drawn, not just "it parsed". The normalisation pass rewrites directions and merges edges before anything appears.


Node fields

Every key is optional; a block with empty frontmatter still becomes a node.

FieldBehaviour
typeCategory, freely invented. Defaults to note. type: map is reserved for config.
idSlugged. Falls back to slug(title), then slug("<file>-<n>").
title / nameCard name. Falls back to the first heading anywhere in the body, then Untitled.
subtitleOne line under the title.
descriptionOpening paragraph of the detail view.
weightHow loudly the card draws. Word or 1–5.
tagsChips. Nested arrays are flattened.
rel / rels / linksEdges. Map or list.
any other keyA wikilink value becomes an edge; anything else becomes a metadata row.

title falling back to any heading — not just a leading one — means a node whose body starts with prose and has ## Open questions halfway down will be named "Open questions". Set title: on anything containing headings.

Two nodes with neither title: nor a heading both become Untitled and both slug to the same id.

Weight

Absent or empty → 3. A number (or numeric string) is rounded and clamped to 1–5. Otherwise a case-insensitive word lookup, and any unrecognised word silently becomes 3.

ValueWeight
lead critical primary key5
major high4
normal medium default3
minor low2
faint background trivial1
7→5, 0→1, 2.6→3, "4"→4clamped and rounded
urgent, blocker, anything else3

Weight affects drawing only, never the graph.

ids and slugging

slug() runs on id, on the title fallback, and on both ends of every link — which is why [[Acme Corp]] finds id: acme-corp.

trim → lowercase → delete everything except [A-Za-z0-9_ -] → collapse [\s_]+ to "-" → trim "-"
InputSlugNote
my_nodemy-nodeunderscores become hyphens
Acme Corp.acme-corppunctuation dropped
Foo—Barfoobarem-dash dropped with no separator
Ünïcode Namencode-namenon-ASCII letters are deleted, not transliterated
2026-07-142026-07-14digits and hyphens survive

So id: my_node, [[my-node]] and [[my_node]] are all the same node. Give accented or non-Latin titles an explicit ASCII id:.

Duplicate ids do not merge, despite a warning that says "later copy merged". Both nodes draw; only the second is reachable by link. Treat it as an error.


The map block

KeyBehaviour
titleThe map's name.
layoutExactly LR for left-to-right. Every other value — lr, RL, horizontal — silently gives TB.
typesPer type { label, color }, or a bare colour string as shorthand (label defaults to the key).
inverseworks_at: employs — key is the passive wording, value the active one.
symmetricRelations with no direction: [knows, met].

Those five keys are the only ones read. Anything else is never looked at. direction:, colors:, rankdir: and palette: are all valid YAML that does absolutely nothing — this is the most common authoring mistake, because the names are so plausible.

Types you omit get a palette colour in encounter order, so a map needs no colours at all to read clearly.

Two map blocks do not merge. The last one replaces the config wholesale, so a layout: LR in the first is lost if the second omits it. Keep exactly one.


1. rel: / rels: / links:

yaml
rel:
  employs: [dana, marcus]      # relation -> id, or list of ids
  runs: records-portal
rel:
  - employs: dana              # list of single-key maps also works
rel: [dana, marcus]            # list of bare scalars -> label is "links"

2. Any non-reserved frontmatter key whose value is a wikilink

yaml
works_at: "[[acme]]"           # edge labelled "works_at"
attendees: ["[[dana]]", "[[marcus]]"]

Reserved keys are skipped: id type title name subtitle description weight tags rel rels links layout types inverse symmetric. A wikilink inside description: makes no edge.

3. Inline fields in the body — /(?:^|\s)([A-Za-z][\w-]*)::\s*(.+)$/gm

markdown
owns:: [[depot]]                 label "owns"
mid sentence owns:: [[depot]]    also matches — preceded by a space
nope-owns:: [[d]]                matches; label is "nope-owns" (hyphens legal)
1bad:: [[d]]                     no match — must start with a letter

4. Bare wikilinks in the body → an untyped mentions edge, unless an inline field on the same node already claimed that target.

[[target|alias]] links to target; the alias only changes displayed text.

Self-links are dropped — a node with rel: { knows: <its own id> } produces no edge at all.


This pass rewrites what you wrote. It is where authored text stops matching the canvas, and where most "the graph is wrong but it parsed" bugs live.

Show full SKILL.md (759 more words)Show less
Step 1 — every edge is turned to face one way

Up to 3 rounds per edge. Each round:

  • if inverse[label] exists → relabel to that value and swap the ends;
  • else if the label matches /^(.+?)[\s_-]by$/ → strip the suffix and swap;
  • else stop.

So owned_by, owned-by and owned by all become owned pointing the other way. standby and nearby do not match — a separator is required.

inverse: is checked first each round, and the rules chain: with inverse: { x: owned_by }, a label x relabels to owned_by and swaps, then the _by rule fires on the result and swaps back — ending as owned in the original direction. Two flips cancel. Avoid declaring an inverse: value that itself ends in _by.

Step 2 — edges collapse by meaning

The identity is source > target > stem(label), where stem lowercases, turns any run of spaces/underscores/hyphens into a single space, and strips a trailing ed, s or d.

Consequences:

  • owns, owned and own between the same pair are one edge. The first spelling encountered is kept.
  • paired_with, paired-with and Paired With are the same relation.
  • Irregular verbs are not handled: knew and knows stay distinct.
Step 3 — the second voice is recovered

An edge carries label (as drawn) and reverse (the same fact from the far end), so one link reads employs from the organisation and works at from the person. reverse comes from whatever the far end actually wrote, or failing that from the declared inverse: counterpart.

inverse: is global per label, not per pair. Declaring inverse: { assigned_to: owns } gives every owns edge in the map the reverse voice "assigned to" — including rosa owns session-store, which then reads as a service assigned to a person. If a label means different things in different places, give those relations different names.

symmetric: matches the same way, so [paired_with], [paired with] and [paired-with] are interchangeable, and [knows] also matches a know label.

Step 4 — mirrors fold

If a→b and b→a share a stem they become one edge marked symmetric, drawn with no arrowhead and a chevron at each end. Anything in symmetric: is marked the same way.

Step 5 — deferring mentions are dropped

A mentions edge is discarded when any named relation already connects that unordered pair.

Link targets that were never defined become ghost nodes — dashed, type: unresolved. Nothing you reference vanishes, but a typo'd id becomes a ghost rather than an error.


Silent failures, ranked

Every one of these parses cleanly.

SymptomCause
A node is missing entirelykey:value with no space — the block never opened
Layout ignoredlayout: anything but exactly LR
Type colours ignoreda key the map block does not read (colors:, direction:)
Config half-applieda second type: map block replaced the first wholesale
Arrow points backwardsa _by/-by/ by suffix, or an inverse: pair, flipped it — by design
A relation reads oddly on unrelated nodesinverse: is global per label
Two relations became onesame stem between the same pair
A dashed node you never wrotetypo in a link target → ghost
Two identical cardsduplicate id: — they do not merge
Node named after a subheadingno title:; the fallback found a heading in the body
Several nodes named "Untitled"no title: and no heading; their ids collide
Unlabelled grey lines everywherebare [[wikilinks]] where a named relation was meant
Everything equally loudan unrecognised weight: word fell back to 3
A relationship you wrote is absentit pointed at itself

Verify before you ship

Inside the md2hd repo, run the real parser and read every link as drawn:

bash
npx vite-node .claude/skills/writing-md2hd-maps/check-map.ts -- path/to/map.md

Anywhere else, there is no validator — check by eye, in this order:

  1. Every block opened. Each --- you intended as a node is followed by a key with a space after its colon. This grep flags the killer:
    bash
    grep -nE '^[A-Za-z_][A-Za-z0-9 ._-]*:[^[:space:]]' map.md
  2. The map block uses only title layout types inverse symmetric.
  3. layout: is exactly LR if you wanted left-to-right.
  4. Exactly one type: map block.
  5. Every id: is unique, and every link target matches an id after slugging.
  6. Every weight: is a listed word or 1–5.
  7. Walk each inverse: and symmetric: entry and confirm the label means the same thing everywhere it is used.
  8. Count bare [[wikilinks]] — each is an unlabelled line. Convert the ones that deserve a name.

Worked example

markdown
---
type: map
title: Checkout Outage — 14 July
layout: LR
types:
  service: { label: Service, color: "#4A9BFF" }
  person: { label: Responder, color: "#3FC8D4" }
  event: { label: Timeline, color: "#EA8A62" }
  action: { label: Action Item, color: "#4FBE8B" }
inverse:
  assigned_to: owns_action
symmetric: [paired_with]
---

---
id: payments-api
type: service
title: Payments API
weight: lead
rel:
  depends_on: session-store
---

The blast radius. Every checkout path routes through it.

---
id: session-store
type: service
title: Session Store
subtitle: Redis · 3 replicas
---

---
id: rosa-imani
type: person
title: Rosa Imani
subtitle: On-call, platform
weight: major
rel:
  operates: session-store
  paired_with: theo-nakamura
---

Took the page at 14:06.

verified:: [[rollback-1447]]

---
id: theo-nakamura
type: person
title: Theo Nakamura
---

---
id: config-push-1402
type: event
title: Config push 14:02
rel:
  degraded: payments-api
---

---
id: rollback-1447
type: event
title: Rollback 14:47
rel:
  reverted: config-push-1402
  restored: payments-api
---

---
id: action-canary
type: action
title: Canary config pushes
rel:
  assigned_to: rosa-imani
  addresses: config-push-1402
---

---
id: action-alerts
type: action
title: Alert on session pool saturation
rel:
  assigned_to: theo-nakamura
  covers: session-store
---

Note what this does not do: it uses operates rather than owns for the service, because owns_action is declared as an inverse and would otherwise attach "assigned to" to a service. That is step 3 above, and it is the mistake worth watching for.

© evan-steinhilb, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in skills/writing-md2hd-maps of evan-steinhilb/md2hd.

Open the folder on GitHubat commit 2c0921c

Compare with similar skills

Writing Md2hd Maps next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Writing Md2hd Maps compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Writing Md2hd Maps this skillevan-steinhilb/md2hd137—~3.8kAutomated safety check: PassMIT
Markdown Article FormatterJimLiu/baoyu-skills27k6 repos~3.5kAutomated safety check: PassMIT
MarkitdownImCa0/just-laws78114 repos~3.2kAutomated safety check: NotesMIT
Obsidian MarkdownAtmosphere/atmosphere3.8k20 repos~1.3kAutomated safety check: PassApache-2.0
Crosspostingwasp-lang/wasp19k—~1.1kAutomated safety check: PassMIT
Gzh Designisjiamu/gzh-design-skill4k—~2.2kAutomated safety check: PassAGPL-3.0

Similar skills

  • Markdown Article Formatter

    JimLiu/baoyu-skills

    Reformats plain text or Markdown articles with frontmatter, a title, a summary, headings, bold, lists and code blocks, and saves a separate formatted copy.

    27k GitHub starsUsed in 6 repos~3.5k tokens
    Documents & OfficeAuto-check passed
  • Markitdown

    ImCa0/just-laws

    Convert files and office documents to Markdown. An agent skill from ImCa0/just-laws.

    781 GitHub starsUsed in 14 repos~3.2k tokens
    Documents & OfficeAuto-check: notes
  • Obsidian Markdown

    Atmosphere/atmosphere

    Create and edit Obsidian Flavored Markdown with wikilinks, embeds, callouts, properties, and other Obsidian-specific syntax.

    3.8k GitHub starsUsed in 20 repos~1.3k tokens
    Documents & OfficeAuto-check passed
  • Crossposting

    wasp-lang/wasp

    Crosspost Wasp blog articles (MDX) to DEV.to and Medium. An agent skill from wasp-lang/wasp.

    19k GitHub stars~1.1k tokensUpdated yesterday
    Documents & OfficeAuto-check passed
  • Gzh Design

    isjiamu/gzh-design-skill

    微信公众号文章排版引擎,将 Markdown 转换为可直接粘贴到公众号编辑器的 HTML。主题风格从 references/theme-index.md 注册的自定义主题库中选取,自动章节编号、关键词下划线标记、引言卡片、目录导航、代码块、图片/GIF、作者签名。支持 Markdown / Word(.docx) / PDF / 纯文本输入(非 Markdown…

    4k GitHub stars~2.2k tokensUpdated yesterday
    Documents & OfficeAuto-check passed
  • Review The Docs

    supabase/supabase

    Official

    Review Supabase docs changes locally in your supabase/supabase checkout — either an open PR (triage, classify, verify) or your own branch before opening a PR (local self-review).

    111k GitHub stars~4.6k tokensUpdated today
    Documents & OfficeAuto-check passed

Questions about Writing Md2hd Maps

What does Writing Md2hd Maps do?

A skill your agent uses when authoring or editing markdown that will be rendered by md2hd as a graph or mind map — writing node frontmatter, linking nodes with rel: or [[wikilinks]], setting up the…. Writing Md2hd Maps is an agent skill from evan-steinhilb/md2hd. Use when authoring or editing markdown that will be rendered by md2hd as a graph or mind map — writing node frontmatter, linking nodes with rel: or [[wikilinks]], setting up the type:map configuration block, converting a folder of existing notes into a map, or diagnosing a map that parses cleanly but draws the wrong thing (nodes missing, arrows reversed, dashed placeholder nodes, unlabelled grey lines).

When should I use Writing Md2hd Maps?

Writing Md2hd Maps fits situations like: editing markdown that will be rendered by md2hd as a graph; mind map — writing node frontmatter; linking nodes with rel:; setting up the type:map configuration block.

How do I install Writing Md2hd Maps in Claude Code?

Run `npx skills add evan-steinhilb/md2hd --skill writing-md2hd-maps -a claude-code`. Or copy the skill folder (skills/writing-md2hd-maps in evan-steinhilb/md2hd) into .claude/skills/writing-md2hd-maps in your project. Claude Code loads it when a task matches its description.

How do I install Writing Md2hd Maps in Codex?

Run `npx skills add evan-steinhilb/md2hd --skill writing-md2hd-maps -a codex`. Or copy the skill folder (skills/writing-md2hd-maps in evan-steinhilb/md2hd) into .agents/skills/writing-md2hd-maps in your project. Codex loads it when a task matches its description.

Can I use Writing Md2hd Maps in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add evan-steinhilb/md2hd --skill writing-md2hd-maps -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/writing-md2hd-maps, .gemini/skills/writing-md2hd-maps, .github/skills/writing-md2hd-maps and .opencode/skills/writing-md2hd-maps in your project.

What does Writing Md2hd Maps need to run?

Going by SKILL.md and its folder, Writing Md2hd Maps needs the command-line tools its instructions call (npx).

Does Writing Md2hd Maps access the network?

SKILL.md contains no URLs. Its commands use npx, which can reach the network depending on how they are called. This is read from the text; nothing was executed.

Is Writing Md2hd Maps safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Writing Md2hd Maps use?

Writing Md2hd Maps is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Writing Md2hd Maps use?

About 3.8k tokens (SKILL.md is roughly 15k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Writing Md2hd Maps?

Skills that share tags, products or a category with Writing Md2hd Maps: Markdown Article Formatter (JimLiu/baoyu-skills, 27k stars), Markitdown (ImCa0/just-laws, 781 stars), Obsidian Markdown (Atmosphere/atmosphere, 3.8k stars) and Crossposting (wasp-lang/wasp, 19k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Writing Md2hd Maps?

evan-steinhilb (a GitHub user) maintains it in evan-steinhilb/md2hd, which has 137 GitHub stars. The repository was last updated on August 26, 2026.

Source: evan-steinhilb/md2hd on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.