@geml/geml 1.7.8 → 1.8.1

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.
@@ -1,365 +1,365 @@
1
- === meta
2
- title = "GEML authoring reference"
3
- role = "detail sections behind the geml skill"
4
- howto = "pull ONE section: geml get <this-file> '#<section-id>' — ids: typed-block, tables, charts, data, inline, hidden, cli, editing, project-config, checklist, reference"
5
- ===
6
-
7
- %% The golden rules and the validation workflow live in ../SKILL.md (always loaded
8
- %% with the skill). This file holds the full detail, one addressable section per
9
- %% topic. Keep the section ids stable — SKILL.md's section map points at them.
10
-
11
- # Typed block {#typed-block}
12
-
13
- ==== code {#ex-anatomy lang=geml}
14
- === <type> {#id .class key=val}
15
- <body>
16
- ===
17
- ====
18
-
19
- The **type** decides how the body is read (the *body mode*):
20
-
21
- - `raw` (verbatim): `code`, `diagram`, `table`, `data`, `math`, `embed`
22
- - `flow` (parsed prose with inline markup): `note` (callout), `text`
23
- (addressable prose — an `#id` for a run of plain prose, no callout chrome;
24
- use sparingly)
25
- - `data` (one `key=val` per line): `meta`
26
-
27
- An **unknown type** is a warning (body kept raw) — prefer the registered types.
28
-
29
- ## Attribute object {#attribute-object}
30
-
31
- Written `{#id .class key=val}` on the opening fence or a heading line:
32
-
33
- - `#id` — unique anchor for references.
34
- - `.class` — a *semantic* label (no styling implied).
35
- - `key=val` — typed: quoted `"…"` = string; `true`/`false` = bool;
36
- integer/float syntax = number; any other bare word = string. A **bare word
37
- with no `=` is a boolean flag set to true** (e.g. `hidden`).
38
- - Order is insignificant; recommended `#id`, then `.class`, then `key=val`.
39
-
40
- ## Examples of each block {#block-examples}
41
-
42
- ==== code {#ex-blocks lang=geml}
43
- === meta
44
- title = "Budget plan"
45
- version = "1.0-draft"
46
- ===
47
-
48
- === code {#hello lang=python}
49
- print("hi")
50
- ===
51
-
52
- === note {.warning}
53
- Back up before upgrading. (flow body — inline markup works here)
54
- ===
55
-
56
- === text {#thesis}
57
- Addressable prose: plain rendering, but geml get/set #thesis can edit it.
58
- ===
59
-
60
- === math {#gauss caption="Gaussian integral"}
61
- \int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
62
- ===
63
-
64
- === diagram {#flow format=mermaid caption="Review flow"}
65
- graph LR
66
- A[Draft] --> B{Review} -->|ok| C[Publish]
67
- ===
68
- ====
69
-
70
- `diagram` hosts an external DSL (`mermaid`, `graphviz`, `dot`, `d2`,
71
- `plantuml`, `geml-chart`); the processor never interprets the body. An unknown
72
- `format` is a warning.
73
-
74
- # Tables {#tables}
75
-
76
- Two bodies, one model: the visual (pipe) form, or the data form
77
- (`format=csv`/`tsv`). Both parse to the same table model.
78
-
79
- ==== code {#ex-tables lang=geml}
80
- === table {#budget caption="Annual cost"}
81
- | Plan | Months | Rate |
82
- |-------|-------:|-----:|
83
- | Basic | 1 | 30 |
84
- ===
85
-
86
- === table {#fy25 format=csv header=1 compute="FY [%.1f] = Q1 + Q2 + Q3 + Q4" summary="Segment = 'Total'; FY [%.1f] = sum(FY)"}
87
- Segment, Q1, Q2, Q3, Q4
88
- Cloud, 8, 10, 12, 14
89
- ===
90
- ====
91
-
92
- - `delim=";"` — one character replacing the data form's natural delimiter (`,`
93
- for csv, tab for tsv): European CSV, `|`-delimited exports. Anything but a
94
- single character is an error, a tab is `format=tsv` (attribute values have no
95
- escapes), and `delim` without a data `format` is ignored (warning). The data
96
- form splits and nothing more — it never strips outer `|` like the visual form.
97
- - `compute="Name = expr; Name2 = expr2"` — per-row formulas over columns (by
98
- header name, or single letter `A`,`B`,…), operators `+ - * / ( )`.
99
- Reference an earlier computed column by name. Quote names with spaces:
100
- `'Unit Price'`.
101
- - Aggregates `sum|avg|min|max|count` (e.g. `sum(FY)`) — for the `summary=`
102
- foot row. A bare (non-aggregated) column ref in `summary` is an error.
103
- - A trailing `[printf]` on a name sets numeric display: `FY [%.1f]`,
104
- `P [%.1f%%]`.
105
- - Merge cells with `span="r2c1:2x1"`.
106
-
107
- # Charts {#charts}
108
-
109
- Render a table — don't copy it:
110
-
111
- ==== code {#ex-chart lang=geml}
112
- === diagram {#rev format=geml-chart data=#fy25 type=bar x=Segment y=FY}
113
- ===
114
- ====
115
-
116
- `data=#id` must point at a `table` block (single source of truth); the column
117
- refs (`x`, `y`, …) are checked. `type ∈ {bar,line,area,pie,scatter}`. The body
118
- is empty (the spec lives in attributes).
119
-
120
- # Data blocks {#data}
121
-
122
- `=== data` carries the VALUE TREE (scalars/arrays/objects — JSON's value
123
- domain) as verified data. The dividing line: `code` shows text the processor
124
- never interprets; `data` IS data — the body parses under `format=`, and a
125
- body the engine rejects is a build ERROR naming the line.
126
-
127
- ==== code {#ex-data lang=geml}
128
- === data {#cfg}
129
- {"name": "geml", "port": 8140}
130
- ===
131
-
132
- === data {#log format=jsonl}
133
- {"ts":"09:00","latency":41}
134
- {"ts":"09:01","latency":58}
135
- ===
136
-
137
- === diagram {format=geml-chart data=#log type=line x=ts y=latency}
138
- ===
139
- ====
140
-
141
- - `format=json` (default): the body is ONE JSON value. `format=jsonl`: one
142
- JSON value per non-blank line — the record-stream form. Because a document
143
- is a flat sequence of blocks, appending a complete `data` block at EOF is a
144
- valid continuation of any document (blind-append, like a jsonl file, with
145
- ids and verification on top).
146
- - `src=` loads the content from an external file (`.json`/`.jsonl`; explicit
147
- `format=` wins over the extension) — exactly ONE of `src=` and a body.
148
- `http(s)` sources load at render time. The log arrangement: keep the
149
- records in a plain `.jsonl` any tool can append to and tail — the GEML doc
150
- is its verified, chartable view. A chart may also name a local file
151
- directly: `data=log.jsonl`.
152
- - A source route MAY narrow the file to a line range — `src=log.jsonl#L900-999`,
153
- 1-based and inclusive — which is how a window of a long log is addressed.
154
- `code` uses the SAME route syntax for the code it shows
155
- (`src=src/attrs.ts#L14-24`): the route is the source of truth, a range the
156
- file no longer has is an error (a drifted reference fails the build), and a
157
- body kept alongside it is a snapshot that warns when it goes stale. Routes
158
- resolve document-relative, or relative to `--root` when one is given.
159
- - `yaml`/`toml` are RESERVED names: no engine in the core — body kept raw
160
- plus a warning, never guessed. csv/tsv belong to `table`, not `data`
161
- (their delimiter/header dialect parameters only mean something against a
162
- column model).
163
- - A chart's `data=#id` accepts a `data` block whose value is a RECORD ARRAY
164
- (non-empty array of objects): keys project to columns; every column the
165
- chart references must be present and scalar in every record.
166
- - `schema=` names a block (`#id`) or GEML document holding a schema —
167
- reference-checked only today; value validation is a later GEP.
168
- - The parsed value lives in the model: `geml get '#cfg' --json` returns the
169
- node with `value`, no re-parsing. `geml fmt` canonicalizes: json at
170
- two-space indent, jsonl one compact value per line.
171
-
172
- # Inline markup {#inline}
173
-
174
- Inside flow blocks only: `*emphasis*` · `**strong**` · `` `code` `` ·
175
- `~~strike~~` · `$inline math$`.
176
-
177
- - Link: `[text](https://…)` · internal ref `[text](#id)` · auto-ref `[[#id]]`
178
- (link text from the target's caption/heading) · footnote `[^id]`.
179
- - Media embed: `![alt](clip.mp4)` — kind (image/audio/video) inferred from the
180
- extension; renders/plays in place (a link navigates, an embed does not).
181
- - Hard line break: trailing `\`. Escape punctuation with `\`; block syntax at
182
- line start is escaped the same way (`\===`, `\#`).
183
- - Lists: `- item` / `1. item`. **Task list**: `- [ ] open` / `- [x] done`.
184
-
185
- # Hidden, comments, interpolation, embed {#hidden}
186
-
187
- - **`%%` line** — a hidden, raw, never-rendered note (TODO/review remark).
188
- Kept in the model (tools can find it) but NOT inline-parsed, so a scratch
189
- note can't break the build. Line-start only.
190
- - **`{hidden}` block** — present in the model and **fully reference-checked**,
191
- but not rendered. Use it for a source table that only feeds a chart:
192
- `=== table {#fy25 hidden …}`.
193
- - **`{{key}}`** in flow text is replaced with the matching `=== meta` value;
194
- an unknown key is a build **error** (single source of truth). Inside a code
195
- span it stays verbatim — that is how to *show* the syntax.
196
- - **`=== embed {src=other.geml#id}`** stands for content that lives elsewhere
197
- and renders it in place; a fragment naming a heading takes the whole
198
- section, and no fragment takes the whole document. `src=` is
199
- reference-checked, so a reference-only index document can be validated.
200
- Cycles are an error; nesting is capped.
201
-
202
- # CLI {#cli}
203
-
204
- Validate first — `geml check` exits non-zero on any error, a hard pass/fail
205
- signal, and prints only diagnostics (cheap on context):
206
-
207
- === code {#cli-check lang=sh}
208
- geml check file.geml # diagnostics + exit code only
209
- geml check --json file.geml # machine-readable diagnostics array
210
- geml check --root . file.geml # widen cross-doc reference resolution to a dir
211
- ===
212
-
213
- All commands accept `-` to read from stdin.
214
-
215
- === code {#cli-verbs lang=sh}
216
- geml file.geml # document-model JSON (default --to json)
217
- geml list file.geml # CALL FIRST: every block, its address, kind, line range
218
- geml find "text" file|dir # search block CONTENT -> file<TAB>address; exit 1 = no hit
219
- # a NAMED file is searched whatever its extension — `list`,
220
- # `get` and `find` all read Markdown, so this addresses a
221
- # plain README without converting it; a DIRECTORY walks *.geml
222
- geml get file.geml # same listing as `list` (the no-selector default)
223
- geml get file.geml '#id' # print ONE block (raw span; --json = model node)
224
- geml get file.geml '=== note' # every block of a type; '@a3f9c1d2' = a block with no #id
225
- geml get file.geml 'L27-58' # position: the smallest block holding those lines
226
- geml get file.geml '#sec' --intro # a section cut three ways: --head | --intro | --body
227
- geml set file.geml '#id' --in f # replace ONE block (guarded: re-parsed, never writes broken)
228
- geml set file.geml '#sec' --intro # replace just the opening; the subsections stay put
229
- geml replace file.geml OLD NEW # EXPERIMENTAL, may be withdrawn: literal swap, checked and
230
- # reported; --within '#id' or '=== type' narrows the scope
231
- geml add file.geml --after '#id' --in f # insert a fragment (keeps its own ids)
232
- geml delete file.geml '#id' ['#id2'] # remove one or more blocks
233
- geml rename file.geml '#old' '#new' # rename an id AND every reference to it
234
- ===
235
-
236
- A heading id addresses its whole SECTION (through the next same-or-higher
237
- heading); `--head` narrows any id to its head line alone (rename a heading, or
238
- edit a block's attributes without re-sending its body).
239
-
240
- An `embed` block has no content of its own, so `get '#e'` returns the FRAME
241
- (its `src=`). To see what the window looks onto, add `--view`:
242
-
243
- === code {#cli-view lang=sh}
244
- geml get file.geml '#e' --view # the entity block the chain ends at
245
- geml get file.geml '#e' --view --body # just its body — the usual want
246
- geml get file.geml '#e' --view --json # its model node, plus `from`
247
- ===
248
-
249
- `--view` resolves to the ENTITY block: multi-layer chains are followed to the
250
- end, and on any block that is not an embed it changes nothing. Provenance goes
251
- to stderr (`view: #e -> part.geml#tip`) because the bytes belong to ANOTHER
252
- document — their refs and relative paths resolve against that one. It is
253
- read-only (`set` refuses it), chain reads are confined to `--root` (default: the
254
- document's own directory), and a non-local target is refused, never fetched. A
255
- SECTION selector is the identity: piercing an embed inside it would splice two
256
- documents' bytes together, so address that embed instead. MCP: `geml_get
257
- {view: true, part: "body"}`.
258
-
259
- Conversion is ONE entry — `geml <file> --to <format>` — not a verb per format:
260
-
261
- === code {#cli-convert lang=sh}
262
- geml file.geml --to html -o out.html # one self-contained, interactive HTML file
263
- geml file.geml --to md -o out.md # GitHub-Flavored Markdown (lossy; loss notes on stderr)
264
- geml input.md --to geml -o out.geml # Markdown -> GEML
265
- geml file.geml --to geml # canonical re-format (idempotent)
266
- ===
267
-
268
- Install: `npm i -g @geml/geml` (package `@geml/geml`, command `geml`), or
269
- one-shot via `npx -y @geml/geml <args>`. From a clone of the geml-spec repo:
270
- `cd geml-parser && npm install && npm run build && npm link`, or run
271
- `node geml-parser/dist/geml.js <args>` directly.
272
-
273
- # Editing and versioning {#editing}
274
-
275
- When revising a `.geml` over many steps, work **one block at a time** and
276
- snapshot as you go, rather than re-emitting the whole file:
277
-
278
- === code {#editing-loop lang=sh}
279
- geml get file.geml '#intro' # read just this block (a heading id = its whole section)
280
- geml set file.geml '#intro' --in - # replace just this span (stdin or --in FILE);
281
- # the splice is re-parsed and REJECTED if it breaks the doc
282
- geml history save file.geml -m "…" # snapshot into the .gemlhistory sidecar — do this each step
283
- geml history get file.geml # revisions, newest first; first column IS the --rev selector
284
- geml revert file.geml '#intro' # roll ONE block back to the previous revision (= --rev -1)
285
- geml revert file.geml '#intro' --rev -2 # …two revisions back (also: --rev 0 = tip, --rev <id>)
286
- geml revert file.geml '#intro' --rev changed # …the block's last ACTUAL change — use this after other
287
- # blocks were written since; a fixed -N silently no-ops there
288
- ===
289
-
290
- **Retain every step.** `history` and `revert` can only recover what was saved
291
- — so after each meaningful edit to a `.geml`, run `geml history save`
292
- (automatable with a `PostToolUse` hook). Together, `get`/`set` (address one
293
- block) and `history`/`revert` (version and rewind it) let an agent revise a
294
- document incrementally and undo any single section.
295
-
296
- **A section can be cut three ways**, on `get` and `set` alike: `--head` (the
297
- heading line), `--intro` (what it says before its first subheading — empty when
298
- one follows immediately, the whole body when none does), `--body` (everything
299
- under it, so it always contains the intro). `--intro` is how you edit a
300
- section's opening without pulling its subsections into context, and setting an
301
- empty one writes an opening where the section had none.
302
-
303
- **When the exact old text is already known** and nothing needs reading — a
304
- version string in six places, a renamed term — `geml replace` is the cheap path,
305
- and the one to prefer over dropping to `sed`: the same two short strings, but
306
- the result is re-parsed before it lands, the blocks it touched are named back to
307
- you, and it is in `.gemlhistory` to revert. It swaps a LITERAL, never a pattern,
308
- and refuses a swap that would rename an id (use `geml rename`, which fixes the
309
- references too). **It is EXPERIMENTAL and may be withdrawn** — reach for it, but
310
- do not build anything on it that cannot change.
311
-
312
- **A write is refused when it would BREAK the document**, never merely because it
313
- removes something: a replacement that drops blocks is carried out and NAMED on
314
- stderr — unnamed blocks included — with `geml revert` as the way back. Read,
315
- edit, write back, and nothing is dropped, because `get` handed those blocks to
316
- you. Send content that omits them only when removing them is the point.
317
-
318
- **Where sidecars do NOT belong:** a doc that git already versions — config
319
- docs especially — usually needs no `.gemlhistory`; do not create one there
320
- unless the user asks for finer-than-commit history.
321
-
322
- # Project config in GEML {#project-config}
323
-
324
- A project's Claude instruction docs can live in GEML with **zero settings
325
- changes and zero hooks** — quiet by design:
326
-
327
- 1. Put the doc at `.claude/geml/<name>.geml` (e.g. `instructions.geml`), one
328
- topic per section, each heading with a stable `{#id}` — so a single rule
329
- can be edited (`geml set`) or removed (`geml delete`) without touching the
330
- rest.
331
- 2. Reference it from the project's `CLAUDE.md` with one import line:
332
- `@.claude/geml/instructions.geml` — Claude Code expands imports of any
333
- file type literally at session start (recursive, max 4 hops).
334
- 3. Leave everything else in `CLAUDE.md` alone — especially tool-managed,
335
- installer-appended sections (they are updated in place by their
336
- installers and must stay in Markdown where those tools grep for them).
337
- 4. The imported `.geml` must not contain bare `@path` tokens in prose — the
338
- import scanner does not recognize GEML code fences, only Markdown ones.
339
- 5. No `.gemlhistory` for these files: git is their history.
340
-
341
- When asked to create or restructure project instructions, prefer this shape
342
- without ceremony; never convert files the user did not ask about, and never
343
- move a tool-managed block out of `CLAUDE.md`.
344
-
345
- # Authoring checklist {#checklist}
346
-
347
- - [ ] Every closing fence is a `=` run of exactly the opening length, or the
348
- labeled fence `=== #id` of an id-carrying block.
349
- - [ ] Bodies containing `===` runs are wrapped in a LONGER fence (`====`) —
350
- a same-length bare `===` in the body closes the block early, labeled
351
- close or not.
352
- - [ ] Headings are ATX `#`; metadata is a `=== meta` block (no frontmatter);
353
- the document title is `title = "…"` in meta, not an H1.
354
- - [ ] All ids unique; all `[t](#id)` / `[[#id]]` / `[[doc.geml#id]]` /
355
- `[^id]` / `src=` / `data=` references resolve.
356
- - [ ] `{{key}}` keys exist in `=== meta` (code-span occurrences stay literal).
357
- - [ ] No raw HTML; comments use `%%`, hidden content uses `{hidden}`.
358
- - [ ] Validated: `geml check` reports zero error diagnostics (exit 0).
359
-
360
- # Reference {#reference}
361
-
362
- Full normative spec, in the geml-spec repo
363
- (https://github.com/geml-spec/geml): `spec/GEML-spec.md` (English),
364
- `spec/GEML-spec_CN.md` (中文). History sidecar: `spec/GEML-history-spec.md`.
365
- The spec is itself written in GEML (dogfood): `spec/in_geml_format/`.
1
+ === meta
2
+ title = "GEML authoring reference"
3
+ role = "detail sections behind the geml skill"
4
+ howto = "pull ONE section: geml get <this-file> '#<section-id>' — ids: typed-block, tables, charts, data, inline, hidden, cli, editing, project-config, checklist, reference"
5
+ ===
6
+
7
+ %% The golden rules and the validation workflow live in ../SKILL.md (always loaded
8
+ %% with the skill). This file holds the full detail, one addressable section per
9
+ %% topic. Keep the section ids stable — SKILL.md's section map points at them.
10
+
11
+ # Typed block {#typed-block}
12
+
13
+ ==== code {#ex-anatomy lang=geml}
14
+ === <type> {#id .class key=val}
15
+ <body>
16
+ ===
17
+ ====
18
+
19
+ The **type** decides how the body is read (the *body mode*):
20
+
21
+ - `raw` (verbatim): `code`, `diagram`, `table`, `data`, `math`, `embed`
22
+ - `flow` (parsed prose with inline markup): `note` (callout), `text`
23
+ (addressable prose — an `#id` for a run of plain prose, no callout chrome;
24
+ use sparingly)
25
+ - `data` (one `key=val` per line): `meta`
26
+
27
+ An **unknown type** is a warning (body kept raw) — prefer the registered types.
28
+
29
+ ## Attribute object {#attribute-object}
30
+
31
+ Written `{#id .class key=val}` on the opening fence or a heading line:
32
+
33
+ - `#id` — unique anchor for references.
34
+ - `.class` — a *semantic* label (no styling implied).
35
+ - `key=val` — typed: quoted `"…"` = string; `true`/`false` = bool;
36
+ integer/float syntax = number; any other bare word = string. A **bare word
37
+ with no `=` is a boolean flag set to true** (e.g. `hidden`).
38
+ - Order is insignificant; recommended `#id`, then `.class`, then `key=val`.
39
+
40
+ ## Examples of each block {#block-examples}
41
+
42
+ ==== code {#ex-blocks lang=geml}
43
+ === meta
44
+ title = "Budget plan"
45
+ version = "1.0-draft"
46
+ ===
47
+
48
+ === code {#hello lang=python}
49
+ print("hi")
50
+ ===
51
+
52
+ === note {.warning}
53
+ Back up before upgrading. (flow body — inline markup works here)
54
+ ===
55
+
56
+ === text {#thesis}
57
+ Addressable prose: plain rendering, but geml get/set #thesis can edit it.
58
+ ===
59
+
60
+ === math {#gauss caption="Gaussian integral"}
61
+ \int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
62
+ ===
63
+
64
+ === diagram {#flow format=mermaid caption="Review flow"}
65
+ graph LR
66
+ A[Draft] --> B{Review} -->|ok| C[Publish]
67
+ ===
68
+ ====
69
+
70
+ `diagram` hosts an external DSL (`mermaid`, `graphviz`, `dot`, `d2`,
71
+ `plantuml`, `geml-chart`); the processor never interprets the body. An unknown
72
+ `format` is a warning.
73
+
74
+ # Tables {#tables}
75
+
76
+ Two bodies, one model: the visual (pipe) form, or the data form
77
+ (`format=csv`/`tsv`). Both parse to the same table model.
78
+
79
+ ==== code {#ex-tables lang=geml}
80
+ === table {#budget caption="Annual cost"}
81
+ | Plan | Months | Rate |
82
+ |-------|-------:|-----:|
83
+ | Basic | 1 | 30 |
84
+ ===
85
+
86
+ === table {#fy25 format=csv header=1 compute="FY [%.1f] = Q1 + Q2 + Q3 + Q4" summary="Segment = 'Total'; FY [%.1f] = sum(FY)"}
87
+ Segment, Q1, Q2, Q3, Q4
88
+ Cloud, 8, 10, 12, 14
89
+ ===
90
+ ====
91
+
92
+ - `delim=";"` — one character replacing the data form's natural delimiter (`,`
93
+ for csv, tab for tsv): European CSV, `|`-delimited exports. Anything but a
94
+ single character is an error, a tab is `format=tsv` (attribute values have no
95
+ escapes), and `delim` without a data `format` is ignored (warning). The data
96
+ form splits and nothing more — it never strips outer `|` like the visual form.
97
+ - `compute="Name = expr; Name2 = expr2"` — per-row formulas over columns (by
98
+ header name, or single letter `A`,`B`,…), operators `+ - * / ( )`.
99
+ Reference an earlier computed column by name. Quote names with spaces:
100
+ `'Unit Price'`.
101
+ - Aggregates `sum|avg|min|max|count` (e.g. `sum(FY)`) — for the `summary=`
102
+ foot row. A bare (non-aggregated) column ref in `summary` is an error.
103
+ - A trailing `[printf]` on a name sets numeric display: `FY [%.1f]`,
104
+ `P [%.1f%%]`.
105
+ - Merge cells with `span="r2c1:2x1"`.
106
+
107
+ # Charts {#charts}
108
+
109
+ Render a table — don't copy it:
110
+
111
+ ==== code {#ex-chart lang=geml}
112
+ === diagram {#rev format=geml-chart data=#fy25 type=bar x=Segment y=FY}
113
+ ===
114
+ ====
115
+
116
+ `data=#id` must point at a `table` block (single source of truth); the column
117
+ refs (`x`, `y`, …) are checked. `type ∈ {bar,line,area,pie,scatter}`. The body
118
+ is empty (the spec lives in attributes).
119
+
120
+ # Data blocks {#data}
121
+
122
+ `=== data` carries the VALUE TREE (scalars/arrays/objects — JSON's value
123
+ domain) as verified data. The dividing line: `code` shows text the processor
124
+ never interprets; `data` IS data — the body parses under `format=`, and a
125
+ body the engine rejects is a build ERROR naming the line.
126
+
127
+ ==== code {#ex-data lang=geml}
128
+ === data {#cfg}
129
+ {"name": "geml", "port": 8140}
130
+ ===
131
+
132
+ === data {#log format=jsonl}
133
+ {"ts":"09:00","latency":41}
134
+ {"ts":"09:01","latency":58}
135
+ ===
136
+
137
+ === diagram {format=geml-chart data=#log type=line x=ts y=latency}
138
+ ===
139
+ ====
140
+
141
+ - `format=json` (default): the body is ONE JSON value. `format=jsonl`: one
142
+ JSON value per non-blank line — the record-stream form. Because a document
143
+ is a flat sequence of blocks, appending a complete `data` block at EOF is a
144
+ valid continuation of any document (blind-append, like a jsonl file, with
145
+ ids and verification on top).
146
+ - `src=` loads the content from an external file (`.json`/`.jsonl`; explicit
147
+ `format=` wins over the extension) — exactly ONE of `src=` and a body.
148
+ `http(s)` sources load at render time. The log arrangement: keep the
149
+ records in a plain `.jsonl` any tool can append to and tail — the GEML doc
150
+ is its verified, chartable view. A chart may also name a local file
151
+ directly: `data=log.jsonl`.
152
+ - A source route MAY narrow the file to a line range — `src=log.jsonl#L900-999`,
153
+ 1-based and inclusive — which is how a window of a long log is addressed.
154
+ `code` uses the SAME route syntax for the code it shows
155
+ (`src=src/attrs.ts#L14-24`): the route is the source of truth, a range the
156
+ file no longer has is an error (a drifted reference fails the build), and a
157
+ body kept alongside it is a snapshot that warns when it goes stale. Routes
158
+ resolve document-relative, or relative to `--root` when one is given.
159
+ - `yaml`/`toml` are RESERVED names: no engine in the core — body kept raw
160
+ plus a warning, never guessed. csv/tsv belong to `table`, not `data`
161
+ (their delimiter/header dialect parameters only mean something against a
162
+ column model).
163
+ - A chart's `data=#id` accepts a `data` block whose value is a RECORD ARRAY
164
+ (non-empty array of objects): keys project to columns; every column the
165
+ chart references must be present and scalar in every record.
166
+ - `schema=` names a block (`#id`) or GEML document holding a schema —
167
+ reference-checked only today; value validation is a later GEP.
168
+ - The parsed value lives in the model: `geml get '#cfg' --json` returns the
169
+ node with `value`, no re-parsing. `geml fmt` canonicalizes: json at
170
+ two-space indent, jsonl one compact value per line.
171
+
172
+ # Inline markup {#inline}
173
+
174
+ Inside flow blocks only: `*emphasis*` · `**strong**` · `` `code` `` ·
175
+ `~~strike~~` · `$inline math$`.
176
+
177
+ - Link: `[text](https://…)` · internal ref `[text](#id)` · auto-ref `[[#id]]`
178
+ (link text from the target's caption/heading) · footnote `[^id]`.
179
+ - Media embed: `![alt](clip.mp4)` — kind (image/audio/video) inferred from the
180
+ extension; renders/plays in place (a link navigates, an embed does not).
181
+ - Hard line break: trailing `\`. Escape punctuation with `\`; block syntax at
182
+ line start is escaped the same way (`\===`, `\#`).
183
+ - Lists: `- item` / `1. item`. **Task list**: `- [ ] open` / `- [x] done`.
184
+
185
+ # Hidden, comments, interpolation, embed {#hidden}
186
+
187
+ - **`%%` line** — a hidden, raw, never-rendered note (TODO/review remark).
188
+ Kept in the model (tools can find it) but NOT inline-parsed, so a scratch
189
+ note can't break the build. Line-start only.
190
+ - **`{hidden}` block** — present in the model and **fully reference-checked**,
191
+ but not rendered. Use it for a source table that only feeds a chart:
192
+ `=== table {#fy25 hidden …}`.
193
+ - **`{{key}}`** in flow text is replaced with the matching `=== meta` value;
194
+ an unknown key is a build **error** (single source of truth). Inside a code
195
+ span it stays verbatim — that is how to *show* the syntax.
196
+ - **`=== embed {src=other.geml#id}`** stands for content that lives elsewhere
197
+ and renders it in place; a fragment naming a heading takes the whole
198
+ section, and no fragment takes the whole document. `src=` is
199
+ reference-checked, so a reference-only index document can be validated.
200
+ Cycles are an error; nesting is capped.
201
+
202
+ # CLI {#cli}
203
+
204
+ Validate first — `geml check` exits non-zero on any error, a hard pass/fail
205
+ signal, and prints only diagnostics (cheap on context):
206
+
207
+ === code {#cli-check lang=sh}
208
+ geml check file.geml # diagnostics + exit code only
209
+ geml check --json file.geml # machine-readable diagnostics array
210
+ geml check --root . file.geml # widen cross-doc reference resolution to a dir
211
+ ===
212
+
213
+ All commands accept `-` to read from stdin.
214
+
215
+ === code {#cli-verbs lang=sh}
216
+ geml file.geml # document-model JSON (default --to json)
217
+ geml list file.geml # CALL FIRST: every block, its address, kind, line range
218
+ geml find "text" file|dir # search block CONTENT -> file<TAB>address; exit 1 = no hit
219
+ # a NAMED file is searched whatever its extension — `list`,
220
+ # `get` and `find` all read Markdown, so this addresses a
221
+ # plain README without converting it; a DIRECTORY walks *.geml
222
+ geml get file.geml # same listing as `list` (the no-selector default)
223
+ geml get file.geml '#id' # print ONE block (raw span; --json = model node)
224
+ geml get file.geml '=== note' # every block of a type; '@a3f9c1d2' = a block with no #id
225
+ geml get file.geml 'L27-58' # position: the smallest block holding those lines
226
+ geml get file.geml '#sec' --intro # a section cut three ways: --head | --intro | --body
227
+ geml set file.geml '#id' --in f # replace ONE block (guarded: re-parsed, never writes broken)
228
+ geml set file.geml '#sec' --intro # replace just the opening; the subsections stay put
229
+ geml replace file.geml OLD NEW # EXPERIMENTAL, may be withdrawn: literal swap, checked and
230
+ # reported; --within '#id' or '=== type' narrows the scope
231
+ geml add file.geml --after '#id' --in f # insert a fragment (keeps its own ids)
232
+ geml delete file.geml '#id' ['#id2'] # remove one or more blocks
233
+ geml rename file.geml '#old' '#new' # rename an id AND every reference to it
234
+ ===
235
+
236
+ A heading id addresses its whole SECTION (through the next same-or-higher
237
+ heading); `--head` narrows any id to its head line alone (rename a heading, or
238
+ edit a block's attributes without re-sending its body).
239
+
240
+ An `embed` block has no content of its own, so `get '#e'` returns the FRAME
241
+ (its `src=`). To see what the window looks onto, add `--view`:
242
+
243
+ === code {#cli-view lang=sh}
244
+ geml get file.geml '#e' --view # the entity block the chain ends at
245
+ geml get file.geml '#e' --view --body # just its body — the usual want
246
+ geml get file.geml '#e' --view --json # its model node, plus `from`
247
+ ===
248
+
249
+ `--view` resolves to the ENTITY block: multi-layer chains are followed to the
250
+ end, and on any block that is not an embed it changes nothing. Provenance goes
251
+ to stderr (`view: #e -> part.geml#tip`) because the bytes belong to ANOTHER
252
+ document — their refs and relative paths resolve against that one. It is
253
+ read-only (`set` refuses it), chain reads are confined to `--root` (default: the
254
+ document's own directory), and a non-local target is refused, never fetched. A
255
+ SECTION selector is the identity: piercing an embed inside it would splice two
256
+ documents' bytes together, so address that embed instead. MCP: `geml_get
257
+ {view: true, part: "body"}`.
258
+
259
+ Conversion is ONE entry — `geml <file> --to <format>` — not a verb per format:
260
+
261
+ === code {#cli-convert lang=sh}
262
+ geml file.geml --to html -o out.html # one self-contained, interactive HTML file
263
+ geml file.geml --to md -o out.md # GitHub-Flavored Markdown (lossy; loss notes on stderr)
264
+ geml input.md --to geml -o out.geml # Markdown -> GEML
265
+ geml file.geml --to geml # canonical re-format (idempotent)
266
+ ===
267
+
268
+ Install: `npm i -g @geml/geml` (package `@geml/geml`, command `geml`), or
269
+ one-shot via `npx -y @geml/geml <args>`. From a clone of the geml-spec repo:
270
+ `cd geml-parser && npm install && npm run build && npm link`, or run
271
+ `node geml-parser/dist/geml.js <args>` directly.
272
+
273
+ # Editing and versioning {#editing}
274
+
275
+ When revising a `.geml` over many steps, work **one block at a time** and
276
+ snapshot as you go, rather than re-emitting the whole file:
277
+
278
+ === code {#editing-loop lang=sh}
279
+ geml get file.geml '#intro' # read just this block (a heading id = its whole section)
280
+ geml set file.geml '#intro' --in - # replace just this span (stdin or --in FILE);
281
+ # the splice is re-parsed and REJECTED if it breaks the doc
282
+ geml history save file.geml -m "…" # snapshot into the .gemlhistory sidecar — do this each step
283
+ geml history get file.geml # revisions, newest first; first column IS the --rev selector
284
+ geml revert file.geml '#intro' # roll ONE block back to the previous revision (= --rev -1)
285
+ geml revert file.geml '#intro' --rev -2 # …two revisions back (also: --rev 0 = tip, --rev <id>)
286
+ geml revert file.geml '#intro' --rev changed # …the block's last ACTUAL change — use this after other
287
+ # blocks were written since; a fixed -N silently no-ops there
288
+ ===
289
+
290
+ **Retain every step.** `history` and `revert` can only recover what was saved
291
+ — so after each meaningful edit to a `.geml`, run `geml history save`
292
+ (automatable with a `PostToolUse` hook). Together, `get`/`set` (address one
293
+ block) and `history`/`revert` (version and rewind it) let an agent revise a
294
+ document incrementally and undo any single section.
295
+
296
+ **A section can be cut three ways**, on `get` and `set` alike: `--head` (the
297
+ heading line), `--intro` (what it says before its first subheading — empty when
298
+ one follows immediately, the whole body when none does), `--body` (everything
299
+ under it, so it always contains the intro). `--intro` is how you edit a
300
+ section's opening without pulling its subsections into context, and setting an
301
+ empty one writes an opening where the section had none.
302
+
303
+ **When the exact old text is already known** and nothing needs reading — a
304
+ version string in six places, a renamed term — `geml replace` is the cheap path,
305
+ and the one to prefer over dropping to `sed`: the same two short strings, but
306
+ the result is re-parsed before it lands, the blocks it touched are named back to
307
+ you, and it is in `.gemlhistory` to revert. It swaps a LITERAL, never a pattern,
308
+ and refuses a swap that would rename an id (use `geml rename`, which fixes the
309
+ references too). **It is EXPERIMENTAL and may be withdrawn** — reach for it, but
310
+ do not build anything on it that cannot change.
311
+
312
+ **A write is refused when it would BREAK the document**, never merely because it
313
+ removes something: a replacement that drops blocks is carried out and NAMED on
314
+ stderr — unnamed blocks included — with `geml revert` as the way back. Read,
315
+ edit, write back, and nothing is dropped, because `get` handed those blocks to
316
+ you. Send content that omits them only when removing them is the point.
317
+
318
+ **Where sidecars do NOT belong:** a doc that git already versions — config
319
+ docs especially — usually needs no `.gemlhistory`; do not create one there
320
+ unless the user asks for finer-than-commit history.
321
+
322
+ # Project config in GEML {#project-config}
323
+
324
+ A project's Claude instruction docs can live in GEML with **zero settings
325
+ changes and zero hooks** — quiet by design:
326
+
327
+ 1. Put the doc at `.claude/geml/<name>.geml` (e.g. `instructions.geml`), one
328
+ topic per section, each heading with a stable `{#id}` — so a single rule
329
+ can be edited (`geml set`) or removed (`geml delete`) without touching the
330
+ rest.
331
+ 2. Reference it from the project's `CLAUDE.md` with one import line:
332
+ `@.claude/geml/instructions.geml` — Claude Code expands imports of any
333
+ file type literally at session start (recursive, max 4 hops).
334
+ 3. Leave everything else in `CLAUDE.md` alone — especially tool-managed,
335
+ installer-appended sections (they are updated in place by their
336
+ installers and must stay in Markdown where those tools grep for them).
337
+ 4. The imported `.geml` must not contain bare `@path` tokens in prose — the
338
+ import scanner does not recognize GEML code fences, only Markdown ones.
339
+ 5. No `.gemlhistory` for these files: git is their history.
340
+
341
+ When asked to create or restructure project instructions, prefer this shape
342
+ without ceremony; never convert files the user did not ask about, and never
343
+ move a tool-managed block out of `CLAUDE.md`.
344
+
345
+ # Authoring checklist {#checklist}
346
+
347
+ - [ ] Every closing fence is a `=` run of exactly the opening length, or the
348
+ labeled fence `=== #id` of an id-carrying block.
349
+ - [ ] Bodies containing `===` runs are wrapped in a LONGER fence (`====`) —
350
+ a same-length bare `===` in the body closes the block early, labeled
351
+ close or not.
352
+ - [ ] Headings are ATX `#`; metadata is a `=== meta` block (no frontmatter);
353
+ the document title is `title = "…"` in meta, not an H1.
354
+ - [ ] All ids unique; all `[t](#id)` / `[[#id]]` / `[[doc.geml#id]]` /
355
+ `[^id]` / `src=` / `data=` references resolve.
356
+ - [ ] `{{key}}` keys exist in `=== meta` (code-span occurrences stay literal).
357
+ - [ ] No raw HTML; comments use `%%`, hidden content uses `{hidden}`.
358
+ - [ ] Validated: `geml check` reports zero error diagnostics (exit 0).
359
+
360
+ # Reference {#reference}
361
+
362
+ Full normative spec, in the geml-spec repo
363
+ (https://github.com/geml-spec/geml): `spec/GEML-spec.md` (English),
364
+ `spec/GEML-spec_CN.md` (中文). History sidecar: `spec/GEML-history-spec.md`.
365
+ The spec is itself written in GEML (dogfood): `spec/in_geml_format/`.