@geml/geml 1.7.8 → 1.8.2
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 -431
- 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 +190 -139
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +5 -1
- package/dist/geml.js +63 -25
- package/dist/inline.d.ts +7 -1
- package/dist/inline.js +203 -137
- package/dist/mcp.js +68 -26
- package/dist/render-html.js +35 -35
- package/dist/render.js +157 -157
- package/package.json +1 -1
- package/skill/SKILL.md +167 -167
- package/skill/references/authoring.geml +369 -365
package/dist/geml.js
CHANGED
|
@@ -18,7 +18,7 @@ import { readFileSync, realpathSync } from "node:fs";
|
|
|
18
18
|
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
20
20
|
import { normalizeSource } from "./diagnostics.js";
|
|
21
|
-
import { coerce, parseAttrs } from "./attrs.js";
|
|
21
|
+
import { coerce, oddNames, parseAttrs } from "./attrs.js";
|
|
22
22
|
import { META_REF_SRC, parseInline, isSafeUrl, schemeOf } from "./inline.js";
|
|
23
23
|
import { parseTable } from "./table.js";
|
|
24
24
|
import { USES, buildChart } from "./chart.js";
|
|
@@ -91,17 +91,26 @@ function jsonErrorLine(e, text, openLineNo) {
|
|
|
91
91
|
export { SEVERITY } from "./diagnostics.js";
|
|
92
92
|
// Type registry: which body mode each typed block uses. Unknown types are a
|
|
93
93
|
// warning and fall back to `raw` (forward compatibility, §3/§8).
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
94
|
+
//
|
|
95
|
+
// A Map, not an object, because the key is the type name off a fence head — i.e.
|
|
96
|
+
// document-controlled. Indexing a plain object with it answered for the whole
|
|
97
|
+
// prototype chain: `=== constructor` (also toString, valueOf, hasOwnProperty,
|
|
98
|
+
// isPrototypeOf, propertyIsEnumerable, toLocaleString) returned an inherited
|
|
99
|
+
// FUNCTION, which is not undefined, so the unknown-block-type warning never
|
|
100
|
+
// fired and a function reached the model's `mode` field — a value the published
|
|
101
|
+
// `BodyMode` type says cannot occur, and one JSON.stringify silently drops.
|
|
102
|
+
// Every other document-keyed registry here is already a Set or a Map.
|
|
103
|
+
const REGISTRY = new Map([
|
|
104
|
+
["code", "raw"],
|
|
105
|
+
["diagram", "raw"],
|
|
106
|
+
["math", "raw"],
|
|
107
|
+
["table", "raw"], // structured table parsing lands in M3
|
|
108
|
+
["data", "raw"], // GEP-0005: value tree — a format engine parses the raw body in a second stage
|
|
109
|
+
["embed", "raw"], // block transclusion: `src=` points at the content, body unused
|
|
110
|
+
["note", "flow"],
|
|
111
|
+
["text", "flow"], // addressable prose container: an id/attrs for a run of flow, no callout chrome
|
|
112
|
+
["meta", "data"],
|
|
113
|
+
]);
|
|
105
114
|
// §7: built-in diagram renderer registry. Unknown formats are a warning (the
|
|
106
115
|
// processor keeps the body raw rather than interpreting it).
|
|
107
116
|
const DIAGRAM_RENDERERS = new Set(["mermaid", "graphviz", "dot", "d2", "plantuml", "geml-chart", "geml-code-graph"]);
|
|
@@ -133,6 +142,21 @@ export const FENCE_OPEN = /^(={3,})[ \t]+([A-Za-z][A-Za-z0-9_-]*)[ \t]*(?:(\{.*\
|
|
|
133
142
|
// Together: the group runs to the end of the line and starts at the first `{`
|
|
134
143
|
// after the last OTHER `}`. Returns the RegExpExecArray shape the call sites
|
|
135
144
|
// already destructure.
|
|
145
|
+
// A name in the attribute object that is not a NAME (§4). A WARNING, not an
|
|
146
|
+
// error: it has always parsed, documents in the wild rely on the leniency, and
|
|
147
|
+
// what the author needs is to be told — `{#a & b}` gives the id `a` and two
|
|
148
|
+
// flags called `&` and `b`, which is a legal parse of something nobody wrote.
|
|
149
|
+
function reportOddNames(a, line, diags) {
|
|
150
|
+
for (const { kind, name } of oddNames(a)) {
|
|
151
|
+
diags.push({
|
|
152
|
+
severity: "warning",
|
|
153
|
+
code: "name-not-a-name",
|
|
154
|
+
message: `${kind} \`${name}\` is not a NAME (§4: letters, digits, \`-\`, \`_\`)`
|
|
155
|
+
+ (kind === "flag" ? " — an attribute object is whitespace-separated, so a space in an id or class splits it into flags like this one" : ""),
|
|
156
|
+
line,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
136
160
|
const HEADING_HEAD = /^(#{1,6})[ \t]+/;
|
|
137
161
|
function matchHeading(line) {
|
|
138
162
|
const m = HEADING_HEAD.exec(line);
|
|
@@ -184,7 +208,7 @@ function slug(text) {
|
|
|
184
208
|
return text
|
|
185
209
|
.toLowerCase()
|
|
186
210
|
.replace(/`[^`]*`/g, "")
|
|
187
|
-
.replace(/[^\p{L}\p{N}\s
|
|
211
|
+
.replace(/[^\p{L}\p{N}\s\-_]/gu, "")
|
|
188
212
|
.trim()
|
|
189
213
|
.replace(/\s+/g, "-");
|
|
190
214
|
}
|
|
@@ -405,6 +429,7 @@ function scanBlocks(lines, base, ctx, depth = 0) {
|
|
|
405
429
|
const type = open[2];
|
|
406
430
|
const attrs = open[3] ? parseAttrs(open[3]) : { classes: [], attrs: {} };
|
|
407
431
|
const openLineNo = base + i + 1;
|
|
432
|
+
reportOddNames(attrs, openLineNo, diags);
|
|
408
433
|
// Collect the body. A block closes on the FIRST line that is a bare fence
|
|
409
434
|
// of exactly the opening length, OR — when it has an id — a labeled fence
|
|
410
435
|
// `=== #id` (a `=` run of any length ≥ 3 followed by the block's id). The
|
|
@@ -439,7 +464,7 @@ function scanBlocks(lines, base, ctx, depth = 0) {
|
|
|
439
464
|
const how = attrs.id !== undefined ? `${"=".repeat(openLen)} or \`=== #${attrs.id}\`` : "=".repeat(openLen);
|
|
440
465
|
diags.push({ severity: "error", code: "unterminated-block", message: `unterminated \`${type}\` block (no matching ${how})`, line: openLineNo });
|
|
441
466
|
}
|
|
442
|
-
let mode = REGISTRY
|
|
467
|
+
let mode = REGISTRY.get(type);
|
|
443
468
|
if (mode === undefined) {
|
|
444
469
|
diags.push({ severity: "warning", code: "unknown-block-type", message: `unknown block type \`${type}\`; body kept as raw`, line: openLineNo });
|
|
445
470
|
mode = "raw";
|
|
@@ -663,6 +688,7 @@ function scanBlocks(lines, base, ctx, depth = 0) {
|
|
|
663
688
|
const level = h[1].length;
|
|
664
689
|
const rawText = h[2];
|
|
665
690
|
const a = parseAttrs(h[3] ?? "");
|
|
691
|
+
reportOddNames(a, lineNo, diags);
|
|
666
692
|
const text = interpolate(rawText, lineNo, ctx);
|
|
667
693
|
const id = a.id ?? slug(rawText);
|
|
668
694
|
registerId(ctx, id, lineNo);
|
|
@@ -1111,8 +1137,9 @@ function gatherIds(source) {
|
|
|
1111
1137
|
}
|
|
1112
1138
|
// Pre-scan for `=== meta` blocks (at any fence depth) and merge their
|
|
1113
1139
|
// `key=val` lines, so `{{key}}` interpolation can resolve forward references.
|
|
1114
|
-
function collectMeta(lines) {
|
|
1140
|
+
function collectMeta(lines, diags) {
|
|
1115
1141
|
const meta = new Map();
|
|
1142
|
+
const firstLine = new Map(); // key → 1-based line of the defining fence
|
|
1116
1143
|
for (let i = 0; i < lines.length; i++) {
|
|
1117
1144
|
const open = FENCE_OPEN.exec(lines[i]);
|
|
1118
1145
|
if (!open || open[2] !== "meta")
|
|
@@ -1122,8 +1149,17 @@ function collectMeta(lines) {
|
|
|
1122
1149
|
let j = i + 1;
|
|
1123
1150
|
for (; j < lines.length && !isCloseFence(lines[j], len); j++)
|
|
1124
1151
|
body.push(lines[j]);
|
|
1125
|
-
for (const [k, v] of Object.entries(parseData(body)))
|
|
1126
|
-
meta.
|
|
1152
|
+
for (const [k, v] of Object.entries(parseData(body))) {
|
|
1153
|
+
if (meta.has(k)) {
|
|
1154
|
+
diags?.push({ severity: "warning", code: "duplicate-meta-key",
|
|
1155
|
+
message: `meta key \`${k}\` already defined at line ${firstLine.get(k)}; later definition at line ${i + 1} is ignored`,
|
|
1156
|
+
line: i + 1 });
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
meta.set(k, String(v));
|
|
1160
|
+
firstLine.set(k, i + 1);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1127
1163
|
i = j;
|
|
1128
1164
|
}
|
|
1129
1165
|
return meta;
|
|
@@ -1283,13 +1319,14 @@ function resolveCodeSources(ctx, opts) {
|
|
|
1283
1319
|
if (slice === null)
|
|
1284
1320
|
continue;
|
|
1285
1321
|
const hasBody = (block.raw ?? []).some((l) => l.trim() !== "");
|
|
1286
|
-
if (
|
|
1287
|
-
block
|
|
1322
|
+
if (hasBody) {
|
|
1323
|
+
// A code block must not carry both src= and an inline body (§3.3).
|
|
1324
|
+
ctx.diags.push({ severity: "error", code: "code-src-and-body",
|
|
1325
|
+
message: `code: carries both \`src=\` and an inline body; exactly one is permitted`,
|
|
1326
|
+
line });
|
|
1288
1327
|
}
|
|
1289
|
-
else
|
|
1290
|
-
|
|
1291
|
-
// Silence would let the two drift — the very thing the route prevents.
|
|
1292
|
-
ctx.diags.push({ severity: "warning", code: "stale-code-snapshot", message: `code block body differs from its source \`${target}\` — the body is a snapshot and is now out of date`, line });
|
|
1328
|
+
else {
|
|
1329
|
+
block.raw = slice;
|
|
1293
1330
|
}
|
|
1294
1331
|
}
|
|
1295
1332
|
}
|
|
@@ -1547,7 +1584,8 @@ function resolveCharts(ctx, opts) {
|
|
|
1547
1584
|
}
|
|
1548
1585
|
export function parse(source, opts = {}) {
|
|
1549
1586
|
const lines = normalizeSource(source).split("\n");
|
|
1550
|
-
const
|
|
1587
|
+
const diags = [];
|
|
1588
|
+
const ctx = { diags, ids: new Map(), refs: [], meta: collectMeta(lines, diags), resolveDoc: opts.resolveDoc };
|
|
1551
1589
|
const children = scanBlocks(lines, 0, ctx);
|
|
1552
1590
|
// Table sources first: a chart reads the build-time model of the table it
|
|
1553
1591
|
// charts, so that model has to be filled before charts are resolved.
|
|
@@ -1659,7 +1697,7 @@ units) {
|
|
|
1659
1697
|
// Only a flow body is scanned for nested blocks (raw/data bodies are
|
|
1660
1698
|
// opaque), so an id inside a `code` body is *not* addressable — exactly
|
|
1661
1699
|
// the parser's contract.
|
|
1662
|
-
if ((REGISTRY
|
|
1700
|
+
if ((REGISTRY.get(type) ?? "raw") === "flow" && depth < MAX_NESTING) {
|
|
1663
1701
|
collectSpans(lines.slice(i + 1, closed ? end - 1 : end), base + i + 1, out, ctx, depth + 1, units);
|
|
1664
1702
|
}
|
|
1665
1703
|
i = end;
|
package/dist/inline.d.ts
CHANGED
|
@@ -70,4 +70,10 @@ export interface RefSink {
|
|
|
70
70
|
export declare const META_REF_SRC = "\\{\\{\\s*([A-Za-z_][A-Za-z0-9_-]*)\\s*\\}\\}";
|
|
71
71
|
export declare function schemeOf(url: string): string | null;
|
|
72
72
|
export declare function isSafeUrl(url: string, allowDataImage?: boolean): boolean;
|
|
73
|
-
|
|
73
|
+
interface Pairs {
|
|
74
|
+
br: Int32Array;
|
|
75
|
+
pa: Int32Array;
|
|
76
|
+
off: number;
|
|
77
|
+
}
|
|
78
|
+
export declare function parseInline(s: string, line: number, sink: RefSink, depth?: number, pairs?: Pairs): Inline[];
|
|
79
|
+
export {};
|