@geml/geml 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -6
- package/codemap/build.mjs +5 -5
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +3 -3
- package/dist/geml.js +517 -250
- package/dist/history.d.ts +11 -8
- package/dist/history.js +20 -15
- package/dist/inline.js +7 -2
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +69 -20
- package/dist/render.js +1 -22
- package/dist/selector.d.ts +55 -0
- package/dist/selector.js +112 -0
- package/dist/table.d.ts +0 -4
- package/dist/table.js +57 -50
- package/dist/to-md.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,8 @@ print("hi")
|
|
|
21
21
|
===
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
- **Addressable** — every block
|
|
24
|
+
- **Addressable** — every block can be named: an `#id`, or a content address for
|
|
25
|
+
the ones nobody named; `geml get` / `geml set '<selector>'`
|
|
25
26
|
read or patch one section without re-emitting the whole file (on this repo's
|
|
26
27
|
own spec, ~**66× less context** than shipping the whole document).
|
|
27
28
|
- **Verifiable** — references are checked at build time (a dangling `#id` is an
|
|
@@ -95,20 +96,50 @@ Every command reads a file path, or `-` for stdin. Exit codes: `0` ok ·
|
|
|
95
96
|
```sh
|
|
96
97
|
geml doc.geml # document-model JSON (default --to json)
|
|
97
98
|
geml doc.geml --to md|html|geml # convert; geml notes.md -> GEML
|
|
98
|
-
geml get doc.geml ['
|
|
99
|
-
geml set doc.geml '
|
|
99
|
+
geml get doc.geml ['<selector>'] # list addressable blocks, or print what the selector matches
|
|
100
|
+
geml set doc.geml '<selector>' [--head|--body] [--in F[#src]] # replace ONE block's content
|
|
100
101
|
geml add doc.geml (--append|--before #id|--after #id) [--in F[#src]] # insert a fragment
|
|
101
102
|
geml delete doc.geml '#id' ['#id2' …] # remove one or more blocks
|
|
102
103
|
geml rename doc.geml '#old' '#new' # rename an id + every reference to it
|
|
103
104
|
geml revert doc.geml '#id' [--rev -1] # undo a block: splice / resurrect / remove
|
|
104
105
|
geml check doc.geml [--root <dir>] # validate only: diagnostics + exit code (--json for the array)
|
|
105
|
-
geml history <
|
|
106
|
+
geml history <save|get|restore|verify> doc.geml [...] # .gemlhistory version sidecar (get = list revisions, or print one)
|
|
106
107
|
geml codemap <build|verify|render|serve|refresh|find|mcp> # your codebase's call graph as GEML docs
|
|
107
108
|
geml --help | --version # --version --json prints {"parser","spec"}
|
|
108
109
|
```
|
|
109
110
|
|
|
110
111
|
The agent loop: `geml get` a block → `set`/`add`/`delete`/`rename` it →
|
|
111
|
-
`geml check` → `geml history
|
|
112
|
+
`geml check` → `geml history save` — small, precise, verifiable edits.
|
|
113
|
+
|
|
114
|
+
### Selectors
|
|
115
|
+
|
|
116
|
+
`get` and `set` take the same selector, which is a **filter over blocks**:
|
|
117
|
+
|
|
118
|
+
| Selector | Matches |
|
|
119
|
+
|---|---|
|
|
120
|
+
| *(omitted)* | nothing — `get` **lists** every addressable block, one per line, by its shortest unique address |
|
|
121
|
+
| `#id` | that block. A heading id addresses its **whole section** |
|
|
122
|
+
| `'## Heading'` | a heading line copied out of the document, resolved to its id |
|
|
123
|
+
| `'=== note'` | **every** `note` block — 0..N of them |
|
|
124
|
+
| `'=== note@a3f9c1d2'` | one block by CONTENT, for blocks that carry no `#id` |
|
|
125
|
+
| `'@a3f9c1d2'` | the same, with the type check dropped |
|
|
126
|
+
|
|
127
|
+
`get` answers with N contents when N match (document order, count on stderr);
|
|
128
|
+
`set` writes ONE block, so a selector matching several is refused (exit 2) with
|
|
129
|
+
the unique address of each candidate. `--head` is the head line, `--body` the
|
|
130
|
+
body; both round-trip — `geml get f X --body | geml set f X --body` leaves the
|
|
131
|
+
file byte-identical.
|
|
132
|
+
|
|
133
|
+
A `@<hex>` **content address** is the first 8 hex of the SHA-256 of the block's
|
|
134
|
+
own text (line endings normalized to LF, no trailing newline), with `~1`, `~2`…
|
|
135
|
+
distinguishing byte-identical blocks. Read them out of `geml get doc.geml` —
|
|
136
|
+
they are printed for every block that has no `#id`. Being content-derived, an
|
|
137
|
+
address **goes stale when the block changes** and then fails with exit 1 rather
|
|
138
|
+
than silently addressing a different block: it doubles as a precondition. That
|
|
139
|
+
also means `set` through one prints the new address on stderr. The exact hash
|
|
140
|
+
input is pinned in
|
|
141
|
+
[the selector design doc](../docs/design/specs/2026-08-04-geml-get-set-selector-design-change.md)
|
|
142
|
+
§3.4 so a second implementation computes the same values.
|
|
112
143
|
|
|
113
144
|
Conversion is one entry — `geml <file> [--to json|html|md|geml]`; the input
|
|
114
145
|
format is inferred (`--from` overrides > extension > GEML), the target is `--to`
|
|
@@ -143,7 +174,7 @@ that did not exist then. So each forward edit has an inverse:
|
|
|
143
174
|
| `rename #old #new` | `rename #new #old` (self-inverse) |
|
|
144
175
|
|
|
145
176
|
`revert` reads the `.gemlhistory` sidecar, so `set`/`delete`/`add` undo needs a
|
|
146
|
-
prior `geml history
|
|
177
|
+
prior `geml history save`; `rename` is its own inverse and needs no history.
|
|
147
178
|
|
|
148
179
|
A **heading's** `#id` addresses its whole **section** — the heading line through
|
|
149
180
|
the line before the next heading of the same-or-higher level — so the prose
|
package/codemap/build.mjs
CHANGED
|
@@ -501,7 +501,7 @@ console.error(
|
|
|
501
501
|
);
|
|
502
502
|
|
|
503
503
|
// --history: snapshot every changed document into its .gemlhistory sidecar —
|
|
504
|
-
// the graph's own architectural history (geml history
|
|
504
|
+
// the graph's own architectural history (geml history get / revert per node).
|
|
505
505
|
// Targets = documents rewritten this build, plus any document that has no
|
|
506
506
|
// sidecar yet (first run, or --history adopted later).
|
|
507
507
|
if (args.includes("--history")) {
|
|
@@ -510,7 +510,7 @@ if (args.includes("--history")) {
|
|
|
510
510
|
console.error("--history needs the built parser (cd geml-parser && npm install && npm run build)");
|
|
511
511
|
process.exit(1);
|
|
512
512
|
}
|
|
513
|
-
const {
|
|
513
|
+
const { save, isCurrent } = await import(`file://${histMod.replace(/\\/g, "/")}`);
|
|
514
514
|
const message = flag("-m", flag("--message", "graph build"));
|
|
515
515
|
const targets = new Set(stats.writtenDocs);
|
|
516
516
|
for (const d of stats.allDocs) {
|
|
@@ -518,7 +518,7 @@ if (args.includes("--history")) {
|
|
|
518
518
|
const gemlPath = join(outDir, d);
|
|
519
519
|
const sidecar = gemlPath.replace(/\.geml$/, ".gemlhistory");
|
|
520
520
|
// No sidecar yet, or the sidecar tip drifted from the file (a previous
|
|
521
|
-
//
|
|
521
|
+
// save was refused): both need a snapshot even though this build
|
|
522
522
|
// did not rewrite the document.
|
|
523
523
|
if (!existsSync(sidecar) || !isCurrent(sidecar, gemlPath)) targets.add(d);
|
|
524
524
|
}
|
|
@@ -527,10 +527,10 @@ if (args.includes("--history")) {
|
|
|
527
527
|
for (const d of [...targets].sort()) {
|
|
528
528
|
const gemlPath = join(outDir, d);
|
|
529
529
|
try {
|
|
530
|
-
|
|
530
|
+
save({ gemlPath, historyPath: gemlPath.replace(/\.geml$/, ".gemlhistory"), summary: message });
|
|
531
531
|
committed++;
|
|
532
532
|
} catch (e) {
|
|
533
|
-
// One document's history refusing a
|
|
533
|
+
// One document's history refusing a save (e.g. the round-trip gate)
|
|
534
534
|
// must not abort the build or the other documents' snapshots. The
|
|
535
535
|
// failing document's previous revision stays intact.
|
|
536
536
|
histFailed.push(d);
|
package/dist/diagnostics.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type DiagnosticCode = "unterminated-block" | "unknown-block-type" | "block-nesting-too-deep" | "list-nesting-too-deep" | "inline-nesting-too-deep" | "duplicate-id" | "unresolved-reference" | "unresolved-footnote" | "unresolved-cross-document-reference" | "unresolvable-document" | "unchecked-cross-document-reference" | "embed-missing-src" | "ignored-embed-body" | "transclusion-cycle" | "embed-target-not-geml" | "media-target-is-document" | "inline-transclusion-not-inline" | "unsafe-embed-scheme" | "
|
|
1
|
+
export type DiagnosticCode = "unterminated-block" | "unknown-block-type" | "unknown-attribute" | "block-nesting-too-deep" | "list-nesting-too-deep" | "inline-nesting-too-deep" | "duplicate-id" | "unresolved-reference" | "unresolved-footnote" | "unresolved-cross-document-reference" | "unresolvable-document" | "unchecked-cross-document-reference" | "embed-missing-src" | "ignored-embed-body" | "transclusion-cycle" | "embed-target-not-geml" | "media-target-is-document" | "inline-transclusion-not-inline" | "unsafe-embed-scheme" | "unresolvable-table-source" | "table-source-not-a-table" | "unknown-metadata-reference" | "table-src-and-body" | "unknown-table-format" | "bad-compute-formula" | "unlexable-compute-formula" | "compute-error" | "compute-non-numeric-cell" | "compute-not-a-number" | "bad-summary-entry" | "summary-unknown-column" | "unlexable-summary-expression" | "summary-error" | "unknown-diagram-format" | "ignored-diagram-body" | "code-graph-missing-src" | "code-graph-unresolvable-document" | "chart-missing-data" | "chart-data-not-a-table" | "chart-missing-type" | "chart-unknown-type" | "chart-unknown-rows-scope" | "chart-missing-channel" | "chart-empty-channel" | "chart-unknown-column" | "chart-unused-channel" | "chart-missing-summary-row" | "chart-summary-row-unavailable" | "chart-non-numeric-value";
|
|
2
2
|
export interface Diagnostic {
|
|
3
3
|
severity: "error" | "warning";
|
|
4
4
|
code: DiagnosticCode;
|
package/dist/diagnostics.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
export const SEVERITY = {
|
|
16
16
|
"unterminated-block": "error",
|
|
17
17
|
"unknown-block-type": "warning",
|
|
18
|
+
"unknown-attribute": "warning",
|
|
18
19
|
"block-nesting-too-deep": "error",
|
|
19
20
|
"list-nesting-too-deep": "error",
|
|
20
21
|
"inline-nesting-too-deep": "error",
|
|
@@ -31,7 +32,6 @@ export const SEVERITY = {
|
|
|
31
32
|
"media-target-is-document": "error",
|
|
32
33
|
"inline-transclusion-not-inline": "error",
|
|
33
34
|
"unsafe-embed-scheme": "error",
|
|
34
|
-
"source-attr-conflict": "error",
|
|
35
35
|
"unresolvable-table-source": "error",
|
|
36
36
|
"table-source-not-a-table": "error",
|
|
37
37
|
"unknown-metadata-reference": "error",
|
|
@@ -40,12 +40,12 @@ export const SEVERITY = {
|
|
|
40
40
|
"bad-compute-formula": "error",
|
|
41
41
|
"unlexable-compute-formula": "error",
|
|
42
42
|
"compute-error": "error",
|
|
43
|
+
"compute-non-numeric-cell": "warning",
|
|
44
|
+
"compute-not-a-number": "warning",
|
|
43
45
|
"bad-summary-entry": "error",
|
|
44
46
|
"summary-unknown-column": "error",
|
|
45
47
|
"unlexable-summary-expression": "error",
|
|
46
48
|
"summary-error": "error",
|
|
47
|
-
"bad-span": "error",
|
|
48
|
-
"span-outside-table": "warning",
|
|
49
49
|
"unknown-diagram-format": "warning",
|
|
50
50
|
"ignored-diagram-body": "warning",
|
|
51
51
|
"code-graph-missing-src": "warning",
|