🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@geml/geml

Package Overview
Dependencies
Maintainers
1
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@geml/geml - npm Package Compare versions

Comparing version
1.6.0
to
1.7.0
skill/references/authoring.geml

Sorry, the diff of this file is not supported yet

+82
---
name: geml
description: >-
Read, author, edit, or validate GEML — the General Expressive Markup Language
(.geml files) and its .gemlhistory versioning sidecar. Use whenever creating
or modifying a .geml/.gemlhistory file, converting Markdown to GEML, or when
the user mentions GEML, typed blocks, === fences, or geml-chart. Ensures the
output parses cleanly (zero error diagnostics) against the reference parser.
---
# Writing and reading GEML correctly
GEML expresses **every** kind of structured content — code, tables, diagrams,
math, callouts, metadata — through **one** primitive: the **typed block**
(`=== <type> {#id .class key=val}` … `===`). Always finish by **validating**: a
GEML file is correct only when `geml check` reports **no error diagnostics**
(exit 0).
## Golden rules (the things that are easy to get wrong)
1. **Fences are runs of `=` (≥3).** A block closes at a `=` run of **exactly
the opening length**, or — when the block has an `#id` — at the labeled
fence `=== #id` (any `=` run ≥3 followed by the id; no length counting).
2. **Nest with longer fences.** A body containing `===` lines needs a
**longer** outer fence: `====` wraps `===`. Careful: a same-length bare
`===` in the body closes the block even if you intend a labeled close —
the labeled close only spares you length-counting, it does NOT protect
same-length inner fences.
3. **Headings are ATX `#` only** (`#`…`######`). No setext underlines, no
`---` breaks, no YAML frontmatter — metadata is a `=== meta` block, and the
document TITLE lives there (`title = "…"`), not in an H1. A heading may
carry a stable explicit id: `## Title {#sec}`.
4. **Every `#id` is unique per document**, and **every reference must
resolve** — `[t](#id)`, `[[#id]]`, `[^id]`, `src=`, `data=`,
`other.geml#id`. An unresolved reference is a build **error**.
5. **No raw HTML.** Notes → `=== note`, comments → `%%` lines, hidden content
→ `{hidden}`, addressable prose → `=== text`, verified data → `=== data`
(json/jsonl; `code` shows text, `data` IS data).
## Validate every time
```sh
geml check file.geml # diagnostics + exit code only; exit 0 = correct
geml check --json file.geml # machine-readable diagnostics array
```
If `geml` is not on PATH: `npm i -g @geml/geml` (package `@geml/geml`, command
`geml`), or run without installing via `npx -y @geml/geml check file.geml`.
Inside the geml-spec repo prefer the local build:
`node geml-parser/dist/geml.js <args>`. If no parser is reachable, follow the
golden rules and validate once it is.
## Work blockwise (agent editing)
```sh
geml get file.geml '#id' # read ONE block (a heading id = its whole section)
geml set file.geml '#id' --in f # replace ONE block (re-parsed; never writes a broken doc)
geml history save file.geml -m "…" # snapshot to .gemlhistory after each meaningful edit
geml revert file.geml '#id' # roll ONE block back (--rev -2 | changed | <rev-id>)
```
## Full reference — pull ONE section, not the whole file
`references/authoring.geml` (under this skill's base directory) holds the
detailed reference. Fetch just the section you need:
```sh
geml get <skill-base>/references/authoring.geml '#tables'
```
| section | covers |
|---|---|
| `#typed-block` | block anatomy, attribute object, examples of every registered type |
| `#tables` | pipe/CSV bodies, `compute=`, `summary=`, printf display, `span=` merges |
| `#charts` | `geml-chart` diagrams bound to a table via `data=#id` |
| `#data` | the `data` block — value tree, `json`/`jsonl` formats, blind append, chart binding |
| `#inline` | inline markup, links/refs/footnotes, task lists, media embeds |
| `#hidden` | `%%` comments, `{hidden}`, `{{key}}` interpolation, `=== embed` |
| `#cli` | every CLI verb — get/set/add/delete/rename, `--to` conversion, check |
| `#editing` | the blockwise editing loop + `.gemlhistory` versioning |
| `#project-config` | carrying a project's Claude config docs in GEML, quietly |
| `#checklist` | full pre-flight authoring checklist |
+5
-0

@@ -15,2 +15,5 @@ // Stubs for the Node built-ins that geml-parser imports for its CLI/history

export const statSync = () => ({ isDirectory: () => false });
export const mkdirSync = () => {};
export const readdirSync = () => [];
export const copyFileSync = () => {};
export const basename = (p) => p;

@@ -25,2 +28,4 @@ export const dirname = (p) => p;

export const spawnSync = () => ({ status: 1 });
// node:os — `geml skill install` resolves the home directory; CLI-only.
export const homedir = () => "/";
export const createHash = () => ({

@@ -27,0 +32,0 @@ update() { return this; },

+10
-3

@@ -42,5 +42,12 @@ #!/usr/bin/env node

const shq = (s) => `"${String(s).replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/, '$1$1')}"`;
// A codemap document's `src=` routes are written relative to the indexed
// SOURCE root (`geml-parser/src/attrs.ts` from a document two levels down), so
// checking one has to be told that root — the same value `serve` derives, from
// the recorded recipe, falling back to the parent of the graph directory.
let srcRoot = resolve(rootDir, "..");
try { srcRoot = resolve(rootDir, JSON.parse(readFileSync(join(rootDir, "_index", "refresh.json"), "utf8")).root ?? ".."); } catch { /* no recipe: parent */ }
const checkArgs = (file) => ["check", "--root", srcRoot, file];
const runCheck = (file) => {
// The built-parser path: run node on geml.js directly (array args, no shell).
if (cli.endsWith(".js")) return spawnSync(process.execPath, [cli, "check", file], { encoding: "utf8" });
if (cli.endsWith(".js")) return spawnSync(process.execPath, [cli, ...checkArgs(file)], { encoding: "utf8" });
// A non-.js cli may be a .cmd/.bat launcher (e.g. geml.cmd on PATH), which

@@ -50,5 +57,5 @@ // Node can only spawn through the shell. Hand cmd.exe ONE pre-escaped command

if (process.platform === "win32") {
return spawnSync([q(cli), ...["check", file].map(shq)].join(" "), { encoding: "utf8", shell: true });
return spawnSync([q(cli), ...checkArgs(file).map(shq)].join(" "), { encoding: "utf8", shell: true });
}
return spawnSync(cli, ["check", file], { encoding: "utf8" });
return spawnSync(cli, checkArgs(file), { encoding: "utf8" });
};

@@ -55,0 +62,0 @@ if (!existsSync(localParser)) {

@@ -6,2 +6,3 @@ import { type DiagnosticCode } from "./diagnostics.js";

export type RowScope = "data" | "all" | "summary";
export declare const USES: Record<ChartType, Set<string>>;
export interface ChartDataset {

@@ -8,0 +9,0 @@ categories: string[];

@@ -11,3 +11,6 @@ // GEML reference parser — chart-from-table (§7 geml-chart renderer).

// Channels each type can use; supplying any other is a warning (ignored).
const USES = {
// Exported for the record-array projection (GEP-0005): it must validate only
// the channels the chart TYPE actually reads, matching buildChart's leniency
// (an unused channel is a warning here, so it must not be an error there).
export const USES = {
bar: new Set(["x", "y", "series"]),

@@ -14,0 +17,0 @@ line: new Set(["x", "y", "series"]),

@@ -1,2 +0,2 @@

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";
export type DiagnosticCode = "unterminated-block" | "unknown-block-type" | "unknown-attribute" | "block-nesting-too-deep" | "list-nesting-too-deep" | "inline-nesting-too-deep" | "stray-labeled-fence" | "fence-like-line" | "unresolvable-code-source" | "bad-code-source" | "bad-source-range" | "stale-code-snapshot" | "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-table-delimiter" | "ignored-table-delimiter" | "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" | "chart-data-not-records" | "data-parse" | "unknown-data-format" | "data-format-no-engine" | "bad-data-schema" | "data-src-and-body" | "bad-data-source" | "unresolvable-data-source";
export interface Diagnostic {

@@ -3,0 +3,0 @@ severity: "error" | "warning";

@@ -22,2 +22,8 @@ // GEML reference parser — the diagnostic catalogue (spec Appendix A).

"inline-nesting-too-deep": "error",
"stray-labeled-fence": "warning",
"fence-like-line": "warning",
"unresolvable-code-source": "warning",
"bad-code-source": "error",
"bad-source-range": "error",
"stale-code-snapshot": "warning",
"duplicate-id": "error",

@@ -41,2 +47,4 @@ "unresolved-reference": "error",

"unknown-table-format": "warning",
"bad-table-delimiter": "error",
"ignored-table-delimiter": "warning",
"bad-compute-formula": "error",

@@ -67,2 +75,10 @@ "unlexable-compute-formula": "error",

"chart-non-numeric-value": "error",
"chart-data-not-records": "error",
"data-parse": "error",
"unknown-data-format": "warning",
"data-format-no-engine": "warning",
"bad-data-schema": "error",
"data-src-and-body": "error",
"bad-data-source": "error",
"unresolvable-data-source": "error",
};

@@ -69,0 +85,0 @@ // ---------------------------------------------------------------------------

@@ -11,3 +11,3 @@ #!/usr/bin/env node

export { mdToGeml, type ConvertResult } from "./from-md.js";
export { renderHtml } from "./render-html.js";
export { renderHtml, pageAssets } from "./render-html.js";
export { type RenderOptions } from "./render.js";

@@ -17,2 +17,5 @@ export { serialize } from "./serialize.js";

export type BodyMode = "raw" | "flow" | "data";
export type DataValue = null | boolean | number | string | DataValue[] | {
[key: string]: DataValue;
};
export interface ListItem {

@@ -58,2 +61,3 @@ text: string;

chart?: ChartModel;
value?: DataValue;
hidden?: boolean;

@@ -60,0 +64,0 @@ };

@@ -287,2 +287,11 @@ #!/usr/bin/env node

},
view: {
type: "boolean",
description: "Read THROUGH an `embed` block to the entity block it stands for, following a multi-layer chain to its end. An `embed` has no content of its own, so this is the only way to see what it points at; on any other block it changes nothing. Returns {from, content}: `from` names the document the content actually came from, and its references and relative paths resolve against THAT document, not this one.",
},
part: {
type: "string",
enum: ["whole", "head", "body"],
description: "How much of the block to return (default: whole). `body` is usually what you want together with `view`.",
},
},

@@ -294,6 +303,18 @@ required: ["file", "id"],

const sel = selectorArg(args.id);
const run = runCli(["get", real, sel]);
// Same name, same enum, same validation as `geml_set` — one concept for a
// model to learn, and `body` is already taken there for the replacement text.
const part = args.part ?? "whole";
if (!["whole", "head", "body"].includes(part))
throw new Error(`part must be whole|head|body, got \`${part}\``);
const flag = part === "head" ? ["--head"] : part === "body" ? ["--body"] : [];
const run = runCli(["get", real, sel, ...flag, ...(args.view ? ["--view"] : [])]);
if (!run.ok)
throw new Error(run.stderr || `nothing matches ${sel}`);
return run.stdout;
if (!args.view)
return run.stdout;
// There is no stderr across an MCP call, and provenance is mandatory: lift
// it out of the CLI's pinned `view: <sel> -> <doc>[#<id>]` line into a
// field of its own.
const m = /^view: .*? -> (.+)$/m.exec(run.stderr);
return { from: m ? m[1].trim() : null, content: run.stdout };
},

@@ -300,0 +321,0 @@ },

import { type Document } from "./geml.js";
import { type RenderOptions } from "./render.js";
export declare function renderHtml(doc: Document, opts?: RenderOptions): string;
export declare const pageAssets: {
readonly css: "\n:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }\n* { box-sizing: border-box; }\nbody { margin:0; color:var(--fg); background:#fafbfc; font:16px/1.6 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,\"PingFang SC\",\"Microsoft Yahei\",sans-serif; }\nmain { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }\nh1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }\nh1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh3 { font-size:1.25em; } h4 { font-size:1em; }\np { margin:.7em 0; }\na { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }\ncode { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }\npre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }\npre code { background:none; padding:0; font-size:.85em; }\npre.output { background:#0d1117; color:#e6edf3; }\npre.output code { color:inherit; }\nul,ol { padding-left:1.6em; } li { margin:.2em 0; }\nul.task-list { list-style:none; padding-left:.2em; }\nli.task input[type=checkbox] { appearance:none; -webkit-appearance:none; width:1.1em; height:1.1em; margin:0 .5em 0 0; vertical-align:-.2em; border:1.5px solid #c8ccd0; border-radius:4px; background:#fff; position:relative; opacity:1; cursor:default; box-sizing:border-box; }\nli.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }\nli.task input[type=checkbox]:checked::after { content:\"✓\"; position:absolute; top:0; right:0; bottom:0; left:0; display:flex; align-items:center; justify-content:center; color:#fff; font-size:.8em; line-height:1; font-weight:700; }\naside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }\naside.aside { border-left-color:#8b949e; background:#f6f8fa; }\naside.warning { border-left-color:#d97706; background:#fff8f0; }\naside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }\nfigure { margin:1.2em 0; }\nfigcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }\ntable.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }\ntable.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }\ntable.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }\ntable.geml-table thead th::after { content:\" \\2195\"; color:var(--muted); font-size:.8em; }\ntable.geml-table thead th.asc::after { content:\" \\2191\"; color:var(--accent); }\ntable.geml-table thead th.desc::after { content:\" \\2193\"; color:var(--accent); }\ntable.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }\ntable.geml-table td.computed { color:#0a7c52; }\ntable.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }\n.table-tools { margin-bottom:6px; } .table-filter { width:240px; max-width:100%; padding:5px 9px; border:1px solid var(--bd); border-radius:7px; font-size:.85em; }\n.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }\n.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }\n.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }\n.c-title { font-size:15px; font-weight:600; fill:var(--fg); }\n.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }\n.media { max-width:100%; border-radius:8px; }\n.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }\n.math-block { overflow-x:auto; padding:.4em 0; }\nsup.fn a { font-size:.75em; }\n.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }\n.geml-footer code { font-size:.95em; }\n.code-graph { margin:1.4em 0; }\n.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }\n.cg-scroll { overflow:auto; min-height:52vh; max-height:72vh; }\n.cg-svg { display:block; }\n.cg-search-wrap { position:relative; display:inline-block; }\n.cg-search { font:12px/1.4 inherit; padding:2px 7px; border:1px solid var(--bd); border-radius:4px; background:var(--bg); color:var(--fg); min-width:13ch; }\n.cg-search-menu { position:absolute; z-index:30; top:calc(100% + 2px); left:0; min-width:24ch; max-width:52ch; max-height:52vh; overflow:auto; background:var(--bg); border:1px solid var(--bd); border-radius:6px; box-shadow:0 6px 20px rgba(0,0,0,.18); }\n.cg-search-row { display:block; width:100%; text-align:left; padding:4px 9px 4px 18px; border:0; background:none; color:var(--fg); cursor:pointer; font:12px/1.4 inherit; }\n.cg-search-row:hover { background:var(--bd); }\n.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }\n.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }\n.cg-search-grp:first-of-type { border-top:0; }\n.cg-stage { display:flex; gap:10px; align-items:flex-start; }\n.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }\n.cg-src { flex:0 0 42%; max-width:46%; display:flex; flex-direction:column; border:1px solid var(--bd); border-radius:6px; overflow:hidden; background:var(--bg); }\n.cg-src-hd { display:flex; gap:8px; align-items:center; justify-content:space-between; padding:4px 8px; border-bottom:1px solid var(--bd); color:var(--muted); font:.76em ui-monospace,Consolas,monospace; word-break:break-all; }\n.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }\n.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:72vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }\n.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }\n.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }\n.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }\n.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }\n.cg-crumb .cg-seg:hover { text-decoration:underline; }\n.cg-frame { display:block; width:100%; height:72vh; border:0; background:var(--bg); }\n.cg-flash { color:#b42318; }\n.cg-legend { display:flex; gap:14px; align-items:center; justify-content:space-between; flex-wrap:wrap; font-size:.75em; color:var(--muted); margin-top:6px; }\n.cg-upbtn { cursor:pointer; }\n.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }\n.cg-upbtn text { font-size:11px; fill:#57606a; }\n.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }\n.cg-upbtn:hover text { fill:var(--accent); }\n.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }\n.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }\n.cg-chip { display:inline-flex; align-items:center; gap:4px; }\n.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }\n.cg-note { font-size:.8em; color:#9a6700; }\n.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }\n.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }\n.cg-n { cursor:pointer; }\n.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }\n.cg-n.leaf { opacity:.45; }\n.cg-n.test rect { stroke-dasharray:3 2; }\n.cg-n.grp rect { stroke-width:1.8; }\n.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }\n.cg-e.cand { stroke-dasharray:2 3; }\n.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }\n.cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */\n.cg-e.soft { opacity:.55; }\n.cg-svg.hl .cg-n { opacity:.22; }\n.cg-svg.hl .cg-e { opacity:.1; }\n.cg-svg.hl .cg-n.hl { opacity:1; }\n.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }\n";
readonly js: "\n(function () {\n function cmp(a, b) {\n var na = a.dataset.sort, nb = b.dataset.sort;\n if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);\n return (a.textContent || \"\").localeCompare(b.textContent || \"\");\n }\n document.querySelectorAll(\"table.geml-table\").forEach(function (table) {\n var tbody = table.tBodies[0];\n if (!tbody) return;\n // Sort on header click.\n var ths = table.tHead ? table.tHead.rows[0].cells : [];\n Array.prototype.forEach.call(ths, function (th, col) {\n th.addEventListener(\"click\", function () {\n var dir = th.classList.contains(\"asc\") ? \"desc\" : \"asc\";\n Array.prototype.forEach.call(ths, function (h) { h.classList.remove(\"asc\", \"desc\"); });\n th.classList.add(dir);\n var rows = Array.prototype.slice.call(tbody.rows);\n rows.sort(function (r1, r2) {\n var c = cmp(r1.cells[col], r2.cells[col]);\n return dir === \"asc\" ? c : -c;\n });\n rows.forEach(function (r) { tbody.appendChild(r); });\n });\n });\n // Filter rows.\n var fig = table.closest(\".table-figure\");\n var input = fig ? fig.querySelector(\".table-filter\") : null;\n if (input) input.addEventListener(\"input\", function () {\n var q = input.value.toLowerCase();\n Array.prototype.forEach.call(tbody.rows, function (r) {\n r.style.display = (r.textContent || \"\").toLowerCase().indexOf(q) >= 0 ? \"\" : \"none\";\n });\n });\n });\n})();\n";
readonly codeGraphJs: string;
};

@@ -36,3 +36,3 @@ // GEML CLI HTML export — the standalone, self-contained page.

const importMap = wantLive
? `<script type="importmap">{"imports":{"node:fs":"${lg}_node-stub.js","node:path":"${lg}_node-stub.js","node:crypto":"${lg}_node-stub.js","node:url":"${lg}_node-stub.js","node:child_process":"${lg}_node-stub.js"}}</script>\n`
? `<script type="importmap">{"imports":{"node:fs":"${lg}_node-stub.js","node:path":"${lg}_node-stub.js","node:crypto":"${lg}_node-stub.js","node:url":"${lg}_node-stub.js","node:child_process":"${lg}_node-stub.js","node:os":"${lg}_node-stub.js"}}</script>\n`
: "";

@@ -94,4 +94,13 @@ const liveJs = wantLive

}
// Fragment mode: the body markup alone, for embedding into an existing
// layout. No shell, no CDN tags, no inline CSS/JS — see RenderOptions.
if (opts.fragment)
return body + "\n";
const title = opts.title ?? ctx.docTitle() ?? "GEML document";
return page(title, body, ctx, opts.source);
}
// The page shell's static assets, for fragment consumers: `css` styles every
// geml-* class a fragment emits (include once per site); `js` is the tables'
// sort/filter enhancement (once per page); `codeGraphJs` matters only when a
// fragment carries a code-graph mount. The full-page output inlines all three.
export const pageAssets = { css: CSS, js: JS, codeGraphJs: CODE_GRAPH_JS };

@@ -11,2 +11,3 @@ import { type Block, type Document } from "./geml.js";

graphSidecar?: string;
fragment?: boolean;
}

@@ -13,0 +14,0 @@ export declare function esc(s: string): string;

@@ -177,2 +177,14 @@ // GEML serializer (§ round-trip): document model -> GEML source.

}
else if (b.type === "data" && b.value !== undefined && b.attrs["src"] === undefined) {
// src= content lives in the FILE: a value loaded from it must never be
// inlined into the document by a re-format — the body stays as authored
// (normally empty), or the source of truth would silently fork.
// GEP-0005 canonical form — legal because this is DATA, not verbatim text
// (code stays byte-preserved): json pretty-prints at two spaces, jsonl is
// one compact value per line. Engine-less bodies (yaml/toml/unknown)
// never reach here (no `value`) and fall through byte-preserved.
body = String(b.attrs["format"] ?? "json") === "jsonl" && Array.isArray(b.value)
? b.value.map((v) => JSON.stringify(v))
: JSON.stringify(b.value, null, 2).split("\n");
}
else {

@@ -179,0 +191,0 @@ body = b.raw ?? [];

@@ -63,2 +63,23 @@ // GEML reference parser — Milestone 3: tables (§6).

}
// The character a data body splits on: the format's natural delimiter unless
// `delim=` names another single one — `;` for a European CSV, `|` for a
// pipe-delimited export (§6). Measured in code points, so an astral character
// counts as one. §4 attribute values carry no escape syntax, so a tab
// delimiter is spelled `format=tsv`, never `delim="\t"`; a value that is not
// exactly one character is an error and the natural delimiter is used, which
// keeps the rest of the table readable.
function resolveDelim(fmt, raw, diagnostics) {
const natural = fmt === "tsv" ? "\t" : ",";
if (raw === undefined)
return natural;
const d = String(raw);
if ([...d].length === 1)
return d;
diagnostics.push({
severity: "error",
code: "bad-table-delimiter",
message: `\`delim="${d}"\` must be exactly one character (for a tab use \`format=tsv\`); split on ${fmt === "tsv" ? "a tab" : "`,`"} instead`,
});
return natural;
}
function letters(n) {

@@ -275,3 +296,3 @@ const out = [];

const header = headerAttr === undefined ? true : headerAttr === true || headerAttr === 1 || headerAttr === "1";
raw = parseDelimited(body, fmt === "tsv" ? "\t" : ",", header);
raw = parseDelimited(body, resolveDelim(fmt, attrs["delim"], diagnostics), header);
}

@@ -281,2 +302,7 @@ else {

diagnostics.push({ severity: "warning", code: "unknown-table-format", message: `unknown table format \`${fmt}\`; parsed as visual grid` });
// `delim` refines the data form; it does not select it. Silently dropping it
// would leave a `=== table {delim=";"}` parsed as a one-column visual grid
// with nothing to say why.
if (attrs["delim"] !== undefined)
diagnostics.push({ severity: "warning", code: "ignored-table-delimiter", message: "`delim` applies to a data body (`format=csv`/`tsv`); this body was parsed as a visual grid, so it is ignored" });
raw = parseVisual(body);

@@ -283,0 +309,0 @@ }

@@ -147,2 +147,7 @@ // GEML -> Markdown projection (the inverse direction of from-md.ts).

return fence(attr(b, "lang") ?? "", raw);
// GEP-0005: a data block projects as a fenced code block in its format —
// the nearest GFM shape (Markdown has no verified-data construct; that loss
// is the usual --to md lossiness, not a defect of the projection).
if (b.type === "data")
return fence(attr(b, "format") ?? "json", raw);
if (b.type === "math")

@@ -149,0 +154,0 @@ return ["$$", ...raw, "$$"].join("\n");

{
"name": "@geml/geml",
"version": "1.6.0",
"version": "1.7.0",
"mcpName": "io.github.geml-spec/geml",

@@ -18,2 +18,3 @@ "publishConfig": {

"codemap",
"skill",
"README.md",

@@ -20,0 +21,0 @@ "LICENSE"

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display