@geml/geml 1.4.2 → 1.4.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 CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 GEML contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GEML contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,155 +1,186 @@
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 has an `#id`; `geml get` / `geml set '#id'`
25
- read or patch one section without re-emitting the whole file (on this repo's
26
- own spec, ~**31× less context** than shipping the whole document).
27
- - **Verifiable** — references are checked at build time (a dangling `#id` is an
28
- error, not a silent dead link), and the parser emits a document-model JSON
29
- with a `diagnostics` array, so agents and CI get a structured pass/fail signal.
30
- - **Versioned** — `geml history` and `geml revert` snapshot and rewind
31
- revisions over a plain-text `.gemlhistory` sidecar.
32
-
33
- Try the format in the [playground](https://geml-spec.github.io/geml/playground/)
34
- — no install. Full pitch, spec, and format comparison live in the
35
- [repository](https://github.com/geml-spec/geml).
36
-
37
- ## Install
38
-
39
- ```sh
40
- npm install -g @geml/geml # global CLI — installs the `geml` command
41
- # or, per project:
42
- npm install @geml/geml # library + local bin
43
- ```
44
-
45
- Requires Node ≥ 22.
46
-
47
- ## CLI
48
-
49
- The CLI is built around one question: can a single agent author and maintain an
50
- entire `.geml` file from the command line — create, add, edit, delete, and copy
51
- blocks in from other files? Three tests keep the command set honest:
52
-
53
- - **Complete** — every step of a document's life has a verb, so an agent never
54
- rewrites the whole file to change one block.
55
- - **Ergonomic** — few flags, sensible defaults, and pipeline-friendly I/O, so
56
- multi-step edits chain without ceremony.
57
- - **Consistent** — behavior is uniform and predictable: name a target `#id` and
58
- the content adopts it, every write is guarded, a file is edited in place while
59
- `-` streams to stdout.
60
-
61
- Every command reads a file path, or `-` for stdin. Exit codes: `0` ok ·
62
- `1` document/operation error · `2` usage error.
63
-
64
- ```sh
65
- geml doc.geml # document-model JSON (default --to json)
66
- geml doc.geml --to md|html|geml # convert; geml notes.md -> GEML
67
- geml get doc.geml ['#id'] # list addressable ids, or print one block (heading id = its section)
68
- geml set doc.geml '#id' [--head|--body] [--in F[#src]] # replace a block's content (id kept)
69
- geml add doc.geml (--append|--before #id|--after #id) [--in F[#src]] # insert a fragment
70
- geml delete doc.geml '#id' ['#id2' …] # remove one or more blocks
71
- geml rename doc.geml '#old' '#new' # rename an id + every reference to it
72
- geml revert doc.geml '#id' [--rev -1] # undo a block: splice / resurrect / remove
73
- geml check doc.geml [--root <dir>] # validate only: diagnostics + exit code (--json for the array)
74
- geml history <commit|verify|show|restore|log> doc.geml [...] # .gemlhistory version sidecar
75
- geml codemap <build|verify|render|serve|refresh|find|mcp> # your codebase's call graph as GEML docs
76
- geml --help | --version # --version --json prints {"parser","spec"}
77
- ```
78
-
79
- The agent loop: `geml get` a block `set`/`add`/`delete`/`rename` it
80
- `geml check` `geml history commit` small, precise, verifiable edits.
81
-
82
- Conversion is one entry — `geml <file> [--to json|html|md|geml]`; the input
83
- format is inferred (`--from` overrides > extension > GEML), the target is `--to`
84
- (default: GEML JSON, Markdown GEML), and `-o` names the output path.
85
-
86
- `set` and `add` take their content from `--in F` (F's block whose id equals the
87
- target), `--in F#src` (F's block `#src`), or stdin (raw bytes). `set` **replaces
88
- a whole block** and normalizes the content's id to the targetso you can fork
89
- any block into this slot without hand-editing its id (`--head` swaps just the
90
- head line, `--body` just the body). `add` **inserts a fragment** (one or more
91
- blocks, or bare prose) at `--append` / `--before #id` / `--after #id`, keeping
92
- the content's own ids (a collision is refused). `delete` removes one or more
93
- ids; `rename` rewrites an id's declaration and every reference to it.
94
-
95
- Mutations (`set`/`add`/`delete`/`rename`) write the **whole updated document**:
96
- in place when the input is a file, or to **stdout** when the input is `-`; `-o`
97
- redirects the write (`-o -` forces stdout), so edits pipe cleanly. Every write
98
- is guarded re-parsed and refused if it would break the document or drop an id
99
- (a reference left dangling by `delete` is a warning, not a refusal; `geml check`
100
- flags it later).
101
-
102
- Undo is `revert`, which reconciles one block to a past revision (`--rev`, default
103
- `-1`): it **splices** back changed content, **resurrects** a deleted block (placed
104
- by its old neighbours, or `--append`/`--before`/`--after`), or **removes** a block
105
- that did not exist then. So each forward edit has an inverse:
106
-
107
- | forward edit | undo |
108
- |---|---|
109
- | `set #id` | `revert #id` (splice) |
110
- | `delete #id` | `revert #id` (resurrect) |
111
- | `add #id` | `revert #id` (remove) or `delete #id` |
112
- | `rename #old #new` | `rename #new #old` (self-inverse) |
113
-
114
- `revert` reads the `.gemlhistory` sidecar, so `set`/`delete`/`add` undo needs a
115
- prior `geml history commit`; `rename` is its own inverse and needs no history.
116
-
117
- A **heading's** `#id` addresses its whole **section** the heading line through
118
- the line before the next heading of the same-or-higher level so the prose
119
- under a heading is block-editable with no extra syntax.
120
- Spans overlap: blocks nested in the section keep their own ids, and a `set` on
121
- the section that drops one of them is refused by the guard. `get --json` on a
122
- heading covers the same content as the raw span: a section envelope
123
- `{kind:"section", id, level, blocks:[heading, …its section's blocks]}` (a
124
- block/footnote id still prints its single model node). `--head` narrows
125
- `get`/`set`/`revert` to ANY id's head line — a heading's line, or a typed
126
- block's opening fence line, so an agent renames a heading or edits a block's
127
- attributes (caption, compute, …) without touching the body. Convention: keep
128
- the document title in `=== meta` (`title = "…"`), not an H1 a lone top-level
129
- `#` section is the whole document, the telltale that it is really a title.
130
-
131
- ## Library
132
-
133
- ```js
134
- import { parse, serialize, renderHtml, gemlToMd, mdToGeml } from "@geml/geml";
135
-
136
- const doc = parse(src); // { kind:"document", children, ids, diagnostics }
137
- const ok = !doc.diagnostics.some(d => d.severity === "error");
138
- const html = renderHtml(doc); // one self-contained HTML string
139
- const md = gemlToMd(doc).md; // GitHub-Flavored Markdown (lossy)
140
- const geml = mdToGeml(markdown).geml; // the inverse
141
- const canonical = serialize(doc); // GEML text; parse(serialize(parse(x))) is stable
142
- ```
143
-
144
- `parse(src, { resolveDoc })` enables cross-document reference checking — pass a
145
- function that returns another file's source by path (or `null`).
146
-
147
- ## Documentation
148
-
149
- Full normative spec, history-sidecar spec, and format comparison live in the
150
- [repository](https://github.com/geml-spec/geml). The spec is itself
151
- written in GEML (`GEML-spec.geml`) and parsed clean on every test run.
152
-
153
- ## License
154
-
155
- 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 has an `#id`; `geml get` / `geml set '#id'`
25
+ read or patch one section without re-emitting the whole file (on this repo's
26
+ own spec, ~**31× less context** than shipping the whole document).
27
+ - **Verifiable** — references are checked at build time (a dangling `#id` is an
28
+ error, not a silent dead link), and the parser emits a document-model JSON
29
+ with a `diagnostics` array, so agents and CI get a structured pass/fail signal.
30
+ - **Versioned** — `geml history` and `geml revert` snapshot and rewind
31
+ revisions over a plain-text `.gemlhistory` sidecar.
32
+
33
+ Try the format in the [playground](https://geml-spec.github.io/geml/playground/)
34
+ — no install. Full pitch, spec, and format comparison live in the
35
+ [repository](https://github.com/geml-spec/geml).
36
+
37
+ ## Install
38
+
39
+ ```sh
40
+ npm install -g @geml/geml # global CLI — installs the `geml` command
41
+ # or, per project:
42
+ npm install @geml/geml # library + local bin
43
+ ```
44
+
45
+ Requires Node ≥ 22.
46
+
47
+ ## CLI
48
+
49
+ The CLI is built around one question: can a single agent author and maintain an
50
+ entire `.geml` file from the command line — create, add, edit, delete, and copy
51
+ blocks in from other files? Three tests keep the command set honest:
52
+
53
+ - **Complete** — every step of a document's life has a verb, so an agent never
54
+ rewrites the whole file to change one block.
55
+ - **Ergonomic** — few flags, sensible defaults, and pipeline-friendly I/O, so
56
+ multi-step edits chain without ceremony.
57
+ - **Consistent** — behavior is uniform and predictable: name a target `#id` and
58
+ the content adopts it, every write is guarded, a file is edited in place while
59
+ `-` streams to stdout.
60
+
61
+ The command set borrows from two settled models rather than inventing one, and
62
+ they overlap where it counts. **A document is a table**: a block is a row, its
63
+ `#id` is the primary key (unique per document), and `[[#id]]`/`[t](#id)`/`[^id]`/
64
+ `data=#id` are foreign keys — so `get`/`add`/`set`/`delete` are
65
+ SELECT/INSERT/UPDATE/DELETE. **A block is also a resource** at a URI-like address
66
+ (`file#id`), named before the operation, the way REST puts the noun first. Both
67
+ models agree on a small orthogonal verb set instead of a method per use case, and
68
+ they agree on idempotence: `set` and `delete` are idempotent (deleting a missing
69
+ id is a no-op, so a retry is safe), `add` is not.
70
+
71
+ Where they diverge, each covers what the other cannot. The relational view names
72
+ the integrity rules: the write guard is a constraint check with rollback, and
73
+ `delete` merely *warning* about references it leaves dangling is a **deferred**
74
+ foreign-key check, not `ON DELETE RESTRICT`. It also explains `rename`, the verb
75
+ most open to "can this be cut" a **primary-key update with a cascading
76
+ foreign-key rewrite**, irreducible because `delete` + `add` would leave every
77
+ reference dangling and nothing else rewrites references in bulk. HTTP has no
78
+ method for that at all. The REST view supplies what a database deliberately does
79
+ not: every call is **stateless** no session, no current document, no config
80
+ file, no environment variable which is what lets calls be retried,
81
+ parallelized, and piped.
82
+
83
+ Both pay off in undo. Because the verbs are orthogonal, each edit has exactly one
84
+ inverse, so `revert` never needs to know which verb made a change — it reconciles
85
+ a block to a revision in three cases (content changed, row missing, row extra)
86
+ and there is no fourth, while `rename` is its own inverse and needs no history at
87
+ all. A wider, RPC-shaped verb set (`replace`, `move`, `merge`, `split`, ) would
88
+ need a per-verb inverse and an operation log to pick onean undo-stack engine
89
+ instead of three branches. Full rationale:
90
+ [`docs/design/specs/2026-07-24-geml-block-mutation-cli-design.md`](../docs/design/specs/2026-07-24-geml-block-mutation-cli-design.md).
91
+
92
+ Every command reads a file path, or `-` for stdin. Exit codes: `0` ok ·
93
+ `1` document/operation error · `2` usage error.
94
+
95
+ ```sh
96
+ geml doc.geml # document-model JSON (default --to json)
97
+ geml doc.geml --to md|html|geml # convert; geml notes.md -> GEML
98
+ geml get doc.geml ['#id'] # list addressable ids, or print one block (heading id = its section)
99
+ geml set doc.geml '#id' [--head|--body] [--in F[#src]] # replace a block's content (id kept)
100
+ geml add doc.geml (--append|--before #id|--after #id) [--in F[#src]] # insert a fragment
101
+ geml delete doc.geml '#id' ['#id2' …] # remove one or more blocks
102
+ geml rename doc.geml '#old' '#new' # rename an id + every reference to it
103
+ geml revert doc.geml '#id' [--rev -1] # undo a block: splice / resurrect / remove
104
+ geml check doc.geml [--root <dir>] # validate only: diagnostics + exit code (--json for the array)
105
+ geml history <commit|verify|show|restore|log> doc.geml [...] # .gemlhistory version sidecar
106
+ geml codemap <build|verify|render|serve|refresh|find|mcp> # your codebase's call graph as GEML docs
107
+ geml --help | --version # --version --json prints {"parser","spec"}
108
+ ```
109
+
110
+ The agent loop: `geml get` a block `set`/`add`/`delete`/`rename` it
111
+ `geml check` `geml history commit` — small, precise, verifiable edits.
112
+
113
+ Conversion is one entry — `geml <file> [--to json|html|md|geml]`; the input
114
+ format is inferred (`--from` overrides > extension > GEML), the target is `--to`
115
+ (default: GEML JSON, Markdown GEML), and `-o` names the output path.
116
+
117
+ `set` and `add` take their content from `--in F` (F's block whose id equals the
118
+ target), `--in F#src` (F's block `#src`), or stdin (raw bytes). `set` **replaces
119
+ a whole block** and normalizes the content's id to the target — so you can fork
120
+ any block into this slot without hand-editing its id (`--head` swaps just the
121
+ head line, `--body` just the body). `add` **inserts a fragment** (one or more
122
+ blocks, or bare prose) at `--append` / `--before #id` / `--after #id`, keeping
123
+ the content's own ids (a collision is refused). `delete` removes one or more
124
+ ids; `rename` rewrites an id's declaration and every reference to it.
125
+
126
+ Mutations (`set`/`add`/`delete`/`rename`) write the **whole updated document**:
127
+ in place when the input is a file, or to **stdout** when the input is `-`; `-o`
128
+ redirects the write (`-o -` forces stdout), so edits pipe cleanly. Every write
129
+ is guarded re-parsed and refused if it would break the document or drop an id
130
+ (a reference left dangling by `delete` is a warning, not a refusal; `geml check`
131
+ flags it later).
132
+
133
+ Undo is `revert`, which reconciles one block to a past revision (`--rev`, default
134
+ `-1`): it **splices** back changed content, **resurrects** a deleted block (placed
135
+ by its old neighbours, or `--append`/`--before`/`--after`), or **removes** a block
136
+ that did not exist then. So each forward edit has an inverse:
137
+
138
+ | forward edit | undo |
139
+ |---|---|
140
+ | `set #id` | `revert #id` (splice) |
141
+ | `delete #id` | `revert #id` (resurrect) |
142
+ | `add #id` | `revert #id` (remove) — or `delete #id` |
143
+ | `rename #old #new` | `rename #new #old` (self-inverse) |
144
+
145
+ `revert` reads the `.gemlhistory` sidecar, so `set`/`delete`/`add` undo needs a
146
+ prior `geml history commit`; `rename` is its own inverse and needs no history.
147
+
148
+ A **heading's** `#id` addresses its whole **section** — the heading line through
149
+ the line before the next heading of the same-or-higher level — so the prose
150
+ under a heading is block-editable with no extra syntax.
151
+ Spans overlap: blocks nested in the section keep their own ids, and a `set` on
152
+ the section that drops one of them is refused by the guard. `get --json` on a
153
+ heading covers the same content as the raw span: a section envelope
154
+ `{kind:"section", id, level, blocks:[heading, …its section's blocks]}` (a
155
+ block/footnote id still prints its single model node). `--head` narrows
156
+ `get`/`set`/`revert` to ANY id's head line — a heading's line, or a typed
157
+ block's opening fence line, so an agent renames a heading or edits a block's
158
+ attributes (caption, compute, …) without touching the body. Convention: keep
159
+ the document title in `=== meta` (`title = "…"`), not an H1 — a lone top-level
160
+ `#` section is the whole document, the telltale that it is really a title.
161
+
162
+ ## Library
163
+
164
+ ```js
165
+ import { parse, serialize, renderHtml, gemlToMd, mdToGeml } from "@geml/geml";
166
+
167
+ const doc = parse(src); // { kind:"document", children, ids, diagnostics }
168
+ const ok = !doc.diagnostics.some(d => d.severity === "error");
169
+ const html = renderHtml(doc); // one self-contained HTML string
170
+ const md = gemlToMd(doc).md; // GitHub-Flavored Markdown (lossy)
171
+ const geml = mdToGeml(markdown).geml; // the inverse
172
+ const canonical = serialize(doc); // GEML text; parse(serialize(parse(x))) is stable
173
+ ```
174
+
175
+ `parse(src, { resolveDoc })` enables cross-document reference checking — pass a
176
+ function that returns another file's source by path (or `null`).
177
+
178
+ ## Documentation
179
+
180
+ Full normative spec, history-sidecar spec, and format comparison live in the
181
+ [repository](https://github.com/geml-spec/geml). The spec is itself
182
+ written in GEML (`GEML-spec.geml`) and parsed clean on every test run.
183
+
184
+ ## License
185
+
186
+ MIT.