@geml/geml 1.5.0 → 1.5.1
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 +217 -217
- 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 +29 -29
- package/codemap/build.mjs +609 -609
- package/codemap/cross-stack.mjs +303 -303
- package/codemap/detect.mjs +399 -399
- package/codemap/emit.mjs +480 -480
- package/codemap/entries.mjs +129 -129
- package/codemap/exclude.mjs +52 -52
- package/codemap/find.mjs +49 -49
- package/codemap/foldings.mjs +110 -110
- package/codemap/joern-export.sc +83 -83
- package/codemap/mcp-server.mjs +431 -431
- package/codemap/normalize.mjs +275 -275
- package/codemap/recipe-trust.mjs +103 -103
- package/codemap/refresh.mjs +310 -310
- package/codemap/render-all.mjs +77 -77
- package/codemap/serve.mjs +585 -585
- package/codemap/sfc-virtualize.mjs +367 -367
- package/codemap/verify.mjs +148 -148
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +3 -3
- package/dist/geml.js +156 -128
- package/dist/inline.js +7 -2
- package/dist/mcp.js +19 -19
- package/dist/render-html.js +35 -35
- package/dist/render.js +137 -158
- package/dist/table.d.ts +0 -4
- package/dist/table.js +57 -50
- package/dist/to-md.js +4 -1
- package/package.json +66 -66
package/dist/geml.js
CHANGED
|
@@ -154,7 +154,7 @@ function registerId(ctx, id, line) {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
// §5: a list marker — `-`/`*` (unordered) or `N.` (ordered) — capturing the
|
|
157
|
-
// leading indent (in spaces; a tab counts as
|
|
157
|
+
// leading indent (in spaces; a tab counts as 4) and the item content. Nesting
|
|
158
158
|
// is decided by that indent.
|
|
159
159
|
const MARKER = /^([ \t]*)(?:[-*]|(\d+)\.)[ \t]+(.*)$/;
|
|
160
160
|
function matchMarker(line) {
|
|
@@ -162,7 +162,14 @@ function matchMarker(line) {
|
|
|
162
162
|
if (!m)
|
|
163
163
|
return null;
|
|
164
164
|
const ordered = m[2] !== undefined;
|
|
165
|
-
|
|
165
|
+
let indent = 0;
|
|
166
|
+
for (const ch of m[1]) {
|
|
167
|
+
if (ch === '\t')
|
|
168
|
+
indent += 4;
|
|
169
|
+
else
|
|
170
|
+
indent += 1;
|
|
171
|
+
}
|
|
172
|
+
const mk = { indent, ordered, rest: m[3] };
|
|
166
173
|
if (ordered)
|
|
167
174
|
mk.start = parseInt(m[2], 10);
|
|
168
175
|
return mk;
|
|
@@ -249,7 +256,26 @@ function scanBlocks(lines, base, ctx, depth = 0) {
|
|
|
249
256
|
const diags = ctx.diags;
|
|
250
257
|
let i = 0;
|
|
251
258
|
while (i < lines.length) {
|
|
252
|
-
|
|
259
|
+
let line = lines[i];
|
|
260
|
+
let consumed = 1;
|
|
261
|
+
// C-01: Attribute line continuation via `\`.
|
|
262
|
+
// If a line looks like a fence or heading and ends with `\`, fold subsequent lines.
|
|
263
|
+
if ((line.startsWith("===") || line.startsWith("#")) && line.endsWith("\\")) {
|
|
264
|
+
let folded = line.slice(0, -1).trimEnd();
|
|
265
|
+
while (i + consumed < lines.length) {
|
|
266
|
+
const next = lines[i + consumed].trim();
|
|
267
|
+
if (next.endsWith("\\")) {
|
|
268
|
+
folded += " " + next.slice(0, -1).trimEnd();
|
|
269
|
+
consumed++;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
folded += " " + next;
|
|
273
|
+
consumed++;
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
line = folded;
|
|
278
|
+
}
|
|
253
279
|
if (line.trim() === "") {
|
|
254
280
|
i++;
|
|
255
281
|
continue;
|
|
@@ -259,24 +285,7 @@ function scanBlocks(lines, base, ctx, depth = 0) {
|
|
|
259
285
|
const hid = /^[ \t]*%%[ \t]?(.*)$/.exec(line);
|
|
260
286
|
if (hid) {
|
|
261
287
|
blocks.push({ kind: "hidden", text: hid[1] });
|
|
262
|
-
i
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
// §5.2: a Markdown-style footnote definition `[^id]: text` defines the
|
|
266
|
-
// target a `[^id]` reference points at — recorded as a note block with that
|
|
267
|
-
// id, so the reference resolves. (A model that reaches for Markdown
|
|
268
|
-
// footnotes by habit then "just works" instead of leaving a dangling ref.)
|
|
269
|
-
const fndef = /^\[\^([^\]]+)\]:[ \t]?(.*)$/.exec(line);
|
|
270
|
-
if (fndef) {
|
|
271
|
-
const id = fndef[1].trim();
|
|
272
|
-
const lineNo = base + i + 1;
|
|
273
|
-
registerId(ctx, id, lineNo);
|
|
274
|
-
const text = interpolate(fndef[2], lineNo, ctx);
|
|
275
|
-
blocks.push({
|
|
276
|
-
kind: "block", type: "note", mode: "flow", id, classes: ["footnote"], attrs: {},
|
|
277
|
-
children: [{ kind: "paragraph", text, inlines: parseInline(text, lineNo, ctx) }],
|
|
278
|
-
});
|
|
279
|
-
i++;
|
|
288
|
+
i += consumed;
|
|
280
289
|
continue;
|
|
281
290
|
}
|
|
282
291
|
const open = FENCE_OPEN.exec(line);
|
|
@@ -292,7 +301,7 @@ function scanBlocks(lines, base, ctx, depth = 0) {
|
|
|
292
301
|
// safe way to nest (§3).
|
|
293
302
|
const labeled = attrs.id !== undefined ? new RegExp(`^={3,}[ \\t]+#${reLit(attrs.id)}[ \\t]*$`) : null;
|
|
294
303
|
const body = [];
|
|
295
|
-
let j = i +
|
|
304
|
+
let j = i + consumed;
|
|
296
305
|
let closed = false;
|
|
297
306
|
for (; j < lines.length; j++) {
|
|
298
307
|
if (isCloseFence(lines[j], openLen) || (labeled && labeled.test(lines[j]))) {
|
|
@@ -310,6 +319,31 @@ function scanBlocks(lines, base, ctx, depth = 0) {
|
|
|
310
319
|
diags.push({ severity: "warning", code: "unknown-block-type", message: `unknown block type \`${type}\`; body kept as raw`, line: openLineNo });
|
|
311
320
|
mode = "raw";
|
|
312
321
|
}
|
|
322
|
+
else {
|
|
323
|
+
// `hidden` (§4) and `caption` (§4, and the label an auto-reference takes
|
|
324
|
+
// per §5.2) are not type-specific: every typed block may carry them. Only
|
|
325
|
+
// the extras below are per type.
|
|
326
|
+
let validRe;
|
|
327
|
+
if (type === "table")
|
|
328
|
+
validRe = /^(src|format|header|format-data|compute\d*|summary\d*|span\d*)$/;
|
|
329
|
+
else if (type === "embed")
|
|
330
|
+
validRe = /^(src)$/;
|
|
331
|
+
else if (type === "diagram")
|
|
332
|
+
validRe = /^(src|data|format|type|rows|x|y|size|series)$/;
|
|
333
|
+
// `src`/`anchor` on a `code` block are the code-graph profile's
|
|
334
|
+
// (docs/codemap-profile.md): every document `geml codemap build` writes
|
|
335
|
+
// carries them, so warning on them would warn on our own output.
|
|
336
|
+
else if (type === "code")
|
|
337
|
+
validRe = /^(lang|src|anchor|name|entry-via)$/;
|
|
338
|
+
else
|
|
339
|
+
validRe = /^$/;
|
|
340
|
+
const universal = /^(hidden|caption)$/;
|
|
341
|
+
for (const key of Object.keys(attrs.attrs)) {
|
|
342
|
+
if (!universal.test(key) && !validRe.test(key)) {
|
|
343
|
+
diags.push({ severity: "warning", code: "unknown-attribute", message: `unknown attribute \`${key}\` for block type \`${type}\``, line: openLineNo });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
313
347
|
const block = {
|
|
314
348
|
kind: "block", type, mode, classes: attrs.classes, attrs: attrs.attrs,
|
|
315
349
|
};
|
|
@@ -382,18 +416,11 @@ function scanBlocks(lines, base, ctx, depth = 0) {
|
|
|
382
416
|
else {
|
|
383
417
|
block.raw = body;
|
|
384
418
|
if (type === "table") {
|
|
385
|
-
// `src=` and `data=` are one attribute in two spellings: where this
|
|
386
|
-
// table's data comes from. Recorded for the post-scan pass.
|
|
387
419
|
const srcAttr = typeof attrs.attrs["src"] === "string" ? attrs.attrs["src"].trim() : undefined;
|
|
388
|
-
const dataAttr = typeof attrs.attrs["data"] === "string" ? attrs.attrs["data"].trim() : undefined;
|
|
389
|
-
if (srcAttr !== undefined && dataAttr !== undefined) {
|
|
390
|
-
diags.push({ severity: "error", code: "source-attr-conflict", message: "table has both `src=` and `data=`; they mean the same thing — use one", line: openLineNo });
|
|
391
|
-
}
|
|
392
|
-
const target = srcAttr ?? dataAttr;
|
|
393
420
|
// §6: parse the raw body (visual or csv/tsv) into one table model.
|
|
394
|
-
const { model, diagnostics } = parseTable(body,
|
|
395
|
-
if (
|
|
396
|
-
(ctx.tableSources ??= []).push({ block, line: openLineNo, target });
|
|
421
|
+
const { model, diagnostics } = parseTable(body, attrs.attrs, openLineNo, ctx);
|
|
422
|
+
if (srcAttr !== undefined)
|
|
423
|
+
(ctx.tableSources ??= []).push({ block, line: openLineNo, target: srcAttr });
|
|
397
424
|
block.table = model;
|
|
398
425
|
for (const d of diagnostics)
|
|
399
426
|
diags.push({ ...d, line: openLineNo });
|
|
@@ -442,9 +469,10 @@ function scanBlocks(lines, base, ctx, depth = 0) {
|
|
|
442
469
|
if (h) {
|
|
443
470
|
const lineNo = base + i + 1;
|
|
444
471
|
const level = h[1].length;
|
|
445
|
-
const
|
|
446
|
-
const
|
|
447
|
-
const
|
|
472
|
+
const rawText = h[2];
|
|
473
|
+
const a = parseAttrs(h[3] ?? "");
|
|
474
|
+
const text = interpolate(rawText, lineNo, ctx);
|
|
475
|
+
const id = a.id ?? slug(rawText);
|
|
448
476
|
registerId(ctx, id, lineNo);
|
|
449
477
|
const block = {
|
|
450
478
|
kind: "heading", level, text, inlines: parseInline(text, lineNo, ctx), id, classes: a.classes, attrs: a.attrs,
|
|
@@ -452,7 +480,7 @@ function scanBlocks(lines, base, ctx, depth = 0) {
|
|
|
452
480
|
if (a.attrs["hidden"] === true)
|
|
453
481
|
block.hidden = true;
|
|
454
482
|
blocks.push(block);
|
|
455
|
-
i
|
|
483
|
+
i += consumed;
|
|
456
484
|
continue;
|
|
457
485
|
}
|
|
458
486
|
if (LIST_ITEM.test(line)) {
|
|
@@ -711,7 +739,7 @@ function gatherEmbeds(source) {
|
|
|
711
739
|
scanBlocks(normalizeSource(source).split("\n"), 0, ctx);
|
|
712
740
|
return (ctx.embeds ?? []).map((e) => (e.anchor === undefined ? { doc: e.doc } : { doc: e.doc, anchor: e.anchor }));
|
|
713
741
|
}
|
|
714
|
-
// One rule for "where this data comes from", shared by a table's `src
|
|
742
|
+
// One rule for "where this data comes from", shared by a table's `src=`
|
|
715
743
|
// and a chart's `data=`. Three target forms: a data file, `#id` naming a table
|
|
716
744
|
// block in this document, or `doc.geml#id` naming one in another document. An
|
|
717
745
|
// unresolvable target is an error — a table whose source silently produced no
|
|
@@ -773,11 +801,10 @@ function resolveTableSources(ctx, opts) {
|
|
|
773
801
|
err(line, "unresolvable-table-source", `cannot resolve table source \`${target}\``);
|
|
774
802
|
continue;
|
|
775
803
|
}
|
|
776
|
-
// Reuse the body parser: with `src
|
|
804
|
+
// Reuse the body parser: with `src` dropped, the file's lines are just
|
|
777
805
|
// this table's body, so format/header/compute/summary all behave identically.
|
|
778
806
|
const attrs = { ...block.attrs };
|
|
779
807
|
delete attrs["src"];
|
|
780
|
-
delete attrs["data"];
|
|
781
808
|
const { model, diagnostics } = parseTable(normalizeSource(text).split("\n"), attrs, line, ctx);
|
|
782
809
|
model.src = target;
|
|
783
810
|
block.table = model;
|
|
@@ -1004,13 +1031,11 @@ export function parse(source, opts = {}) {
|
|
|
1004
1031
|
}
|
|
1005
1032
|
// The id that a fence/heading line defines, matching how scanBlocks derives it
|
|
1006
1033
|
// (parseAttrs for the attribute object; heading text slug when no explicit id).
|
|
1007
|
-
// The slug MUST come from the
|
|
1008
|
-
//
|
|
1009
|
-
//
|
|
1010
|
-
// make the real one unaddressable. `ctx` is an inert context carrying the
|
|
1011
|
-
// document's meta (diagnostics are discarded — spans never report).
|
|
1034
|
+
// The slug MUST come from the RAW text, before interpolation, so that changing
|
|
1035
|
+
// a meta variable does not silently change the block's addressable id.
|
|
1036
|
+
// `ctx` is passed just in case future features need context.
|
|
1012
1037
|
function idOfHeading(braces, text, line, ctx) {
|
|
1013
|
-
return (braces ? parseAttrs(braces).id : undefined) ?? slug(
|
|
1038
|
+
return (braces ? parseAttrs(braces).id : undefined) ?? slug(text);
|
|
1014
1039
|
}
|
|
1015
1040
|
// The matching close of the fence opened at lines[i] (equal-length run, or the
|
|
1016
1041
|
// labeled `=== #id` close when the block carries an id): the index just past
|
|
@@ -1047,7 +1072,7 @@ function sectionEnd(lines, i, level) {
|
|
|
1047
1072
|
}
|
|
1048
1073
|
// Walk `lines` exactly as scanBlocks does — same fence close rules (equal-length
|
|
1049
1074
|
// or labeled `=== #id`), same flow-only recursion via REGISTRY — recording the
|
|
1050
|
-
// source span of every addressable id (typed block, heading
|
|
1075
|
+
// source span of every addressable id (typed block, heading).
|
|
1051
1076
|
// First definition wins, mirroring ctx.ids (a duplicate id is a build error, so
|
|
1052
1077
|
// `get`/`set` operate on the one the parser actually registered). `base` is the
|
|
1053
1078
|
// absolute line offset of this slice within the whole document.
|
|
@@ -1115,8 +1140,8 @@ types) {
|
|
|
1115
1140
|
export function blockSpans(source) {
|
|
1116
1141
|
const out = new Map();
|
|
1117
1142
|
const lines = normalizeSource(source).split("\n");
|
|
1118
|
-
// Inert context: heading auto-ids slug the
|
|
1119
|
-
//
|
|
1143
|
+
// Inert context: heading auto-ids slug the raw text, but parseDoc still
|
|
1144
|
+
// requires a valid context to parse the document.
|
|
1120
1145
|
const ctx = { diags: [], ids: new Map(), refs: [], meta: collectMeta(lines) };
|
|
1121
1146
|
collectSpans(lines, 0, out, ctx);
|
|
1122
1147
|
return out;
|
|
@@ -1153,8 +1178,8 @@ function toNewline(text, nl) {
|
|
|
1153
1178
|
return nl === "\n" ? lf : lf.replace(/\n/g, nl);
|
|
1154
1179
|
}
|
|
1155
1180
|
// `--head`: narrow any id's span to its HEAD line — the single declaring line
|
|
1156
|
-
// (a heading's `# … {#id}` line, a typed block's opening fence
|
|
1157
|
-
//
|
|
1181
|
+
// (a heading's `# … {#id}` line, or a typed block's opening fence). The head is
|
|
1182
|
+
// by construction the FIRST line of the span, so
|
|
1158
1183
|
// the narrowing is parse-free and needs no type check. Main use: `set --head`
|
|
1159
1184
|
// edits a block's attributes (caption/compute/lang/…) without re-sending its
|
|
1160
1185
|
// body, or renames a heading without rewriting its section.
|
|
@@ -1247,54 +1272,54 @@ export const PARSER_VERSION = (() => {
|
|
|
1247
1272
|
}
|
|
1248
1273
|
return "0.0.0";
|
|
1249
1274
|
})();
|
|
1250
|
-
const USAGE = `geml — GEML reference CLI
|
|
1251
|
-
|
|
1252
|
-
Usage:
|
|
1253
|
-
geml <file.geml|-> [--to <fmt>] [--from <fmt>] [--root d] [-o out] transform a document (default: --to json)
|
|
1254
|
-
(--root widens cross-doc resolution to dir d, as on check — an
|
|
1255
|
-
=== embed whose target sits above the file's own directory
|
|
1256
|
-
needs it, or it renders unresolved)
|
|
1257
|
-
--to <output>: json | html | md | geml
|
|
1258
|
-
--to md -> Markdown (lossy)
|
|
1259
|
-
--to html -> self-contained HTML
|
|
1260
|
-
--to geml -> canonical re-format
|
|
1261
|
-
--to json -> document-model JSON (default)
|
|
1262
|
-
--from <input>: geml | md | json (overrides extension; html is output-only)
|
|
1263
|
-
geml notes.md -> GEML (md inferred from extension)
|
|
1264
|
-
geml model.json --to geml -> GEML (round-trips a prior --to json)
|
|
1265
|
-
geml - --from md read Markdown on stdin
|
|
1266
|
-
geml get <file.geml|-> [#id] [--json] [--head] with #id: print that block
|
|
1267
|
-
(a heading id = its whole section; --head = head line;
|
|
1268
|
-
--json = model node). Without #id: list all addressable
|
|
1269
|
-
ids (--json = array).
|
|
1270
|
-
geml set <file.geml|-> #id [--head|--body] [--in f[#src]|-] [-o f] replace ONE block by id
|
|
1271
|
-
(--in F takes F's block #id, F#src takes #src, else stdin raw;
|
|
1272
|
-
default = whole block · --head = head line · --body = body)
|
|
1273
|
-
geml add <file.geml|-> (--append | --before #id | --after #id) [--in f[#src]|-] [-o f] insert a fragment
|
|
1274
|
-
(1+ blocks and/or prose; content keeps its own ids, a clash is refused)
|
|
1275
|
-
geml delete <file.geml|-> #id [#id2 …] [-o f] remove one or more blocks
|
|
1276
|
-
(a missing id is skipped; a dangling reference is a warning, not a refusal)
|
|
1277
|
-
geml rename <file.geml|-> #old #new [-o f] rename an id and every reference to it (id-boundary safe)
|
|
1278
|
-
geml revert <file.geml> #id [--rev <sel>] [--head] undo one block to a past revision (splice / resurrect / remove)
|
|
1279
|
-
(sel: 0 | -N | id-prefix | changed; default -1)
|
|
1280
|
-
geml check <file.geml|-> [--root d] [--json] validate only: diagnostics + exit code
|
|
1281
|
-
(--root widens cross-doc refs to dir d, e.g. the repo root)
|
|
1282
|
-
geml history <commit|verify|show|restore|log> <file.geml> [...] .gemlhistory version sidecar
|
|
1283
|
-
geml codemap <build|verify|render|serve|refresh|find> [...] code-graph toolkit (alias: codegraph)
|
|
1284
|
-
geml mcp --root <dir> [--graph <dir>] [--no-history] serve documents (and the code graph) over MCP (stdio)
|
|
1285
|
-
(10 tools, each geml_ + its CLI verb: list/get/check/history/to +
|
|
1286
|
-
set/add/delete/rename/revert; every write is validated before it
|
|
1287
|
-
reaches disk. A code graph under --root adds four read-only
|
|
1288
|
-
geml_codemap_* tools to the same server)
|
|
1289
|
-
geml --help | --version [--json]
|
|
1290
|
-
|
|
1291
|
-
Use '-' as the file to read from stdin.
|
|
1292
|
-
Mutations (set/add/delete/rename) write the whole updated document in place for a
|
|
1293
|
-
file, or to stdout for '-' input; -o redirects it (-o - = stdout).
|
|
1294
|
-
Exit codes:
|
|
1295
|
-
0 ok
|
|
1296
|
-
1 document/operation error
|
|
1297
|
-
2 command usage error.
|
|
1275
|
+
const USAGE = `geml — GEML reference CLI
|
|
1276
|
+
|
|
1277
|
+
Usage:
|
|
1278
|
+
geml <file.geml|-> [--to <fmt>] [--from <fmt>] [--root d] [-o out] transform a document (default: --to json)
|
|
1279
|
+
(--root widens cross-doc resolution to dir d, as on check — an
|
|
1280
|
+
=== embed whose target sits above the file's own directory
|
|
1281
|
+
needs it, or it renders unresolved)
|
|
1282
|
+
--to <output>: json | html | md | geml
|
|
1283
|
+
--to md -> Markdown (lossy)
|
|
1284
|
+
--to html -> self-contained HTML
|
|
1285
|
+
--to geml -> canonical re-format
|
|
1286
|
+
--to json -> document-model JSON (default)
|
|
1287
|
+
--from <input>: geml | md | json (overrides extension; html is output-only)
|
|
1288
|
+
geml notes.md -> GEML (md inferred from extension)
|
|
1289
|
+
geml model.json --to geml -> GEML (round-trips a prior --to json)
|
|
1290
|
+
geml - --from md read Markdown on stdin
|
|
1291
|
+
geml get <file.geml|-> [#id] [--json] [--head] with #id: print that block
|
|
1292
|
+
(a heading id = its whole section; --head = head line;
|
|
1293
|
+
--json = model node). Without #id: list all addressable
|
|
1294
|
+
ids (--json = array).
|
|
1295
|
+
geml set <file.geml|-> #id [--head|--body] [--in f[#src]|-] [-o f] replace ONE block by id
|
|
1296
|
+
(--in F takes F's block #id, F#src takes #src, else stdin raw;
|
|
1297
|
+
default = whole block · --head = head line · --body = body)
|
|
1298
|
+
geml add <file.geml|-> (--append | --before #id | --after #id) [--in f[#src]|-] [-o f] insert a fragment
|
|
1299
|
+
(1+ blocks and/or prose; content keeps its own ids, a clash is refused)
|
|
1300
|
+
geml delete <file.geml|-> #id [#id2 …] [-o f] remove one or more blocks
|
|
1301
|
+
(a missing id is skipped; a dangling reference is a warning, not a refusal)
|
|
1302
|
+
geml rename <file.geml|-> #old #new [-o f] rename an id and every reference to it (id-boundary safe)
|
|
1303
|
+
geml revert <file.geml> #id [--rev <sel>] [--head] undo one block to a past revision (splice / resurrect / remove)
|
|
1304
|
+
(sel: 0 | -N | id-prefix | changed; default -1)
|
|
1305
|
+
geml check <file.geml|-> [--root d] [--json] validate only: diagnostics + exit code
|
|
1306
|
+
(--root widens cross-doc refs to dir d, e.g. the repo root)
|
|
1307
|
+
geml history <commit|verify|show|restore|log> <file.geml> [...] .gemlhistory version sidecar
|
|
1308
|
+
geml codemap <build|verify|render|serve|refresh|find> [...] code-graph toolkit (alias: codegraph)
|
|
1309
|
+
geml mcp --root <dir> [--graph <dir>] [--no-history] serve documents (and the code graph) over MCP (stdio)
|
|
1310
|
+
(10 tools, each geml_ + its CLI verb: list/get/check/history/to +
|
|
1311
|
+
set/add/delete/rename/revert; every write is validated before it
|
|
1312
|
+
reaches disk. A code graph under --root adds four read-only
|
|
1313
|
+
geml_codemap_* tools to the same server)
|
|
1314
|
+
geml --help | --version [--json]
|
|
1315
|
+
|
|
1316
|
+
Use '-' as the file to read from stdin.
|
|
1317
|
+
Mutations (set/add/delete/rename) write the whole updated document in place for a
|
|
1318
|
+
file, or to stdout for '-' input; -o redirects it (-o - = stdout).
|
|
1319
|
+
Exit codes:
|
|
1320
|
+
0 ok
|
|
1321
|
+
1 document/operation error
|
|
1322
|
+
2 command usage error.
|
|
1298
1323
|
`;
|
|
1299
1324
|
// One-line usage for each subcommand — the single source for both the error
|
|
1300
1325
|
// shown on misuse and the `<cmd> --help` text.
|
|
@@ -1307,35 +1332,35 @@ const SUBHELP = {
|
|
|
1307
1332
|
check: "usage: geml check <file.geml|-> [--root <dir>] [--json] (--root: resolve cross-doc refs within <dir> instead of the file's own directory)",
|
|
1308
1333
|
revert: "usage: geml revert <file.geml> #id [--rev <sel>] [--append|--before #x|--after #x] [--head] [--dry-run] [-o out] (reconcile #id to a revision: splice / resurrect / remove; sel: 0 | -N | id-prefix | changed; default -1)",
|
|
1309
1334
|
history: "usage: geml history <commit|verify|show|restore|log> <file.geml> [...]",
|
|
1310
|
-
codemap: `usage: geml codemap build [--root <repo>] # auto-detect languages, run the indexer(s), and merge into one codemap (--root defaults to the current directory)
|
|
1311
|
-
geml codemap build (--db <graph.db> | --adapter joern|scip --raw <in>)+ [--root <repo>] [--out .geml-code-graph] [--container module|dir|file] [--lang <JAVASRC|NEWC|…>] [--joern <path>] [--history [-m msg]]
|
|
1312
|
-
geml codemap verify [dir] geml check + profile reference checks
|
|
1313
|
-
geml codemap render [dir] every doc -> sibling .html (open index.html from disk)
|
|
1314
|
-
geml codemap serve [dir] [--port 8140] [--watch] [--background|--stop] live viewer: pages render from .geml on request; --watch re-runs the recipe when sources change
|
|
1315
|
-
geml codemap refresh [dir] [--force] [--commit] [--background|--hook] re-run the recorded build recipe (_index/refresh.json); --commit lands it as its own commit
|
|
1316
|
-
geml codemap find <name> [dir] locate a symbol by substring name -> doc#id + src (stdout, no browser)
|
|
1335
|
+
codemap: `usage: geml codemap build [--root <repo>] # auto-detect languages, run the indexer(s), and merge into one codemap (--root defaults to the current directory)
|
|
1336
|
+
geml codemap build (--db <graph.db> | --adapter joern|scip --raw <in>)+ [--root <repo>] [--out .geml-code-graph] [--container module|dir|file] [--lang <JAVASRC|NEWC|…>] [--joern <path>] [--history [-m msg]]
|
|
1337
|
+
geml codemap verify [dir] geml check + profile reference checks
|
|
1338
|
+
geml codemap render [dir] every doc -> sibling .html (open index.html from disk)
|
|
1339
|
+
geml codemap serve [dir] [--port 8140] [--watch] [--background|--stop] live viewer: pages render from .geml on request; --watch re-runs the recipe when sources change
|
|
1340
|
+
geml codemap refresh [dir] [--force] [--commit] [--background|--hook] re-run the recorded build recipe (_index/refresh.json); --commit lands it as its own commit
|
|
1341
|
+
geml codemap find <name> [dir] locate a symbol by substring name -> doc#id + src (stdout, no browser)
|
|
1317
1342
|
(<dir> for verify/render/serve/refresh/find defaults to ./.geml-code-graph; codegraph and code-graph are accepted as aliases of codemap)`,
|
|
1318
|
-
mcp: `usage: geml mcp --root <dir> [--graph <dir>] [--no-history]
|
|
1319
|
-
|
|
1320
|
-
Serve GEML document CRUD over the MCP stdio transport (JSON-RPC 2.0).
|
|
1321
|
-
Every tool is geml_ + its CLI verb, so the terminal and the assistant share
|
|
1322
|
-
one vocabulary.
|
|
1323
|
-
Ten tools: geml_list · geml_get · geml_check · geml_history · geml_to
|
|
1324
|
-
geml_set · geml_add · geml_delete · geml_rename · geml_revert
|
|
1325
|
-
With a code graph under --root, four more (read-only), so one client entry
|
|
1326
|
-
covers both: geml_codemap_search · geml_codemap_callchain
|
|
1327
|
-
geml_codemap_list · geml_codemap_node
|
|
1328
|
-
|
|
1329
|
-
--root <dir> REQUIRED. Root holding the .geml documents. Every path a
|
|
1330
|
-
client names is confined here; a client cannot widen it.
|
|
1331
|
-
--graph <dir> Code-graph directory, inside --root. Defaults to
|
|
1332
|
-
<root>/.geml-code-graph when it holds an index.geml; with
|
|
1333
|
-
no graph the four graph tools are not served at all.
|
|
1334
|
-
--no-history Skip the .gemlhistory commit taken before each write
|
|
1335
|
-
(default: commit, so geml_revert always has a revision to
|
|
1336
|
-
undo to).
|
|
1337
|
-
|
|
1338
|
-
Register with a client:
|
|
1343
|
+
mcp: `usage: geml mcp --root <dir> [--graph <dir>] [--no-history]
|
|
1344
|
+
|
|
1345
|
+
Serve GEML document CRUD over the MCP stdio transport (JSON-RPC 2.0).
|
|
1346
|
+
Every tool is geml_ + its CLI verb, so the terminal and the assistant share
|
|
1347
|
+
one vocabulary.
|
|
1348
|
+
Ten tools: geml_list · geml_get · geml_check · geml_history · geml_to
|
|
1349
|
+
geml_set · geml_add · geml_delete · geml_rename · geml_revert
|
|
1350
|
+
With a code graph under --root, four more (read-only), so one client entry
|
|
1351
|
+
covers both: geml_codemap_search · geml_codemap_callchain
|
|
1352
|
+
geml_codemap_list · geml_codemap_node
|
|
1353
|
+
|
|
1354
|
+
--root <dir> REQUIRED. Root holding the .geml documents. Every path a
|
|
1355
|
+
client names is confined here; a client cannot widen it.
|
|
1356
|
+
--graph <dir> Code-graph directory, inside --root. Defaults to
|
|
1357
|
+
<root>/.geml-code-graph when it holds an index.geml; with
|
|
1358
|
+
no graph the four graph tools are not served at all.
|
|
1359
|
+
--no-history Skip the .gemlhistory commit taken before each write
|
|
1360
|
+
(default: commit, so geml_revert always has a revision to
|
|
1361
|
+
undo to).
|
|
1362
|
+
|
|
1363
|
+
Register with a client:
|
|
1339
1364
|
claude mcp add geml -- geml mcp --root /abs/path/to/repo`,
|
|
1340
1365
|
};
|
|
1341
1366
|
// Set from argv at dispatch time; when true, errors are emitted as a JSON
|
|
@@ -1794,7 +1819,8 @@ function resolveSelector(source, file, raw) {
|
|
|
1794
1819
|
// a heading, its level and text); `--json` is a machine-readable array so an
|
|
1795
1820
|
// agent can pick its next `get #id` target. Ids are listed in document order
|
|
1796
1821
|
// (the registration order parse() records), covering the same set `get #id`
|
|
1797
|
-
// resolves against: typed blocks
|
|
1822
|
+
// resolves against: typed blocks and headings. A `[^id]` reference names one
|
|
1823
|
+
// of those (§5.2); the `[^id]: text` definition line was withdrawn.
|
|
1798
1824
|
function listIds(source, file, json) {
|
|
1799
1825
|
const doc = parse(source, { resolveDoc: resolverFor(file), self: file === "-" ? undefined : basename(file) });
|
|
1800
1826
|
const rows = doc.ids.map((id) => {
|
|
@@ -1804,8 +1830,10 @@ function listIds(source, file, json) {
|
|
|
1804
1830
|
return { id, kind: "heading", level: b.level, text: b.text };
|
|
1805
1831
|
if (b?.kind === "block") {
|
|
1806
1832
|
const row = { id, kind: b.type };
|
|
1833
|
+
// `.footnote` is authored, not synthesized (the `[^id]: text` definition
|
|
1834
|
+
// line was withdrawn) — but it still marks a block meant as a footnote.
|
|
1807
1835
|
if (b.classes.includes("footnote"))
|
|
1808
|
-
row.footnote = true;
|
|
1836
|
+
row.footnote = true;
|
|
1809
1837
|
return row;
|
|
1810
1838
|
}
|
|
1811
1839
|
return { id, kind: b?.kind ?? "unknown" };
|
package/dist/inline.js
CHANGED
|
@@ -315,8 +315,13 @@ function scanAtoms(s, line, sink, depth = 0) {
|
|
|
315
315
|
// guesswork. Delimiters pair only *within* one text run: they never reach across
|
|
316
316
|
// a code span, inline math, a link or image (atoms from phase A), or a block
|
|
317
317
|
// boundary. Any delimiter left unpaired is literal text.
|
|
318
|
-
|
|
319
|
-
|
|
318
|
+
// Unicode punctuation, not just ASCII (§5.3). With an ASCII-only test, `“` and
|
|
319
|
+
// `,` count as ordinary letters, and a run hugged by CJK punctuation on the
|
|
320
|
+
// outside and ASCII punctuation on the inside stops flanking: `“*(foo)*”` loses
|
|
321
|
+
// its emphasis. CommonMark's rule is Unicode-wide, and the algorithm here is
|
|
322
|
+
// meant to be that rule restricted to `*` and `~~` — not a narrower one.
|
|
323
|
+
const PUNCT = /[\p{P}\p{S}]/u;
|
|
324
|
+
const isPunct = (c) => c !== undefined && PUNCT.test(c);
|
|
320
325
|
const isWS = (c) => c === undefined || /\s/.test(c);
|
|
321
326
|
// Left/right-flanking for a delimiter run, given the chars on either side.
|
|
322
327
|
function flank(before, after) {
|
package/dist/mcp.js
CHANGED
|
@@ -619,25 +619,25 @@ export function handleLine(line, write = (s) => process.stdout.write(s)) {
|
|
|
619
619
|
// ---------------------------------------------------------------------------
|
|
620
620
|
// Entry
|
|
621
621
|
// ---------------------------------------------------------------------------
|
|
622
|
-
export const MCP_USAGE = `usage: geml mcp --root <dir> [--graph <dir>] [--no-history]
|
|
623
|
-
|
|
624
|
-
Serve GEML document CRUD over the MCP stdio transport (JSON-RPC 2.0), plus the
|
|
625
|
-
read-only code-graph tools when the root holds a code graph.
|
|
626
|
-
|
|
627
|
-
--root <dir> REQUIRED. Root directory holding the .geml documents.
|
|
628
|
-
Relative paths resolve against the server process's CWD,
|
|
629
|
-
which the CLIENT chooses — pass an absolute path.
|
|
630
|
-
Every path a client names is confined to this directory;
|
|
631
|
-
a client cannot widen or override it.
|
|
632
|
-
--graph <dir> Code-graph directory, inside --root. Defaults to
|
|
633
|
-
<root>/.geml-code-graph when that holds an index.geml.
|
|
634
|
-
With no graph, the code-graph tools are not served
|
|
635
|
-
at all (a client sees only the document tools).
|
|
636
|
-
--no-history Do not auto-commit a .gemlhistory revision before each
|
|
637
|
-
write. Default is to commit, so geml_revert always
|
|
638
|
-
has a revision to undo to.
|
|
639
|
-
|
|
640
|
-
Register with a client:
|
|
622
|
+
export const MCP_USAGE = `usage: geml mcp --root <dir> [--graph <dir>] [--no-history]
|
|
623
|
+
|
|
624
|
+
Serve GEML document CRUD over the MCP stdio transport (JSON-RPC 2.0), plus the
|
|
625
|
+
read-only code-graph tools when the root holds a code graph.
|
|
626
|
+
|
|
627
|
+
--root <dir> REQUIRED. Root directory holding the .geml documents.
|
|
628
|
+
Relative paths resolve against the server process's CWD,
|
|
629
|
+
which the CLIENT chooses — pass an absolute path.
|
|
630
|
+
Every path a client names is confined to this directory;
|
|
631
|
+
a client cannot widen or override it.
|
|
632
|
+
--graph <dir> Code-graph directory, inside --root. Defaults to
|
|
633
|
+
<root>/.geml-code-graph when that holds an index.geml.
|
|
634
|
+
With no graph, the code-graph tools are not served
|
|
635
|
+
at all (a client sees only the document tools).
|
|
636
|
+
--no-history Do not auto-commit a .gemlhistory revision before each
|
|
637
|
+
write. Default is to commit, so geml_revert always
|
|
638
|
+
has a revision to undo to.
|
|
639
|
+
|
|
640
|
+
Register with a client:
|
|
641
641
|
claude mcp add geml -- geml mcp --root /abs/path/to/repo`;
|
|
642
642
|
export function parseArgs(args) {
|
|
643
643
|
let root;
|
package/dist/render-html.js
CHANGED
|
@@ -36,43 +36,43 @@ function page(title, body, ctx, source) {
|
|
|
36
36
|
? `<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`
|
|
37
37
|
: "";
|
|
38
38
|
const liveJs = wantLive
|
|
39
|
-
? `<script type="module">
|
|
40
|
-
globalThis.process ??= { argv: [], env: {} };
|
|
41
|
-
const { parse } = await import("${lg}geml.js");
|
|
42
|
-
const { codeGraphWaves } = await import("${lg}render.js");
|
|
43
|
-
const w = codeGraphWaves(async (rel) => {
|
|
44
|
-
try { const r = await fetch(rel, { cache: "no-cache" }); return r.ok ? await r.text() : null; } catch { return null; }
|
|
45
|
-
}, parse);
|
|
46
|
-
for (const m of document.querySelectorAll(".cg-mount[data-start]")) {
|
|
47
|
-
const start = m.getAttribute("data-start");
|
|
48
|
-
m._cgView = async (view) => {
|
|
49
|
-
// A directed view builds from the node's OWN document (its meta names the
|
|
50
|
-
// module and graph-depth); {doc} opens that document; else the mount's.
|
|
51
|
-
const src = view && view.doc ? view.doc
|
|
52
|
-
: view && view.node ? view.node.slice(0, view.node.lastIndexOf("#"))
|
|
53
|
-
: start;
|
|
54
|
-
const r = await w.build(src, view && view.doc ? undefined : view);
|
|
55
|
-
return r.error !== undefined ? null : r.data;
|
|
56
|
-
};
|
|
57
|
-
}
|
|
39
|
+
? `<script type="module">
|
|
40
|
+
globalThis.process ??= { argv: [], env: {} };
|
|
41
|
+
const { parse } = await import("${lg}geml.js");
|
|
42
|
+
const { codeGraphWaves } = await import("${lg}render.js");
|
|
43
|
+
const w = codeGraphWaves(async (rel) => {
|
|
44
|
+
try { const r = await fetch(rel, { cache: "no-cache" }); return r.ok ? await r.text() : null; } catch { return null; }
|
|
45
|
+
}, parse);
|
|
46
|
+
for (const m of document.querySelectorAll(".cg-mount[data-start]")) {
|
|
47
|
+
const start = m.getAttribute("data-start");
|
|
48
|
+
m._cgView = async (view) => {
|
|
49
|
+
// A directed view builds from the node's OWN document (its meta names the
|
|
50
|
+
// module and graph-depth); {doc} opens that document; else the mount's.
|
|
51
|
+
const src = view && view.doc ? view.doc
|
|
52
|
+
: view && view.node ? view.node.slice(0, view.node.lastIndexOf("#"))
|
|
53
|
+
: start;
|
|
54
|
+
const r = await w.build(src, view && view.doc ? undefined : view);
|
|
55
|
+
return r.error !== undefined ? null : r.data;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
58
|
</script>\n`
|
|
59
59
|
: "";
|
|
60
|
-
return `<!doctype html>
|
|
61
|
-
<html lang="en">
|
|
62
|
-
<head>
|
|
63
|
-
<meta charset="utf-8">
|
|
64
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
65
|
-
<title>${esc(title)}</title>
|
|
66
|
-
<style>${CSS}</style>
|
|
67
|
-
${importMap}${mathHead}${mermaidHead}</head>
|
|
68
|
-
<body>
|
|
69
|
-
<main>
|
|
70
|
-
${body}
|
|
71
|
-
</main>
|
|
72
|
-
${footer}
|
|
73
|
-
<script>${JS}</script>
|
|
74
|
-
${ctx.usedCodeGraph ? `<script>${CODE_GRAPH_JS}</script>\n` : ""}${liveJs}</body>
|
|
75
|
-
</html>
|
|
60
|
+
return `<!doctype html>
|
|
61
|
+
<html lang="en">
|
|
62
|
+
<head>
|
|
63
|
+
<meta charset="utf-8">
|
|
64
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
65
|
+
<title>${esc(title)}</title>
|
|
66
|
+
<style>${CSS}</style>
|
|
67
|
+
${importMap}${mathHead}${mermaidHead}</head>
|
|
68
|
+
<body>
|
|
69
|
+
<main>
|
|
70
|
+
${body}
|
|
71
|
+
</main>
|
|
72
|
+
${footer}
|
|
73
|
+
<script>${JS}</script>
|
|
74
|
+
${ctx.usedCodeGraph ? `<script>${CODE_GRAPH_JS}</script>\n` : ""}${liveJs}</body>
|
|
75
|
+
</html>
|
|
76
76
|
`;
|
|
77
77
|
}
|
|
78
78
|
export function renderHtml(doc, opts = {}) {
|