@geml/dsh-plugin 1.0.3 → 1.0.4
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geml/dsh-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Agent-Native document handling for DSH — addressable blocks let an agent read and edit one section instead of the whole file. Ships the geml MCP server and the authoring and code-graph skills.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/geml-spec/geml",
|
package/skills/geml/SKILL.md
CHANGED
|
@@ -37,14 +37,17 @@ runs 5–11% of the file it maps, but on a changelog of many small sections it i
|
|
|
37
37
|
rather than an error, so if a search you expect to hit comes back empty,
|
|
38
38
|
use `list`.)
|
|
39
39
|
3. `geml get <file> '<address>'` — that block and nothing else.
|
|
40
|
-
4.
|
|
41
|
-
string to
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
asked
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
4. Change it. Two ways, and the choice is about **consent**, not capability:
|
|
41
|
+
- **Ordinary file-editing tool**, using the text from step 3 as the string to
|
|
42
|
+
replace. The default: a document nobody asked to address by block is edited
|
|
43
|
+
the way its author edits it.
|
|
44
|
+
- **`geml set <file> '#id' …`**, when block-addressed editing is what was
|
|
45
|
+
asked for — a knowledge base, a generated index, a log that only grows.
|
|
46
|
+
Measured on a real vault: the frontmatter and every block you did not
|
|
47
|
+
address come out **byte-for-byte unchanged**, and the body you write lands
|
|
48
|
+
verbatim — no escaping, no reflowing. Read
|
|
49
|
+
[`references/markdown-writes.md`](references/markdown-writes.md) first; two
|
|
50
|
+
of its rules are silent when broken.
|
|
48
51
|
|
|
49
52
|
Never convert a document to GEML, never leave a `.gemlhistory` beside one, and
|
|
50
53
|
do not pitch the format: use the tool, report the change you made, and let the
|
|
@@ -125,7 +128,7 @@ already; none is ever created for you. `--dry-run` shows what it would do.
|
|
|
125
128
|
geml list file.geml # CALL THIS FIRST — every block, its address, kind, lines
|
|
126
129
|
geml find "text" file|dir # search block CONTENT -> file<TAB>address (exit 1 = no hit)
|
|
127
130
|
# a NAMED file is searched whatever its extension (.md too);
|
|
128
|
-
# a directory walks *.geml
|
|
131
|
+
# a directory walks *.geml and *.md
|
|
129
132
|
geml get file.geml '#id' # read ONE block (a heading id = its whole section)
|
|
130
133
|
geml set file.geml '#id' --in f # replace ONE block (re-parsed; never writes a broken doc)
|
|
131
134
|
geml history save file.geml -m "…" # snapshot to .gemlhistory after each meaningful edit
|
|
@@ -156,7 +159,7 @@ geml get <skill-base>/references/authoring.geml '#tables'
|
|
|
156
159
|
| section | covers |
|
|
157
160
|
|---|---|
|
|
158
161
|
| `#typed-block` | block anatomy, attribute object, examples of every registered type |
|
|
159
|
-
| `#tables` | pipe/CSV bodies, `compute=`, `summary=`,
|
|
162
|
+
| `#tables` | pipe/CSV bodies of FACTS, `delim=`, printf display — and the `view` that derives over one: `compute=`, `summary=`, `where=`, `order=`, `limit=`, `select=`, `by=`/`aggregate=` |
|
|
160
163
|
| `#charts` | `geml-chart` diagrams bound to a table via `data=#id` |
|
|
161
164
|
| `#data` | the `data` block — value tree, `json`/`jsonl` formats, blind append, chart binding |
|
|
162
165
|
| `#inline` | inline markup, links/refs/footnotes, task lists, media embeds |
|
|
@@ -83,10 +83,13 @@ Two bodies, one model: the visual (pipe) form, or the data form
|
|
|
83
83
|
| Basic | 1 | 30 |
|
|
84
84
|
===
|
|
85
85
|
|
|
86
|
-
=== table {#fy25 format=csv header=1
|
|
86
|
+
=== table {#fy25 format=csv header=1}
|
|
87
87
|
Segment, Q1, Q2, Q3, Q4
|
|
88
88
|
Cloud, 8, 10, 12, 14
|
|
89
89
|
===
|
|
90
|
+
|
|
91
|
+
=== view {#fy25-report src=#fy25 compute="FY [%.1f] = Q1 + Q2 + Q3 + Q4" summary="Segment = 'Total'; FY [%.1f] = sum(FY)"}
|
|
92
|
+
===
|
|
90
93
|
====
|
|
91
94
|
|
|
92
95
|
- `delim=";"` — one character replacing the data form's natural delimiter (`,`
|
|
@@ -94,6 +97,11 @@ Cloud, 8, 10, 12, 14
|
|
|
94
97
|
single character is an error, a tab is `format=tsv` (attribute values have no
|
|
95
98
|
escapes), and `delim` without a data `format` is ignored (warning). The data
|
|
96
99
|
form splits and nothing more — it never strips outer `|` like the visual form.
|
|
100
|
+
A `table` holds FACTS and derives nothing. Everything below belongs to a
|
|
101
|
+
`view`, whose `src=` names a table, another view, or a `.csv`/`.tsv` file —
|
|
102
|
+
a `table` carrying these attributes is an `unknown-attribute` warning, and a
|
|
103
|
+
`table` whose `src=` names a BLOCK is an error pointing at `view`.
|
|
104
|
+
|
|
97
105
|
- `compute="Name = expr; Name2 = expr2"` — per-row formulas over columns (by
|
|
98
106
|
header name, or single letter `A`,`B`,…), operators `+ - * / ( )`.
|
|
99
107
|
Reference an earlier computed column by name. Quote names with spaces:
|
|
@@ -102,7 +110,20 @@ Cloud, 8, 10, 12, 14
|
|
|
102
110
|
foot row. A bare (non-aggregated) column ref in `summary` is an error.
|
|
103
111
|
- A trailing `[printf]` on a name sets numeric display: `FY [%.1f]`,
|
|
104
112
|
`P [%.1f%%]`.
|
|
105
|
-
-
|
|
113
|
+
- `where="Status = 'open' and N > 3"` — keep rows. Comparisons take a number
|
|
114
|
+
or a single-quoted string; `not`/`and`/`or` and parentheses combine them. It
|
|
115
|
+
MAY name a per-row computed column; naming an aggregate-derived one is
|
|
116
|
+
circular and refused.
|
|
117
|
+
- `order="N desc, Id"` (`asc` default, stable), `limit=10`, and
|
|
118
|
+
`select="Id, N"` — columns only, in the order given; an `=` there points at
|
|
119
|
+
`compute=`.
|
|
120
|
+
- `by="Area"` groups; `aggregate="Open = count(Id)"` names the group's
|
|
121
|
+
columns. `by=` alone is the distinct set of those keys. To filter GROUPS,
|
|
122
|
+
consume the view from another one (SQL's `HAVING`).
|
|
123
|
+
- Order of evaluation is SQL's: per-row `compute` → `where` → aggregate
|
|
124
|
+
`compute` → `by`/`aggregate` → `order` → `limit` → `select` → `summary`.
|
|
125
|
+
So `sum(FY)` means the same in `compute=` and `summary=` — over the rows
|
|
126
|
+
shown — and a `summary=` must target a column `select=` kept.
|
|
106
127
|
|
|
107
128
|
# Charts {#charts}
|
|
108
129
|
|
|
@@ -156,8 +177,12 @@ body the engine rejects is a build ERROR naming the line.
|
|
|
156
177
|
file no longer has is an error (a drifted reference fails the build), and a
|
|
157
178
|
body kept alongside it is a snapshot that warns when it goes stale. Routes
|
|
158
179
|
resolve document-relative, or relative to `--root` when one is given.
|
|
159
|
-
- `yaml
|
|
160
|
-
|
|
180
|
+
- `yaml` IS read, for a declared subset: block mappings and sequences,
|
|
181
|
+
quoted and block scalars, and YAML 1.2 **core-schema** scalars — so `yes`
|
|
182
|
+
is the string `"yes"`, not `true`. Anchors, aliases, tags, merge keys, flow
|
|
183
|
+
collections and a second document are outside it and are parse errors,
|
|
184
|
+
never guesses. `toml` stays a RESERVED name with no engine — body kept raw
|
|
185
|
+
plus a warning. csv/tsv belong to `table`, not `data`
|
|
161
186
|
(their delimiter/header dialect parameters only mean something against a
|
|
162
187
|
column model).
|
|
163
188
|
- A chart's `data=#id` accepts a `data` block whose value is a RECORD ARRAY
|
|
@@ -222,7 +247,8 @@ geml list file.geml # CALL FIRST: every block, its address, ki
|
|
|
222
247
|
geml find "text" file|dir # search block CONTENT -> file<TAB>address; exit 1 = no hit
|
|
223
248
|
# a NAMED file is searched whatever its extension — `list`,
|
|
224
249
|
# `get` and `find` all read Markdown, so this addresses a
|
|
225
|
-
# plain README without converting it; a DIRECTORY walks
|
|
250
|
+
# plain README without converting it; a DIRECTORY walks
|
|
251
|
+
# *.geml and *.md — both formats the parser reads
|
|
226
252
|
geml get file.geml # same listing as `list` (the no-selector default)
|
|
227
253
|
geml get file.geml '#id' # print ONE block (raw span; --json = model node)
|
|
228
254
|
geml get file.geml '=== note' # every block of a type; '@a3f9c1d2' = a block with no #id
|
|
@@ -281,8 +307,10 @@ snapshot as you go, rather than re-emitting the whole file:
|
|
|
281
307
|
|
|
282
308
|
=== code {#editing-loop lang=sh}
|
|
283
309
|
geml get file.geml '#intro' # read just this block (a heading id = its whole section)
|
|
284
|
-
geml set file.geml '#intro' --in - # replace just this span (stdin or --in FILE);
|
|
285
|
-
|
|
310
|
+
geml set file.geml '#intro' --body --in - # replace just this span (stdin or --in FILE);
|
|
311
|
+
# raw text needs --body; a bare --in takes a WHOLE
|
|
312
|
+
# block, fences and all. Either way the splice is
|
|
313
|
+
# re-parsed and REJECTED if it breaks the doc
|
|
286
314
|
geml history save file.geml -m "…" # snapshot into the .gemlhistory sidecar — do this each step
|
|
287
315
|
geml history get file.geml # revisions, newest first; first column IS the --rev selector
|
|
288
316
|
geml revert file.geml '#intro' # roll ONE block back to the previous revision (= --rev -1)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Writing to a Markdown file with `geml set`
|
|
2
|
+
|
|
3
|
+
`geml set` and `geml add` edit one block of a `.md` in place. Nothing is
|
|
4
|
+
converted: the file stays the Markdown it was, and a reader who does not have
|
|
5
|
+
`geml` sees no trace of it.
|
|
6
|
+
|
|
7
|
+
Everything below was measured against real documents, not read off the help
|
|
8
|
+
text. Two of the rules are **silent** when broken — no error, exit code 0 — and
|
|
9
|
+
they are the reason to read this before the first write rather than after.
|
|
10
|
+
|
|
11
|
+
## What holds
|
|
12
|
+
|
|
13
|
+
**A write is surgical.** After `geml set <file> '#id' --body --in -`, the YAML
|
|
14
|
+
frontmatter and every block other than `#id` are byte-for-byte what they were.
|
|
15
|
+
The verb splices; it does not re-serialize the file.
|
|
16
|
+
|
|
17
|
+
**The body lands verbatim.** Whatever you write goes in as typed — a
|
|
18
|
+
`> [!tip]` callout, a `[[wikilink]]`, a `` ```dataview `` fence, a table. No
|
|
19
|
+
escaping, no reflowing, no normalization of your Markdown to anyone else's
|
|
20
|
+
taste.
|
|
21
|
+
|
|
22
|
+
**A broken result is refused.** The file is re-parsed before the write lands;
|
|
23
|
+
if the result would not parse, nothing is written.
|
|
24
|
+
|
|
25
|
+
**Heading addresses are stable.** `## Entities` is `#entities` on every run and
|
|
26
|
+
every platform, and it survives anything happening above it. That is the whole
|
|
27
|
+
reason to address a file this way instead of by line number.
|
|
28
|
+
|
|
29
|
+
One exception, and it is GitHub's too: **a repeated heading is numbered by
|
|
30
|
+
position.** The second `## Added` is `#added-1`, the third `#added-2` — the same
|
|
31
|
+
anchors GitHub gives them — so a Keep-a-Changelog file is writable section by
|
|
32
|
+
section. But a new `## Added` inserted above one shifts it: `#added-1` becomes
|
|
33
|
+
`#added-2`. Take a repeated heading's address from a fresh `geml list`, never
|
|
34
|
+
from memory.
|
|
35
|
+
|
|
36
|
+
**The file is read as Markdown, not as GEML.** Where the two disagree, a `.md`
|
|
37
|
+
gets Markdown's reading:
|
|
38
|
+
|
|
39
|
+
- `[[Note]]`, `[[Note#Heading]]`, `[[Note#Heading|alias]]`, `[[Note#^block]]`
|
|
40
|
+
and `![[Note#Heading]]` are Obsidian links. The note is found by name
|
|
41
|
+
anywhere under the resolution root, `.md` implied; one that does not exist
|
|
42
|
+
yet is a **warning**, never a refusal — a vault plans notes that way.
|
|
43
|
+
Within the page, `[[#Heading Text]]` may name a heading by its text.
|
|
44
|
+
- `[^label]` is a GFM footnote. With a `[^label]:` line it is a footnote;
|
|
45
|
+
without one it is plain text — so `[^0-9]` in a sentence about a regex is
|
|
46
|
+
nothing to worry about.
|
|
47
|
+
- `{{title}}` is text: a template engine's placeholder, not a reference.
|
|
48
|
+
- `~~~` fences and indented code blocks are code, like ``` ones. Nothing inside
|
|
49
|
+
is a link, a heading or a footnote.
|
|
50
|
+
|
|
51
|
+
The resolution root is the file's own directory unless you pass `--root`. A
|
|
52
|
+
note in a subfolder that links across its vault needs `--root <vault>`, and
|
|
53
|
+
`geml check` says so when it sees the vault's `.obsidian/` above you. It never
|
|
54
|
+
looks above the root on its own.
|
|
55
|
+
|
|
56
|
+
## The three that bite
|
|
57
|
+
|
|
58
|
+
### 1. Never `set` the frontmatter block — it destroys the frontmatter
|
|
59
|
+
|
|
60
|
+
Frontmatter is not a typed block in Markdown; it is an anonymous prose block,
|
|
61
|
+
and its **closing `---` is part of that block's body**. Replace the body and the
|
|
62
|
+
closer is gone, leaving an opener with nothing to close it — no properties at
|
|
63
|
+
all, for every tool that reads them.
|
|
64
|
+
|
|
65
|
+
Change frontmatter with an ordinary editor. This is silent: exit code 0.
|
|
66
|
+
|
|
67
|
+
### 2. `--body` is for a heading. On a prose block it APPENDS
|
|
68
|
+
|
|
69
|
+
A heading's block is a heading line plus a body. A prose block is body all the
|
|
70
|
+
way down, so it has no separate body to set — `get '#x' --body` on one comes
|
|
71
|
+
back empty, and `set --body` writes into that emptiness, which lands **after**
|
|
72
|
+
the prose already there. Nothing is removed.
|
|
73
|
+
|
|
74
|
+
| target | `set '#id'` | `set '#id' --body` |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| a heading block | refused: *content is prose, not a block — use --body* | replaces the section body, keeps the heading line |
|
|
77
|
+
| a prose block | replaces it | **appends to it, silently** |
|
|
78
|
+
|
|
79
|
+
The rule is the opposite of what one habit would give you, and picking wrong
|
|
80
|
+
fails loudly one way and quietly the other. `geml list` prints the kind in its
|
|
81
|
+
second column — read it before choosing. This is the other silent one.
|
|
82
|
+
|
|
83
|
+
### 3. `--in <file>` is not "read this text"
|
|
84
|
+
|
|
85
|
+
`--in F` means *take block `#id` from file F*. Raw text goes in on **stdin**:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
printf '…' | geml set page.md '#id' --body --in - # right
|
|
89
|
+
geml set page.md '#id' --body --in fragment.txt # looks for #id INSIDE fragment.txt
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Loud, at least: when F has no such block the refusal names the stdin form.
|
|
93
|
+
|
|
94
|
+
## Smaller things worth knowing
|
|
95
|
+
|
|
96
|
+
- `find` is a **literal substring**, not a pattern. `Hot.Cache` does not match
|
|
97
|
+
"Hot Cache". Case-insensitive unless `--case`.
|
|
98
|
+
- `find --head` shows **one line per block**. A block with thirteen matches
|
|
99
|
+
reports one; `find` locates, `get` reads.
|
|
100
|
+
- `set --body` replaces a trailing `---` rule too, if the section ends with one.
|
|
101
|
+
- The blank line after a heading is not re-inserted. Cosmetic; renderers do not
|
|
102
|
+
care, a diff does.
|
|
103
|
+
- A directory walk skips hidden directories. A tree that hides sources in
|
|
104
|
+
`.raw/` must name that directory: `geml find 'x' notes .raw`.
|
|
105
|
+
- `@…` addresses are content hashes and change when the content does. Fine to
|
|
106
|
+
read from a fresh `list`; never store one, and never write to one.
|
|
107
|
+
|
|
108
|
+
Before a first write to a page, `geml check <file>` — exit 0 means every block is
|
|
109
|
+
writable; warnings (a note not yet written) never block one.
|
|
110
|
+
|
|
111
|
+
## Do not convert the file
|
|
112
|
+
|
|
113
|
+
`geml <page>.md --to geml` and back is lossy for anything beyond plain
|
|
114
|
+
Markdown — frontmatter lists collapse, `[[…]]` comes back escaped, thematic
|
|
115
|
+
breaks are dropped. The point of addressing a Markdown file is that it **stays
|
|
116
|
+
Markdown**.
|