@geml/geml 1.8.1 → 1.8.3
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/LICENSE +21 -21
- package/README.md +288 -285
- package/codemap/adapters/crg.mjs +120 -120
- package/codemap/adapters/joern.mjs +131 -131
- package/codemap/adapters/scip.mjs +658 -658
- package/codemap/browser-stub.mjs +34 -34
- package/codemap/build.mjs +629 -629
- package/codemap/cross-stack.mjs +303 -303
- package/codemap/detect.mjs +399 -399
- package/codemap/emit.mjs +510 -510
- package/codemap/entries.mjs +129 -129
- package/codemap/exclude.mjs +56 -56
- package/codemap/find.mjs +49 -49
- package/codemap/foldings.mjs +110 -110
- package/codemap/joern-export.sc +83 -83
- package/codemap/mcp-server.mjs +434 -434
- package/codemap/normalize.mjs +275 -275
- package/codemap/recipe-trust.mjs +103 -103
- package/codemap/refresh.mjs +313 -313
- package/codemap/render-all.mjs +90 -90
- package/codemap/serve.mjs +585 -585
- package/codemap/sfc-virtualize.mjs +367 -367
- package/codemap/verify.mjs +158 -158
- package/dist/attrs.d.ts +8 -0
- package/dist/attrs.js +24 -0
- package/dist/cli.js +191 -140
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +3 -0
- package/dist/geml.js +55 -22
- package/dist/mcp.js +55 -19
- package/dist/render-html.js +35 -35
- package/dist/render.js +157 -157
- package/package.json +67 -67
- package/skill/SKILL.md +167 -167
- package/skill/references/authoring.geml +369 -365
|
@@ -1,365 +1,369 @@
|
|
|
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
|
|
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: `` — 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
|
-
|
|
214
|
-
|
|
215
|
-
===
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
geml
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
geml get file.geml
|
|
227
|
-
geml
|
|
228
|
-
geml
|
|
229
|
-
geml
|
|
230
|
-
|
|
231
|
-
geml
|
|
232
|
-
geml
|
|
233
|
-
geml
|
|
234
|
-
===
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
===
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
geml file
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
geml
|
|
284
|
-
geml
|
|
285
|
-
|
|
286
|
-
geml
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
section
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
-
|
|
355
|
-
|
|
356
|
-
- [ ]
|
|
357
|
-
|
|
358
|
-
- [ ]
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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 <file> --to geml` canonicalizes: json
|
|
170
|
+
at 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: `` — 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
|
+
# EVERY read and write verb takes --root. A write is
|
|
212
|
+
# refused when the result would not parse, so a doc whose
|
|
213
|
+
# ../sibling.md links resolve only from the repo root needs
|
|
214
|
+
# it to be editable at all. `geml mcp` passes its own root.
|
|
215
|
+
===
|
|
216
|
+
|
|
217
|
+
All commands accept `-` to read from stdin.
|
|
218
|
+
|
|
219
|
+
=== code {#cli-verbs lang=sh}
|
|
220
|
+
geml file.geml # document-model JSON (default --to json)
|
|
221
|
+
geml list file.geml # CALL FIRST: every block, its address, kind, line range
|
|
222
|
+
geml find "text" file|dir # search block CONTENT -> file<TAB>address; exit 1 = no hit
|
|
223
|
+
# a NAMED file is searched whatever its extension — `list`,
|
|
224
|
+
# `get` and `find` all read Markdown, so this addresses a
|
|
225
|
+
# plain README without converting it; a DIRECTORY walks *.geml
|
|
226
|
+
geml get file.geml # same listing as `list` (the no-selector default)
|
|
227
|
+
geml get file.geml '#id' # print ONE block (raw span; --json = model node)
|
|
228
|
+
geml get file.geml '=== note' # every block of a type; '@a3f9c1d2' = a block with no #id
|
|
229
|
+
geml get file.geml 'L27-58' # position: the smallest block holding those lines
|
|
230
|
+
geml get file.geml '#sec' --intro # a section cut three ways: --head | --intro | --body
|
|
231
|
+
geml set file.geml '#id' --in f # replace ONE block (guarded: re-parsed, never writes broken)
|
|
232
|
+
geml set file.geml '#sec' --intro # replace just the opening; the subsections stay put
|
|
233
|
+
geml replace file.geml OLD NEW # EXPERIMENTAL, may be withdrawn: literal swap, checked and
|
|
234
|
+
# reported; --within '#id' or '=== type' narrows the scope
|
|
235
|
+
geml add file.geml --after '#id' --in f # insert a fragment (keeps its own ids)
|
|
236
|
+
geml delete file.geml '#id' ['#id2'] # remove one or more blocks
|
|
237
|
+
geml rename file.geml '#old' '#new' # rename an id AND every reference to it
|
|
238
|
+
===
|
|
239
|
+
|
|
240
|
+
A heading id addresses its whole SECTION (through the next same-or-higher
|
|
241
|
+
heading); `--head` narrows any id to its head line alone (rename a heading, or
|
|
242
|
+
edit a block's attributes without re-sending its body).
|
|
243
|
+
|
|
244
|
+
An `embed` block has no content of its own, so `get '#e'` returns the FRAME
|
|
245
|
+
(its `src=`). To see what the window looks onto, add `--view`:
|
|
246
|
+
|
|
247
|
+
=== code {#cli-view lang=sh}
|
|
248
|
+
geml get file.geml '#e' --view # the entity block the chain ends at
|
|
249
|
+
geml get file.geml '#e' --view --body # just its body — the usual want
|
|
250
|
+
geml get file.geml '#e' --view --json # its model node, plus `from`
|
|
251
|
+
===
|
|
252
|
+
|
|
253
|
+
`--view` resolves to the ENTITY block: multi-layer chains are followed to the
|
|
254
|
+
end, and on any block that is not an embed it changes nothing. Provenance goes
|
|
255
|
+
to stderr (`view: #e -> part.geml#tip`) because the bytes belong to ANOTHER
|
|
256
|
+
document — their refs and relative paths resolve against that one. It is
|
|
257
|
+
read-only (`set` refuses it), chain reads are confined to `--root` (default: the
|
|
258
|
+
document's own directory), and a non-local target is refused, never fetched. A
|
|
259
|
+
SECTION selector is the identity: piercing an embed inside it would splice two
|
|
260
|
+
documents' bytes together, so address that embed instead. MCP: `geml_get
|
|
261
|
+
{view: true, part: "body"}`.
|
|
262
|
+
|
|
263
|
+
Conversion is ONE entry — `geml <file> --to <format>` — not a verb per format:
|
|
264
|
+
|
|
265
|
+
=== code {#cli-convert lang=sh}
|
|
266
|
+
geml file.geml --to html -o out.html # one self-contained, interactive HTML file
|
|
267
|
+
geml file.geml --to md -o out.md # GitHub-Flavored Markdown (lossy; loss notes on stderr)
|
|
268
|
+
geml input.md --to geml -o out.geml # Markdown -> GEML
|
|
269
|
+
geml file.geml --to geml # canonical re-format (idempotent)
|
|
270
|
+
===
|
|
271
|
+
|
|
272
|
+
Install: `npm i -g @geml/geml` (package `@geml/geml`, command `geml`), or
|
|
273
|
+
one-shot via `npx -y @geml/geml <args>`. From a clone of the geml-spec repo:
|
|
274
|
+
`cd geml-parser && npm install && npm run build && npm link`, or run
|
|
275
|
+
`node geml-parser/dist/geml.js <args>` directly.
|
|
276
|
+
|
|
277
|
+
# Editing and versioning {#editing}
|
|
278
|
+
|
|
279
|
+
When revising a `.geml` over many steps, work **one block at a time** and
|
|
280
|
+
snapshot as you go, rather than re-emitting the whole file:
|
|
281
|
+
|
|
282
|
+
=== code {#editing-loop lang=sh}
|
|
283
|
+
geml get file.geml '#intro' # read just this block (a heading id = its whole section)
|
|
284
|
+
geml set file.geml '#intro' --in - # replace just this span (stdin or --in FILE);
|
|
285
|
+
# the splice is re-parsed and REJECTED if it breaks the doc
|
|
286
|
+
geml history save file.geml -m "…" # snapshot into the .gemlhistory sidecar — do this each step
|
|
287
|
+
geml history get file.geml # revisions, newest first; first column IS the --rev selector
|
|
288
|
+
geml revert file.geml '#intro' # roll ONE block back to the previous revision (= --rev -1)
|
|
289
|
+
geml revert file.geml '#intro' --rev -2 # …two revisions back (also: --rev 0 = tip, --rev <id>)
|
|
290
|
+
geml revert file.geml '#intro' --rev changed # …the block's last ACTUAL change — use this after other
|
|
291
|
+
# blocks were written since; a fixed -N silently no-ops there
|
|
292
|
+
===
|
|
293
|
+
|
|
294
|
+
**Retain every step.** `history` and `revert` can only recover what was saved
|
|
295
|
+
— so after each meaningful edit to a `.geml`, run `geml history save`
|
|
296
|
+
(automatable with a `PostToolUse` hook). Together, `get`/`set` (address one
|
|
297
|
+
block) and `history`/`revert` (version and rewind it) let an agent revise a
|
|
298
|
+
document incrementally and undo any single section.
|
|
299
|
+
|
|
300
|
+
**A section can be cut three ways**, on `get` and `set` alike: `--head` (the
|
|
301
|
+
heading line), `--intro` (what it says before its first subheading — empty when
|
|
302
|
+
one follows immediately, the whole body when none does), `--body` (everything
|
|
303
|
+
under it, so it always contains the intro). `--intro` is how you edit a
|
|
304
|
+
section's opening without pulling its subsections into context, and setting an
|
|
305
|
+
empty one writes an opening where the section had none.
|
|
306
|
+
|
|
307
|
+
**When the exact old text is already known** and nothing needs reading — a
|
|
308
|
+
version string in six places, a renamed term — `geml replace` is the cheap path,
|
|
309
|
+
and the one to prefer over dropping to `sed`: the same two short strings, but
|
|
310
|
+
the result is re-parsed before it lands, the blocks it touched are named back to
|
|
311
|
+
you, and it is in `.gemlhistory` to revert. It swaps a LITERAL, never a pattern,
|
|
312
|
+
and refuses a swap that would rename an id (use `geml rename`, which fixes the
|
|
313
|
+
references too). **It is EXPERIMENTAL and may be withdrawn** — reach for it, but
|
|
314
|
+
do not build anything on it that cannot change.
|
|
315
|
+
|
|
316
|
+
**A write is refused when it would BREAK the document**, never merely because it
|
|
317
|
+
removes something: a replacement that drops blocks is carried out and NAMED on
|
|
318
|
+
stderr — unnamed blocks included — with `geml revert` as the way back. Read,
|
|
319
|
+
edit, write back, and nothing is dropped, because `get` handed those blocks to
|
|
320
|
+
you. Send content that omits them only when removing them is the point.
|
|
321
|
+
|
|
322
|
+
**Where sidecars do NOT belong:** a doc that git already versions — config
|
|
323
|
+
docs especially — usually needs no `.gemlhistory`; do not create one there
|
|
324
|
+
unless the user asks for finer-than-commit history.
|
|
325
|
+
|
|
326
|
+
# Project config in GEML {#project-config}
|
|
327
|
+
|
|
328
|
+
A project's Claude instruction docs can live in GEML with **zero settings
|
|
329
|
+
changes and zero hooks** — quiet by design:
|
|
330
|
+
|
|
331
|
+
1. Put the doc at `.claude/geml/<name>.geml` (e.g. `instructions.geml`), one
|
|
332
|
+
topic per section, each heading with a stable `{#id}` — so a single rule
|
|
333
|
+
can be edited (`geml set`) or removed (`geml delete`) without touching the
|
|
334
|
+
rest.
|
|
335
|
+
2. Reference it from the project's `CLAUDE.md` with one import line:
|
|
336
|
+
`@.claude/geml/instructions.geml` — Claude Code expands imports of any
|
|
337
|
+
file type literally at session start (recursive, max 4 hops).
|
|
338
|
+
3. Leave everything else in `CLAUDE.md` alone — especially tool-managed,
|
|
339
|
+
installer-appended sections (they are updated in place by their
|
|
340
|
+
installers and must stay in Markdown where those tools grep for them).
|
|
341
|
+
4. The imported `.geml` must not contain bare `@path` tokens in prose — the
|
|
342
|
+
import scanner does not recognize GEML code fences, only Markdown ones.
|
|
343
|
+
5. No `.gemlhistory` for these files: git is their history.
|
|
344
|
+
|
|
345
|
+
When asked to create or restructure project instructions, prefer this shape
|
|
346
|
+
without ceremony; never convert files the user did not ask about, and never
|
|
347
|
+
move a tool-managed block out of `CLAUDE.md`.
|
|
348
|
+
|
|
349
|
+
# Authoring checklist {#checklist}
|
|
350
|
+
|
|
351
|
+
- [ ] Every closing fence is a `=` run of exactly the opening length, or the
|
|
352
|
+
labeled fence `=== #id` of an id-carrying block.
|
|
353
|
+
- [ ] Bodies containing `===` runs are wrapped in a LONGER fence (`====`) —
|
|
354
|
+
a same-length bare `===` in the body closes the block early, labeled
|
|
355
|
+
close or not.
|
|
356
|
+
- [ ] Headings are ATX `#`; metadata is a `=== meta` block (no frontmatter);
|
|
357
|
+
the document title is `title = "…"` in meta, not an H1.
|
|
358
|
+
- [ ] All ids unique; all `[t](#id)` / `[[#id]]` / `[[doc.geml#id]]` /
|
|
359
|
+
`[^id]` / `src=` / `data=` references resolve.
|
|
360
|
+
- [ ] `{{key}}` keys exist in `=== meta` (code-span occurrences stay literal).
|
|
361
|
+
- [ ] No raw HTML; comments use `%%`, hidden content uses `{hidden}`.
|
|
362
|
+
- [ ] Validated: `geml check` reports zero error diagnostics (exit 0).
|
|
363
|
+
|
|
364
|
+
# Reference {#reference}
|
|
365
|
+
|
|
366
|
+
Full normative spec, in the geml-spec repo
|
|
367
|
+
(https://github.com/geml-spec/geml): `spec/GEML-spec.md` (English),
|
|
368
|
+
`spec/GEML-spec_CN.md` (中文). History sidecar: `spec/GEML-history-spec.md`.
|
|
369
|
+
The spec is itself written in GEML (dogfood): `spec/in_geml_format/`.
|