@geml/geml 1.7.8 → 1.8.2

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.
package/README.md CHANGED
@@ -1,285 +1,288 @@
1
- <p align="center">
2
- <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/geml-spec/geml/main/docs/assets/logo/geml-logo-dark.svg">
4
- <img src="https://raw.githubusercontent.com/geml-spec/geml/main/docs/assets/logo/geml-logo-light.svg" alt="GEML" width="300">
5
- </picture>
6
- </p>
7
-
8
- # @geml/geml
9
-
10
- The reference parser, validator, renderer, and CLI for **GEML** (General
11
- Expressive Markup Language) — **one format, two readers.** People and AI agents
12
- co-write the same document: plain text that stays legible for people, and
13
- **addressable, verifiable, and versioned** for machines.
14
-
15
- Every kind of structured content — code, tables, diagrams, math, callouts,
16
- metadata — rides on **one** primitive, the typed block:
17
-
18
- ```
19
- === code {#hello lang=python}
20
- print("hi")
21
- ===
22
- ```
23
-
24
- - **Addressable** — every block can be named: an `#id`, or a content address for
25
- the ones nobody named; `geml get` / `geml set '<selector>'`
26
- read or patch one section without re-emitting the whole file (on this repo's
27
- own spec, ~**120× less context** than shipping the whole document — the block
28
- is ~590 chars whatever the document grows to).
29
- - **Verifiable** — references are checked at build time (a dangling `#id` is an
30
- error, not a silent dead link), and the parser emits a document-model JSON
31
- with a `diagnostics` array, so agents and CI get a structured pass/fail signal.
32
- - **Versioned** — `geml history` and `geml revert` snapshot and rewind
33
- revisions over a plain-text `.gemlhistory` sidecar.
34
-
35
- Try the format in the [playground](https://geml-spec.github.io/geml/playground/)
36
- — no install. Full pitch, spec, and format comparison live in the
37
- [repository](https://github.com/geml-spec/geml).
38
-
39
- ## Install
40
-
41
- ```sh
42
- npm install -g @geml/geml # global CLI — installs the `geml` command
43
- # or, per project:
44
- npm install @geml/geml # library + local bin
45
- ```
46
-
47
- Requires Node ≥ 22.
48
-
49
- ## CLI
50
-
51
- The CLI is built around one question: can a single agent author and maintain an
52
- entire `.geml` file from the command line — create, add, edit, delete, and copy
53
- blocks in from other files? Three tests keep the command set honest:
54
-
55
- - **Complete** — every step of a document's life has a verb, so an agent never
56
- rewrites the whole file to change one block.
57
- - **Ergonomic** — few flags, sensible defaults, and pipeline-friendly I/O, so
58
- multi-step edits chain without ceremony.
59
- - **Consistent** — behavior is uniform and predictable: name a target `#id` and
60
- the content adopts it, every write is guarded, a file is edited in place while
61
- `-` streams to stdout.
62
-
63
- The command set borrows from two settled models rather than inventing one, and
64
- they overlap where it counts. **A document is a table**: a block is a row, its
65
- `#id` is the primary key (unique per document), and `[[#id]]`/`[t](#id)`/`[^id]`/
66
- `data=#id` are foreign keys — so `get`/`add`/`set`/`delete` are
67
- SELECT/INSERT/UPDATE/DELETE. **A block is also a resource** at a URI-like address
68
- (`file#id`), named before the operation, the way REST puts the noun first. Both
69
- models agree on a small orthogonal verb set instead of a method per use case, and
70
- they agree on idempotence: `set` and `delete` are idempotent (deleting a missing
71
- id is a no-op, so a retry is safe), `add` is not.
72
-
73
- Where they diverge, each covers what the other cannot. The relational view names
74
- the integrity rules: the write guard is a constraint check with rollback, and
75
- `delete` merely *warning* about references it leaves dangling is a **deferred**
76
- foreign-key check, not `ON DELETE RESTRICT`. It also explains `rename`, the verb
77
- most open to "can this be cut" — a **primary-key update with a cascading
78
- foreign-key rewrite**, irreducible because `delete` + `add` would leave every
79
- reference dangling and nothing else rewrites references in bulk. HTTP has no
80
- method for that at all. The REST view supplies what a database deliberately does
81
- not: every call is **stateless** — no session, no current document, no config
82
- file, no environment variable — which is what lets calls be retried,
83
- parallelized, and piped.
84
-
85
- Both pay off in undo. Because the verbs are orthogonal, each edit has exactly one
86
- inverse, so `revert` never needs to know which verb made a change — it reconciles
87
- a block to a revision in three cases (content changed, row missing, row extra)
88
- and there is no fourth, while `rename` is its own inverse and needs no history at
89
- all. A wider, RPC-shaped verb set (`replace`, `move`, `merge`, `split`, …) would
90
- need a per-verb inverse and an operation log to pick one — an undo-stack engine
91
- instead of three branches. Full rationale:
92
- [`docs/design/specs/2026-07-24-geml-block-mutation-cli-design.md`](../docs/design/specs/2026-07-24-geml-block-mutation-cli-design.md).
93
-
94
- Every command reads a file path, or `-` for stdin. Exit codes: `0` ok ·
95
- `1` document/operation error · `2` usage error.
96
-
97
- ```sh
98
- geml doc.geml # document-model JSON (default --to json)
99
- geml doc.geml --to md|html|geml # convert; geml notes.md -> GEML
100
- geml list doc.geml # CALL FIRST: every block, its address, kind, line range
101
- geml find "text" doc.geml|dir # search block CONTENT -> file<TAB>address; exit 1 = no hit
102
- geml get doc.geml ['<selector>'] # list addressable blocks, or print what the selector matches
103
- geml get doc.geml '#sec' --intro # a section cuts three ways: --head | --intro | --body
104
- geml set doc.geml '<selector>' [--head|--intro|--body] [--in F[#src]] # replace ONE block's content
105
- geml replace doc.geml OLD NEW [--within '<selector>'] # EXPERIMENTAL: literal swap, checked and reported
106
- geml add doc.geml (--append|--before #id|--after #id) [--in F[#src]] # insert a fragment
107
- geml delete doc.geml '#id' ['#id2' …] # remove one or more blocks
108
- geml rename doc.geml '#old' '#new' # rename an id + every reference to it
109
- geml revert doc.geml '#id' [--rev -1] # undo a block: splice / resurrect / remove
110
- geml check doc.geml [--root <dir>] # validate only: diagnostics + exit code (--json for the array)
111
- geml history <save|get|restore|verify> doc.geml [...] # .gemlhistory version sidecar (get = list revisions, or print one)
112
- geml codemap <build|verify|render|serve|refresh|find> # your codebase's call graph as GEML docs
113
- geml mcp --root <dir> [--graph <dir>] # serve documents (+ the code graph) over MCP
114
- geml --help | --version # --version --json prints {"parser","spec"}
115
- ```
116
-
117
- The agent loop: `geml get` a block `set`/`add`/`delete`/`rename` it
118
- `geml check` → `geml history save` — small, precise, verifiable edits.
119
-
120
- ### Selectors
121
-
122
- `get` and `set` take the same selector, which is a **filter over blocks**:
123
-
124
- | Selector | Matches |
125
- |---|---|
126
- | *(omitted)* | nothing — `get` **lists** every addressable block, one per line, by its shortest unique address |
127
- | `#id` | that block. A heading id addresses its **whole section** |
128
- | `'## Heading'` | a heading line copied out of the document, resolved to its id |
129
- | `'=== note'` | **every** `note` block 0..N of them |
130
- | `'=== note@a3f9c1d2'` | one block by CONTENT, for blocks that carry no `#id` |
131
- | `'@a3f9c1d2'` | the same, with the type check dropped |
132
-
133
- `get` answers with N contents when N match (document order, count on stderr);
134
- `set` writes ONE block, so a selector matching several is refused (exit 2) with
135
- the unique address of each candidate. A section cuts three ways: `--head` is the
136
- heading line, `--intro` its opening region everything under it up to its first
137
- subheading — and `--body` everything under it, so `--body` always contains
138
- `--intro`, and equals it when the section has no subheading. All three
139
- round-trip `geml get f X --body | geml set f X --body` leaves the file
140
- byte-identical — and `--intro` is how a section's opening is edited without
141
- pulling its subsections into context. A block has no intro; asking for one is a
142
- usage error rather than a quiet fall back to the body.
143
-
144
- `replace` is the cheap path when the exact old text is already known and nothing
145
- needs reading a version string in six places, a term renamed. It is the one
146
- operation where GEML can beat `sed` outright rather than imitate it: the same
147
- two short strings, but the result is re-parsed before it lands, the blocks it
148
- touched are named back to you, and it is in `.gemlhistory` to revert. It swaps a
149
- LITERAL, never a pattern, and refuses a swap that would rename an id — that is
150
- `geml rename`, which fixes the references too. **It is EXPERIMENTAL and may be
151
- withdrawn**; build nothing on it that cannot change.
152
-
153
- A write is refused when it would break the document, never merely because it
154
- removes something. A replacement that drops blocks is carried out and the
155
- dropped blocks are named on stderr — unnamed ones counted, references left
156
- dangling reported with `geml revert` as the way back. That is the same stance
157
- `delete` takes, so removing content has one rule rather than two, and no region
158
- becomes uneditable because something inside it happens to carry an id. The
159
- round trip above drops nothing: the blocks came back in the text you sent.
160
-
161
- A `@<hex>` **content address** is the first 8 hex of the SHA-256 of the block's
162
- own text (line endings normalized to LF, no trailing newline), with `~1`, `~2`…
163
- distinguishing byte-identical blocks. Read them out of `geml get doc.geml` —
164
- they are printed for every block that has no `#id`. Being content-derived, an
165
- address **goes stale when the block changes** and then fails with exit 1 rather
166
- than silently addressing a different block: it doubles as a precondition. That
167
- also means `set` through one prints the new address on stderr. The exact hash
168
- input is pinned in
169
- [the selector design doc](../docs/design/specs/2026-08-04-geml-get-set-selector-design-change.md)
170
- §3.4 so a second implementation computes the same values.
171
-
172
- Conversion is one entry — `geml <file> [--to json|html|md|geml]`; the input
173
- format is inferred (`--from` overrides > extension > GEML), the target is `--to`
174
- (default: GEML → JSON, Markdown → GEML), and `-o` names the output path.
175
-
176
- `set` and `add` take their content from `--in F` (F's block whose id equals the
177
- target), `--in F#src` (F's block `#src`), or stdin (raw bytes). `set` **replaces
178
- a whole block** and normalizes the content's id to the target — so you can fork
179
- any block into this slot without hand-editing its id (`--head` swaps just the
180
- head line, `--body` just the body). `add` **inserts a fragment** (one or more
181
- blocks, or bare prose) at `--append` / `--before #id` / `--after #id`, keeping
182
- the content's own ids (a collision is refused). `delete` removes one or more
183
- ids; `rename` rewrites an id's declaration and every reference to it.
184
-
185
- Mutations (`set`/`add`/`delete`/`rename`) write the **whole updated document**:
186
- in place when the input is a file, or to **stdout** when the input is `-`; `-o`
187
- redirects the write (`-o -` forces stdout), so edits pipe cleanly. Every write
188
- is guarded re-parsed and refused if it would break the document or drop an id
189
- (a reference left dangling by `delete` is a warning, not a refusal; `geml check`
190
- flags it later).
191
-
192
- Undo is `revert`, which reconciles one block to a past revision (`--rev`, default
193
- `-1`): it **splices** back changed content, **resurrects** a deleted block (placed
194
- by its old neighbours, or `--append`/`--before`/`--after`), or **removes** a block
195
- that did not exist then. So each forward edit has an inverse:
196
-
197
- | forward edit | undo |
198
- |---|---|
199
- | `set #id` | `revert #id` (splice) |
200
- | `delete #id` | `revert #id` (resurrect) |
201
- | `add #id` | `revert #id` (remove) — or `delete #id` |
202
- | `rename #old #new` | `rename #new #old` (self-inverse) |
203
-
204
- `revert` reads the `.gemlhistory` sidecar, so `set`/`delete`/`add` undo needs a
205
- prior `geml history save`; `rename` is its own inverse and needs no history.
206
-
207
- A **heading's** `#id` addresses its whole **section** the heading line through
208
- the line before the next heading of the same-or-higher level so the prose
209
- under a heading is block-editable with no extra syntax.
210
- Spans overlap: blocks nested in the section keep their own ids, and a `set` on
211
- the section that drops one of them is refused by the guard. `get --json` on a
212
- heading covers the same content as the raw span: a section envelope
213
- `{kind:"section", id, level, blocks:[heading, …its section's blocks]}` (a
214
- block/footnote id still prints its single model node). `--head` narrows
215
- `get`/`set`/`revert` to ANY id's head line a heading's line, or a typed
216
- block's opening fence line, so an agent renames a heading or edits a block's
217
- attributes (caption, compute, …) without touching the body. Convention: keep
218
- the document title in `=== meta` (`title = "…"`), not an H1 — a lone top-level
219
- `#` section is the whole document, the telltale that it is really a title.
220
-
221
- ## MCP Server
222
-
223
- This package includes a standard Model Context Protocol (MCP) server that exposes GEML document CRUD operations. It runs locally and supports Windows, macOS, and Linux.
224
-
225
- To connect it to an MCP-compatible client, provide the `npx` execution command and specify the `--root` argument (the directory containing your `.geml` files).
226
-
227
- ### Claude Desktop
228
- Add to your `claude_desktop_config.json`:
229
- ```json
230
- {
231
- "mcpServers": {
232
- "geml": {
233
- "command": "npx",
234
- "args": [
235
- "-y",
236
- "@geml/geml@latest",
237
- "mcp",
238
- "--root",
239
- "/absolute/path/to/your/docs"
240
- ]
241
- }
242
- }
243
- }
244
- ```
245
-
246
- ### Claude Code / CLI Clients
247
- Run the following command to add the server:
248
- ```sh
249
- claude mcp add geml -- npx -y @geml/geml@latest mcp --root /absolute/path/to/your/docs
250
- ```
251
-
252
- With a code graph under `--root` (`geml codemap build`), the same server also
253
- serves four read-only `geml_codemap_*` tools. Every tool and option:
254
- [`docs/mcp-guide.md`](https://github.com/geml-spec/geml/blob/main/docs/mcp-guide.md).
255
-
256
- ## Library
257
-
258
- ```js
259
- import { parse, serialize, renderHtml, gemlToMd, mdToGeml } from "@geml/geml";
260
-
261
- const doc = parse(src); // { kind:"document", children, ids, diagnostics }
262
- const ok = !doc.diagnostics.some(d => d.severity === "error");
263
- const html = renderHtml(doc); // one self-contained HTML string
264
- const md = gemlToMd(doc).md; // GitHub-Flavored Markdown (lossy)
265
- const geml = mdToGeml(markdown).geml; // the inverse
266
- const canonical = serialize(doc); // GEML text; parse(serialize(parse(x))) is stable
267
- ```
268
-
269
- `parse(src, { resolveDoc })` enables cross-document reference checking — pass a
270
- function that returns another file's source by path (or `null`).
271
-
272
- ## Documentation
273
-
274
- Full normative spec, history-sidecar spec, and format comparison live in the
275
- [repository](https://github.com/geml-spec/geml). The spec is itself
276
- written in GEML (`GEML-spec.geml`) and parsed clean on every test run.
277
-
278
- What changed between releases:
279
- [`CHANGELOG.md`](https://github.com/geml-spec/geml/blob/main/CHANGELOG.md).
280
- The parser and the specification version independently — `geml --version --json`
281
- prints both.
282
-
283
- ## License
284
-
285
- MIT.
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/geml-spec/geml/main/docs/assets/logo/geml-logo-dark.svg">
4
+ <img src="https://raw.githubusercontent.com/geml-spec/geml/main/docs/assets/logo/geml-logo-light.svg" alt="GEML" width="300">
5
+ </picture>
6
+ </p>
7
+
8
+ # @geml/geml
9
+
10
+ The reference parser, validator, renderer, and CLI for **GEML** (General
11
+ Expressive Markup Language) — **one format, two readers.** People and AI agents
12
+ co-write the same document: plain text that stays legible for people, and
13
+ **addressable, verifiable, and versioned** for machines.
14
+
15
+ Every kind of structured content — code, tables, diagrams, math, callouts,
16
+ metadata — rides on **one** primitive, the typed block:
17
+
18
+ ```
19
+ === code {#hello lang=python}
20
+ print("hi")
21
+ ===
22
+ ```
23
+
24
+ - **Addressable** — every block can be named: an `#id`, or a content address for
25
+ the ones nobody named; `geml get` / `geml set '<selector>'`
26
+ read or patch one section without re-emitting the whole file (on this repo's
27
+ own spec, ~**120× less context** than shipping the whole document — the block
28
+ is ~590 chars whatever the document grows to).
29
+ - **Verifiable** — references are checked at build time (a dangling `#id` is an
30
+ error, not a silent dead link), and the parser emits a document-model JSON
31
+ with a `diagnostics` array, so agents and CI get a structured pass/fail signal.
32
+ - **Versioned** — `geml history` and `geml revert` snapshot and rewind
33
+ revisions over a plain-text `.gemlhistory` sidecar.
34
+
35
+ Try the format in the [playground](https://geml-spec.github.io/geml/playground/)
36
+ — no install. Full pitch, spec, and format comparison live in the
37
+ [repository](https://github.com/geml-spec/geml).
38
+
39
+ ## Install
40
+
41
+ ```sh
42
+ npm install -g @geml/geml # global CLI — installs the `geml` command
43
+ # or, per project:
44
+ npm install @geml/geml # library + local bin
45
+ ```
46
+
47
+ Requires Node ≥ 22.
48
+
49
+ ## CLI
50
+
51
+ The CLI is built around one question: can a single agent author and maintain an
52
+ entire `.geml` file from the command line — create, add, edit, delete, and copy
53
+ blocks in from other files? Three tests keep the command set honest:
54
+
55
+ - **Complete** — every step of a document's life has a verb, so an agent never
56
+ rewrites the whole file to change one block.
57
+ - **Ergonomic** — few flags, sensible defaults, and pipeline-friendly I/O, so
58
+ multi-step edits chain without ceremony.
59
+ - **Consistent** — behavior is uniform and predictable: name a target `#id` and
60
+ the content adopts it, every write is guarded, a file is edited in place while
61
+ `-` streams to stdout.
62
+
63
+ The command set borrows from two settled models rather than inventing one, and
64
+ they overlap where it counts. **A document is a table**: a block is a row, its
65
+ `#id` is the primary key (unique per document), and `[[#id]]`/`[t](#id)`/`[^id]`/
66
+ `data=#id` are foreign keys — so `get`/`add`/`set`/`delete` are
67
+ SELECT/INSERT/UPDATE/DELETE. **A block is also a resource** at a URI-like address
68
+ (`file#id`), named before the operation, the way REST puts the noun first. Both
69
+ models agree on a small orthogonal verb set instead of a method per use case, and
70
+ they agree on idempotence: `set` and `delete` are idempotent (deleting a missing
71
+ id is a no-op, so a retry is safe), `add` is not.
72
+
73
+ Where they diverge, each covers what the other cannot. The relational view names
74
+ the integrity rules: the write guard is a constraint check with rollback, and
75
+ `delete` merely *warning* about references it leaves dangling is a **deferred**
76
+ foreign-key check, not `ON DELETE RESTRICT`. It also explains `rename`, the verb
77
+ most open to "can this be cut" — a **primary-key update with a cascading
78
+ foreign-key rewrite**, irreducible because `delete` + `add` would leave every
79
+ reference dangling and nothing else rewrites references in bulk. HTTP has no
80
+ method for that at all. The REST view supplies what a database deliberately does
81
+ not: every call is **stateless** — no session, no current document, no config
82
+ file, no environment variable — which is what lets calls be retried,
83
+ parallelized, and piped.
84
+
85
+ Both pay off in undo. Because the verbs are orthogonal, each edit has exactly one
86
+ inverse, so `revert` never needs to know which verb made a change — it reconciles
87
+ a block to a revision in three cases (content changed, row missing, row extra)
88
+ and there is no fourth, while `rename` is its own inverse and needs no history at
89
+ all. A wider, RPC-shaped verb set (`replace`, `move`, `merge`, `split`, …) would
90
+ need a per-verb inverse and an operation log to pick one — an undo-stack engine
91
+ instead of three branches. Full rationale:
92
+ [`docs/design/specs/2026-07-24-geml-block-mutation-cli-design.md`](../docs/design/specs/2026-07-24-geml-block-mutation-cli-design.md).
93
+
94
+ Every command reads a file path, or `-` for stdin. Exit codes: `0` ok ·
95
+ `1` document/operation error · `2` usage error.
96
+
97
+ ```sh
98
+ geml doc.geml # document-model JSON (default --to json)
99
+ geml doc.geml --to md|html|geml # convert; geml notes.md -> GEML
100
+ geml list doc.geml # CALL FIRST: every block, its address, kind, line range
101
+ geml find "text" doc.geml|dir # search block CONTENT -> file<TAB>address; exit 1 = no hit
102
+ geml get doc.geml ['<selector>'] # list addressable blocks, or print what the selector matches
103
+ geml get doc.geml '#sec' --intro # a section cuts three ways: --head | --intro | --body
104
+ geml set doc.geml '<selector>' [--head|--intro|--body] [--in F[#src]] # replace ONE block's content
105
+ geml replace doc.geml OLD NEW [--within '<selector>'] # EXPERIMENTAL: literal swap, checked and reported
106
+ geml add doc.geml (--append|--before #id|--after #id) [--in F[#src]] # insert a fragment
107
+ geml delete doc.geml '#id' ['#id2' …] # remove one or more blocks
108
+ geml rename doc.geml '#old' '#new' # rename an id + every reference to it
109
+ geml revert doc.geml '#id' [--rev -1] # undo a block: splice / resurrect / remove
110
+ geml check doc.geml [--root <dir>] # validate only: diagnostics + exit code (--json for the array)
111
+ # --root works on every verb above, not just check. A write is refused when the result
112
+ # would not parse, so a document whose ../sibling.md links resolve only from the repo
113
+ # root needs --root to be editable at all. The MCP server passes its own root for you.
114
+ geml history <save|get|restore|verify> doc.geml [...] # .gemlhistory version sidecar (get = list revisions, or print one)
115
+ geml codemap <build|verify|render|serve|refresh|find> # your codebase's call graph as GEML docs
116
+ geml mcp --root <dir> [--graph <dir>] # serve documents (+ the code graph) over MCP
117
+ geml --help | --version # --version --json prints {"parser","spec"}
118
+ ```
119
+
120
+ The agent loop: `geml get` a block → `set`/`add`/`delete`/`rename` it →
121
+ `geml check` → `geml history save` — small, precise, verifiable edits.
122
+
123
+ ### Selectors
124
+
125
+ `get` and `set` take the same selector, which is a **filter over blocks**:
126
+
127
+ | Selector | Matches |
128
+ |---|---|
129
+ | *(omitted)* | nothing `get` **lists** every addressable block, one per line, by its shortest unique address |
130
+ | `#id` | that block. A heading id addresses its **whole section** |
131
+ | `'## Heading'` | a heading line copied out of the document, resolved to its id |
132
+ | `'=== note'` | **every** `note` block — 0..N of them |
133
+ | `'=== note@a3f9c1d2'` | one block by CONTENT, for blocks that carry no `#id` |
134
+ | `'@a3f9c1d2'` | the same, with the type check dropped |
135
+
136
+ `get` answers with N contents when N match (document order, count on stderr);
137
+ `set` writes ONE block, so a selector matching several is refused (exit 2) with
138
+ the unique address of each candidate. A section cuts three ways: `--head` is the
139
+ heading line, `--intro` its opening region everything under it up to its first
140
+ subheading — and `--body` everything under it, so `--body` always contains
141
+ `--intro`, and equals it when the section has no subheading. All three
142
+ round-trip `geml get f X --body | geml set f X --body` leaves the file
143
+ byte-identical — and `--intro` is how a section's opening is edited without
144
+ pulling its subsections into context. A block has no intro; asking for one is a
145
+ usage error rather than a quiet fall back to the body.
146
+
147
+ `replace` is the cheap path when the exact old text is already known and nothing
148
+ needs reading a version string in six places, a term renamed. It is the one
149
+ operation where GEML can beat `sed` outright rather than imitate it: the same
150
+ two short strings, but the result is re-parsed before it lands, the blocks it
151
+ touched are named back to you, and it is in `.gemlhistory` to revert. It swaps a
152
+ LITERAL, never a pattern, and refuses a swap that would rename an id — that is
153
+ `geml rename`, which fixes the references too. **It is EXPERIMENTAL and may be
154
+ withdrawn**; build nothing on it that cannot change.
155
+
156
+ A write is refused when it would break the document, never merely because it
157
+ removes something. A replacement that drops blocks is carried out and the
158
+ dropped blocks are named on stderr unnamed ones counted, references left
159
+ dangling reported with `geml revert` as the way back. That is the same stance
160
+ `delete` takes, so removing content has one rule rather than two, and no region
161
+ becomes uneditable because something inside it happens to carry an id. The
162
+ round trip above drops nothing: the blocks came back in the text you sent.
163
+
164
+ A `@<hex>` **content address** is the first 8 hex of the SHA-256 of the block's
165
+ own text (line endings normalized to LF, no trailing newline), with `~1`, `~2`…
166
+ distinguishing byte-identical blocks. Read them out of `geml get doc.geml`
167
+ they are printed for every block that has no `#id`. Being content-derived, an
168
+ address **goes stale when the block changes** and then fails with exit 1 rather
169
+ than silently addressing a different block: it doubles as a precondition. That
170
+ also means `set` through one prints the new address on stderr. The exact hash
171
+ input is pinned in
172
+ [the selector design doc](../docs/design/specs/2026-08-04-geml-get-set-selector-design-change.md)
173
+ §3.4 so a second implementation computes the same values.
174
+
175
+ Conversion is one entry — `geml <file> [--to json|html|md|geml]`; the input
176
+ format is inferred (`--from` overrides > extension > GEML), the target is `--to`
177
+ (default: GEML JSON, Markdown → GEML), and `-o` names the output path.
178
+
179
+ `set` and `add` take their content from `--in F` (F's block whose id equals the
180
+ target), `--in F#src` (F's block `#src`), or stdin (raw bytes). `set` **replaces
181
+ a whole block** and normalizes the content's id to the target — so you can fork
182
+ any block into this slot without hand-editing its id (`--head` swaps just the
183
+ head line, `--body` just the body). `add` **inserts a fragment** (one or more
184
+ blocks, or bare prose) at `--append` / `--before #id` / `--after #id`, keeping
185
+ the content's own ids (a collision is refused). `delete` removes one or more
186
+ ids; `rename` rewrites an id's declaration and every reference to it.
187
+
188
+ Mutations (`set`/`add`/`delete`/`rename`) write the **whole updated document**:
189
+ in place when the input is a file, or to **stdout** when the input is `-`; `-o`
190
+ redirects the write (`-o -` forces stdout), so edits pipe cleanly. Every write
191
+ is guarded — re-parsed and refused if it would break the document or drop an id
192
+ (a reference left dangling by `delete` is a warning, not a refusal; `geml check`
193
+ flags it later).
194
+
195
+ Undo is `revert`, which reconciles one block to a past revision (`--rev`, default
196
+ `-1`): it **splices** back changed content, **resurrects** a deleted block (placed
197
+ by its old neighbours, or `--append`/`--before`/`--after`), or **removes** a block
198
+ that did not exist then. So each forward edit has an inverse:
199
+
200
+ | forward edit | undo |
201
+ |---|---|
202
+ | `set #id` | `revert #id` (splice) |
203
+ | `delete #id` | `revert #id` (resurrect) |
204
+ | `add #id` | `revert #id` (remove) or `delete #id` |
205
+ | `rename #old #new` | `rename #new #old` (self-inverse) |
206
+
207
+ `revert` reads the `.gemlhistory` sidecar, so `set`/`delete`/`add` undo needs a
208
+ prior `geml history save`; `rename` is its own inverse and needs no history.
209
+
210
+ A **heading's** `#id` addresses its whole **section** the heading line through
211
+ the line before the next heading of the same-or-higher level so the prose
212
+ under a heading is block-editable with no extra syntax.
213
+ Spans overlap: blocks nested in the section keep their own ids, and a `set` on
214
+ the section that drops one of them is refused by the guard. `get --json` on a
215
+ heading covers the same content as the raw span: a section envelope
216
+ `{kind:"section", id, level, blocks:[heading, …its section's blocks]}` (a
217
+ block/footnote id still prints its single model node). `--head` narrows
218
+ `get`/`set`/`revert` to ANY id's head line a heading's line, or a typed
219
+ block's opening fence line, so an agent renames a heading or edits a block's
220
+ attributes (caption, compute, …) without touching the body. Convention: keep
221
+ the document title in `=== meta` (`title = "…"`), not an H1 — a lone top-level
222
+ `#` section is the whole document, the telltale that it is really a title.
223
+
224
+ ## MCP Server
225
+
226
+ This package includes a standard Model Context Protocol (MCP) server that exposes GEML document CRUD operations. It runs locally and supports Windows, macOS, and Linux.
227
+
228
+ To connect it to an MCP-compatible client, provide the `npx` execution command and specify the `--root` argument (the directory containing your `.geml` files).
229
+
230
+ ### Claude Desktop
231
+ Add to your `claude_desktop_config.json`:
232
+ ```json
233
+ {
234
+ "mcpServers": {
235
+ "geml": {
236
+ "command": "npx",
237
+ "args": [
238
+ "-y",
239
+ "@geml/geml@latest",
240
+ "mcp",
241
+ "--root",
242
+ "/absolute/path/to/your/docs"
243
+ ]
244
+ }
245
+ }
246
+ }
247
+ ```
248
+
249
+ ### Claude Code / CLI Clients
250
+ Run the following command to add the server:
251
+ ```sh
252
+ claude mcp add geml -- npx -y @geml/geml@latest mcp --root /absolute/path/to/your/docs
253
+ ```
254
+
255
+ With a code graph under `--root` (`geml codemap build`), the same server also
256
+ serves four read-only `geml_codemap_*` tools. Every tool and option:
257
+ [`docs/mcp-guide.md`](https://github.com/geml-spec/geml/blob/main/docs/mcp-guide.md).
258
+
259
+ ## Library
260
+
261
+ ```js
262
+ import { parse, serialize, renderHtml, gemlToMd, mdToGeml } from "@geml/geml";
263
+
264
+ const doc = parse(src); // { kind:"document", children, ids, diagnostics }
265
+ const ok = !doc.diagnostics.some(d => d.severity === "error");
266
+ const html = renderHtml(doc); // one self-contained HTML string
267
+ const md = gemlToMd(doc).md; // GitHub-Flavored Markdown (lossy)
268
+ const geml = mdToGeml(markdown).geml; // the inverse
269
+ const canonical = serialize(doc); // GEML text; parse(serialize(parse(x))) is stable
270
+ ```
271
+
272
+ `parse(src, { resolveDoc })` enables cross-document reference checking — pass a
273
+ function that returns another file's source by path (or `null`).
274
+
275
+ ## Documentation
276
+
277
+ Full normative spec, history-sidecar spec, and format comparison live in the
278
+ [repository](https://github.com/geml-spec/geml). The spec is itself
279
+ written in GEML (`GEML-spec.geml`) and parsed clean on every test run.
280
+
281
+ What changed between releases:
282
+ [`CHANGELOG.md`](https://github.com/geml-spec/geml/blob/main/CHANGELOG.md).
283
+ The parser and the specification version independently — `geml --version --json`
284
+ prints both.
285
+
286
+ ## License
287
+
288
+ MIT.