geml
Address a document by its BLOCKS instead of reading it whole. Use for any long Markdown or documentation file — README, spec, guide, design doc, changelog — when the job is to find where something is documented, read one section, or change one section: `geml list`, `geml find` an
Install
npx skills add https://github.com/geml-spec/geml/tree/main/integrations/claude-plugin/skills/geml
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install geml-spec-geml@llmmart
git clone https://github.com/geml-spec/geml.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole geml-spec/geml collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Reading and editing documents blockwise
Two jobs. The first is the common one, and it needs none of GEML's syntax.
A document that is NOT GEML — use this as a read layer
geml list, geml find and geml get read Markdown directly. Use them to
locate and read one block of a long document. Nothing is converted, nothing is
written, and the file stays exactly the Markdown it already was.
Only when you would otherwise read the whole file to change part of it. If
the file is short, or you already know the exact string to replace, open it the
ordinary way — the round trip costs more than it saves. What is saved is only
ever the part you did not have to read: on this project's own docs a list map
runs 5–11% of the file it maps, but on a changelog of many small sections it is
37%.
geml list <file>— every block: address, kind, line range.geml find "<text>" <file>— which block holds those words, answered as an ADDRESS rather than a line number, so it survives the next edit. (On a file that is not.gemlthis needs 1.7.5 or newer; older builds report no match rather than an error, so if a search you expect to hit comes back empty, uselist.)geml get <file> '<address>'— that block and nothing else.- Change it. Two ways, and the choice is about consent, not capability:
- Ordinary file-editing tool, using the text from step 3 as the string to replace. The default: a document nobody asked to address by block is edited the way its author edits it.
geml set <file> '#id' …, when block-addressed editing is what was asked for — a knowledge base, a generated index, a log that only grows. Measured on a real vault: the frontmatter and every block you did not address come out byte-for-byte unchanged, and the body you write lands verbatim — no escaping, no reflowing. Readreferences/markdown-writes.mdfirst; two of its rules are silent when broken.
Never convert a document to GEML, never leave a .gemlhistory beside one, and
do not pitch the format: use the tool, report the change you made, and let the
file stay what it is.
If geml --version does not answer, none of this is available. Read and edit
the ordinary way, and do not tell anyone to install anything.
A project moving TO GEML
"This project's documents are GEML now" means new documents are authored as
.geml — notes, plans, findings, reports — in one directory (docs/geml/
unless the project says otherwise), one file per topic, with an index.geml
saying what is there and why. It does not mean converting what is already
written, and nobody has to say "leave the existing files alone" for that to
hold.
Add, never replace. Writing a .geml version of a document is not licence
to delete the Markdown it was drawn from — however completely the content was
carried across, and whatever a "one home per topic" convention seems to imply.
Deleting a file is a request a person makes, never an inference from a
convention. When both exist, say in each what it is for and name one of them as
the place a given fact is maintained: two documents describing a project is
fine, two documents maintaining the same fact is what drifts.
A GEML document — get the syntax right
GEML expresses every kind of structured content — code, tables, diagrams,
math, callouts, metadata — through one primitive: the typed block
(=== <type> {#id .class key=val} … ===). Always finish by validating: a
GEML file is correct only when geml check reports no error diagnostics
(exit 0).
Golden rules (the things that are easy to get wrong)
- Fences are runs of
=(≥3). A block closes at a=run of exactly the opening length, or — when the block has an#id— at the labeled fence=== #id(any=run ≥3 followed by the id; no length counting). - Nest with longer fences. A body containing
===lines needs a longer outer fence:====wraps===. Careful: a same-length bare===in the body closes the block even if you intend a labeled close — the labeled close only spares you length-counting, it does NOT protect same-length inner fences. - Headings are ATX
#only (#…######). No setext underlines, no---breaks, no YAML frontmatter — metadata is a=== metablock, and the document TITLE lives there (title = "…"), not in an H1. A heading may carry a stable explicit id:## Title {#sec}. - Give every section a stable
{#id}—## Findings {#findings}— then keep ids unique per document, with every reference resolving:[t](#id),[[#id]],[^id],src=,data=,other.geml#id. An unresolved reference is a build error. Naming them is the part that pays later: a document with no ids costs what Markdown costs, because there is nothing forgeml getto read orgeml setto replace short of the file. - No raw HTML. Notes →
=== note, comments →%%lines, hidden content →{hidden}, addressable prose →=== text, verified data →=== data(json/jsonl;codeshows text,dataIS data).
Validate every time
geml check file.geml # diagnostics + exit code only; exit 0 = correct
geml check --json file.geml # machine-readable diagnostics array
If geml is not on PATH: npm i -g @geml/geml (package @geml/geml, command
geml), or run without installing via npx -y @geml/geml check file.geml.
Inside the geml-spec repo prefer the local build:
node geml-parser/dist/geml.js <args>. If no parser is reachable, follow the
golden rules and validate once it is.
geml skill install sets all of this up user-global, and installs this text
into whatever other agent tools it detects — a tool's directory has to be there
already; none is ever created for you. --dry-run shows what it would do.
Work blockwise (agent editing)
geml list file.geml # CALL THIS FIRST — every block, its address, kind, lines
geml find "text" file|dir # search block CONTENT -> file<TAB>address (exit 1 = no hit)
# a NAMED file is searched whatever its extension (.md too);
# a directory walks *.geml and *.md
geml get file.geml '#id' # read ONE block (a heading id = its whole section)
geml set file.geml '#id' --in f # replace ONE block (re-parsed; never writes a broken doc)
geml history save file.geml -m "…" # snapshot to .gemlhistory after each meaningful edit
geml revert file.geml '#id' # roll ONE block back (--rev -2 | changed | <rev-id>)
Address a block, never a line range: #id · '## Heading' (its whole section)
· L27-58 (the smallest block holding those lines — how a line number from an
editor, a linter or a diff hunk becomes an address). list and find print
addresses that paste straight into the others, so neither grep nor a line
count is needed to locate anything.
The rest is one geml get away in the reference below, and stays there because
it is needed rarely and this page is read every time: the remaining address
forms in #cli, and in #editing the three ways to cut a section
(--head/--intro/--body), the experimental replace, and what a write that
drops blocks does.
Full reference — pull ONE section, not the whole file
references/authoring.geml (under this skill's base directory) holds the
detailed reference. Fetch just the section you need:
geml get <skill-base>/references/authoring.geml '#tables'
| section | covers |
|---|---|
#typed-block |
block anatomy, attribute object, examples of every registered type |
#tables |
pipe/CSV bodies of FACTS, delim=, printf display — and the view that derives over one: compute=, summary=, where=, order=, limit=, select=, by=/aggregate= |
#charts |
geml-chart diagrams bound to a table via data=#id |
#data |
the data block — value tree, json/jsonl formats, blind append, chart binding |
#inline |
inline markup, links/refs/footnotes, task lists, media embeds |
#hidden |
%% comments, {hidden}, {{key}} interpolation, === embed |
#cli |
every CLI verb — get/set/add/delete/rename, --to conversion, check |
#editing |
the blockwise editing loop + .gemlhistory versioning |
#project-config |
carrying a project's Claude config docs in GEML, quietly |
#checklist |
full pre-flight authoring checklist |
Files (geml)
-
references
-
authoring.geml 19.6 KB · in bundle
-
markdown-writes.md 4.7 KB
# Writing to a Markdown file with `geml set` `geml set` and `geml add` edit one block of a `.md` in place. Nothing is converted: the file stays the Markdown it was, and a reader who does not have `geml` sees no trace of it. Everything below was measured against real documents, not read off the help text. Two of the rules are **silent** when broken — no error, exit code 0 — and they are the reason to read this before the first write rather than after. ## What holds **A write is surgical.** After `geml set <file> '#id' --body --in -`, the YAML frontmatter and every block other than `#id` are byte-for-byte what they were. The verb splices; it does not re-serialize the file. **The body lands verbatim.** Whatever you write goes in as typed — a `> [!tip]` callout, a `[[wikilink]]`, a `` ```dataview `` fence, a table. No escaping, no reflowing, no normalization of your Markdown to anyone else's taste. **A broken result is refused.** The file is re-parsed before the write lands; if the result would not parse, nothing is written. **Heading addresses are stable.** `## Entities` is `#entities` on every run and every platform, and it survives anything happening above it. That is the whole reason to address a file this way instead of by line number. ## The five that bite ### 1. Never `set` the frontmatter block — it destroys the frontmatter Frontmatter is not a typed block in Markdown; it is an anonymous prose block, and its **closing `---` is part of that block's body**. Replace the body and the closer is gone, leaving an opener with nothing to close it — no properties at all, for every tool that reads them. Change frontmatter with an ordinary editor. This is silent: exit code 0. ### 2. `--body` is for a heading. On a prose block it APPENDS A heading's block is a heading line plus a body. A prose block is body all the way down, so it has no separate body to set — `get '#x' --body` on one comes back empty, and `set --body` writes into that emptiness, which lands **after** the prose already there. Nothing is removed. | target | `set '#id'` | `set '#id' --body` | |---|---|---| | a heading block | refused: *content is prose, not a block — use --body* | replaces the section body, keeps the heading line | | a prose block | replaces it | **appends to it, silently** | The rule is the opposite of what one habit would give you, and picking wrong fails loudly one way and quietly the other. `geml list` prints the kind in its second column — read it before choosing. This is the other silent one. ### 3. A `[[name#anchor]]` link is a GEML reference, and is checked `[[file#id]]` is GEML's own cross-document reference syntax, resolved when a write lands. A wiki-style `[[Note#Heading]]` has the same shape, and there is usually no document called `Note` — only `Note.md` — so the reference does not resolve and **the write is refused**. Plain `[[Note]]`, `[[Note|alias]]` and `![[image.png]]` are fine, and `[[Note.md#Heading]]` passes. Refused, note — not mangled. Nothing is written. ### 4. Repeated heading text makes the whole file unwritable Two `## Added` headings derive the same id. The guard judges the **result** of a write, and the result still carries the collision, so the write is refused — including one in a section nowhere near it, and the message names the collision rather than your edit. A Keep-a-Changelog file cannot be edited this way at all. ```sh geml check <file> # exit 0 = writable ``` ### 5. `--in <file>` is not "read this text" `--in F` means *take block `#id` from file F*. Raw text goes in on **stdin**: ```sh printf '…' | geml set page.md '#id' --body --in - # right geml set page.md '#id' --body --in fragment.txt # looks for #id INSIDE fragment.txt ``` ## Smaller things worth knowing - `find` is a **literal substring**, not a pattern. `Hot.Cache` does not match "Hot Cache". Case-insensitive unless `--case`. - `find --head` shows **one line per block**. A block with thirteen matches reports one; `find` locates, `get` reads. - `set --body` replaces a trailing `---` rule too, if the section ends with one. - The blank line after a heading is not re-inserted. Cosmetic; renderers do not care, a diff does. - A directory walk skips hidden directories. A tree that hides sources in `.raw/` must name that directory: `geml find 'x' notes .raw`. - `@…` addresses are content hashes and change when the content does. Fine to read from a fresh `list`; never store one, and never write to one. ## Do not convert the file `geml <page>.md --to geml` and back is lossy for anything beyond plain Markdown — frontmatter lists collapse, `[[…]]` comes back escaped, thematic breaks are dropped. The point of addressing a Markdown file is that it **stays Markdown**.
-
-
SKILL.md 9.2 KB
--- name: geml description: >- Address a document by its BLOCKS instead of reading it whole. Use for any long Markdown or documentation file — README, spec, guide, design doc, changelog — when the job is to find where something is documented, read one section, or change one section: `geml list`, `geml find` and `geml get` read Markdown directly and hand back the one block that matters, leaving the file the Markdown it already was. Skip it when the whole file is short enough to read anyway. Use it also to read, author, edit or validate GEML itself — .geml files, .gemlhistory sidecars, typed blocks, === fences, geml-chart, converting Markdown to GEML — where the output must parse cleanly (zero error diagnostics) against the reference parser. --- # Reading and editing documents blockwise Two jobs. The first is the common one, and it needs none of GEML's syntax. ## A document that is NOT GEML — use this as a read layer `geml list`, `geml find` and `geml get` read **Markdown** directly. Use them to locate and read one block of a long document. Nothing is converted, nothing is written, and the file stays exactly the Markdown it already was. **Only when you would otherwise read the whole file to change part of it.** If the file is short, or you already know the exact string to replace, open it the ordinary way — the round trip costs more than it saves. What is saved is only ever the part you did not have to read: on this project's own docs a `list` map runs 5–11% of the file it maps, but on a changelog of many small sections it is 37%. 1. `geml list <file>` — every block: address, kind, line range. 2. `geml find "<text>" <file>` — which block holds those words, answered as an ADDRESS rather than a line number, so it survives the next edit. (On a file that is not `.geml` this needs 1.7.5 or newer; older builds report no match rather than an error, so if a search you expect to hit comes back empty, use `list`.) 3. `geml get <file> '<address>'` — that block and nothing else. 4. Change it. Two ways, and the choice is about **consent**, not capability: - **Ordinary file-editing tool**, using the text from step 3 as the string to replace. The default: a document nobody asked to address by block is edited the way its author edits it. - **`geml set <file> '#id' …`**, when block-addressed editing is what was asked for — a knowledge base, a generated index, a log that only grows. Measured on a real vault: the frontmatter and every block you did not address come out **byte-for-byte unchanged**, and the body you write lands verbatim — no escaping, no reflowing. Read [`references/markdown-writes.md`](references/markdown-writes.md) first; two of its rules are silent when broken. Never convert a document to GEML, never leave a `.gemlhistory` beside one, and do not pitch the format: use the tool, report the change you made, and let the file stay what it is. If `geml --version` does not answer, none of this is available. Read and edit the ordinary way, and do not tell anyone to install anything. ## A project moving TO GEML "This project's documents are GEML now" means new documents are authored as `.geml` — notes, plans, findings, reports — in one directory (`docs/geml/` unless the project says otherwise), one file per topic, with an `index.geml` saying what is there and why. It does not mean converting what is already written, and nobody has to say "leave the existing files alone" for that to hold. **Add, never replace.** Writing a `.geml` version of a document is not licence to delete the Markdown it was drawn from — however completely the content was carried across, and whatever a "one home per topic" convention seems to imply. Deleting a file is a request a person makes, never an inference from a convention. When both exist, say in each what it is for and name one of them as the place a given fact is maintained: two documents describing a project is fine, two documents maintaining the same fact is what drifts. ## A GEML document — get the syntax right GEML expresses **every** kind of structured content — code, tables, diagrams, math, callouts, metadata — through **one** primitive: the **typed block** (`=== <type> {#id .class key=val}` … `===`). Always finish by **validating**: a GEML file is correct only when `geml check` reports **no error diagnostics** (exit 0). ## Golden rules (the things that are easy to get wrong) 1. **Fences are runs of `=` (≥3).** A block closes at a `=` run of **exactly the opening length**, or — when the block has an `#id` — at the labeled fence `=== #id` (any `=` run ≥3 followed by the id; no length counting). 2. **Nest with longer fences.** A body containing `===` lines needs a **longer** outer fence: `====` wraps `===`. Careful: a same-length bare `===` in the body closes the block even if you intend a labeled close — the labeled close only spares you length-counting, it does NOT protect same-length inner fences. 3. **Headings are ATX `#` only** (`#`…`######`). No setext underlines, no `---` breaks, no YAML frontmatter — metadata is a `=== meta` block, and the document TITLE lives there (`title = "…"`), not in an H1. A heading may carry a stable explicit id: `## Title {#sec}`. 4. **Give every section a stable `{#id}`** — `## Findings {#findings}` — then keep ids unique per document, with **every reference resolving**: `[t](#id)`, `[[#id]]`, `[^id]`, `src=`, `data=`, `other.geml#id`. An unresolved reference is a build **error**. Naming them is the part that pays later: a document with no ids costs what Markdown costs, because there is nothing for `geml get` to read or `geml set` to replace short of the file. 5. **No raw HTML.** Notes → `=== note`, comments → `%%` lines, hidden content → `{hidden}`, addressable prose → `=== text`, verified data → `=== data` (json/jsonl; `code` shows text, `data` IS data). ## Validate every time ```sh geml check file.geml # diagnostics + exit code only; exit 0 = correct geml check --json file.geml # machine-readable diagnostics array ``` If `geml` is not on PATH: `npm i -g @geml/geml` (package `@geml/geml`, command `geml`), or run without installing via `npx -y @geml/geml check file.geml`. Inside the geml-spec repo prefer the local build: `node geml-parser/dist/geml.js <args>`. If no parser is reachable, follow the golden rules and validate once it is. `geml skill install` sets all of this up user-global, and installs this text into whatever other agent tools it detects — a tool's directory has to be there already; none is ever created for you. `--dry-run` shows what it would do. ## Work blockwise (agent editing) ```sh geml list file.geml # CALL THIS FIRST — every block, its address, kind, lines geml find "text" file|dir # search block CONTENT -> file<TAB>address (exit 1 = no hit) # a NAMED file is searched whatever its extension (.md too); # a directory walks *.geml and *.md geml get file.geml '#id' # read ONE block (a heading id = its whole section) geml set file.geml '#id' --in f # replace ONE block (re-parsed; never writes a broken doc) geml history save file.geml -m "…" # snapshot to .gemlhistory after each meaningful edit geml revert file.geml '#id' # roll ONE block back (--rev -2 | changed | <rev-id>) ``` Address a block, never a line range: `#id` · `'## Heading'` (its whole section) · `L27-58` (the smallest block holding those lines — how a line number from an editor, a linter or a diff hunk becomes an address). `list` and `find` print addresses that paste straight into the others, so neither `grep` nor a line count is needed to locate anything. The rest is one `geml get` away in the reference below, and stays there because it is needed rarely and this page is read every time: the remaining address forms in `#cli`, and in `#editing` the three ways to cut a section (`--head`/`--intro`/`--body`), the experimental `replace`, and what a write that drops blocks does. ## Full reference — pull ONE section, not the whole file `references/authoring.geml` (under this skill's base directory) holds the detailed reference. Fetch just the section you need: ```sh geml get <skill-base>/references/authoring.geml '#tables' ``` | section | covers | |---|---| | `#typed-block` | block anatomy, attribute object, examples of every registered type | | `#tables` | pipe/CSV bodies of FACTS, `delim=`, printf display — and the `view` that derives over one: `compute=`, `summary=`, `where=`, `order=`, `limit=`, `select=`, `by=`/`aggregate=` | | `#charts` | `geml-chart` diagrams bound to a table via `data=#id` | | `#data` | the `data` block — value tree, `json`/`jsonl` formats, blind append, chart binding | | `#inline` | inline markup, links/refs/footnotes, task lists, media embeds | | `#hidden` | `%%` comments, `{hidden}`, `{{key}}` interpolation, `=== embed` | | `#cli` | every CLI verb — get/set/add/delete/rename, `--to` conversion, check | | `#editing` | the blockwise editing loop + `.gemlhistory` versioning | | `#project-config` | carrying a project's Claude config docs in GEML, quietly | | `#checklist` | full pre-flight authoring checklist |
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.