figdown 0.3.2 → 0.4.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/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -0
- package/dist/figdown.js +3791 -213
- package/dist/figdown.mjs +3791 -213
- package/examples/evpn-fabric.svg +1 -1
- package/examples/showcase/arp-resolution.svg +3 -2
- package/examples/showcase/ethernet-frame.svg +1 -1
- package/examples/showcase/l2-forwarding-logic.svg +1 -1
- package/examples/showcase/tcp-handshake.svg +4 -3
- package/examples/showcase/tcp-header.svg +1 -1
- package/examples/showcase/tcp-state-machine.svg +53 -24
- package/guide/expressing.md +58 -6
- package/guide/layout.md +54 -10
- package/guide/showcase.md +63 -27
- package/integrations/mcp-server/README.md +11 -5
- package/integrations/mcp-server/server.js +19 -3
- package/package.json +2 -1
- package/skill/figdown/SKILL.md +49 -18
- package/skill/figdown/build-svg.js +16 -2
- package/skill/figdown/figdown.html +3982 -229
- package/skill/figdown/reference/experimental/flowchart.md +6 -4
- package/skill/figdown/reference/experimental/sequence.md +252 -0
- package/skill/figdown/reference/experimental/statechart.md +5 -3
- package/skill/figdown/reference/experimental/topology.md +6 -4
- package/skill/figdown/reference/reading.md +14 -9
- package/skill/figdown/reference/scene.md +8 -3
|
@@ -87,7 +87,7 @@ particular there is no layout-lint tool and no "render to PNG".
|
|
|
87
87
|
|---|---|
|
|
88
88
|
| `figdown_build` | Turn one `.fd` into the deterministic, self-carrying SVG. The artifact embeds its own source, that source's SHA-256 and the engine version (core §7), so it round-trips back to text. |
|
|
89
89
|
| `figdown_check` | Parse one document *or walk a whole tree*, render nothing, write nothing. This is the write → validate → fix loop and the corpus sweep; it exists separately from `build` so that iterating on a broken file costs no render and puts no SVG in the agent's context. |
|
|
90
|
-
| `figdown_read` | The semantic model — participants, relationships and direction, containment, and the **stated meaning** of every class — shipped with the reading contract. Accepts a `.svg` too, recovering the source from its metadata and reporting the artifact stale if the `.fd` has moved on. |
|
|
90
|
+
| `figdown_read` | The semantic model — participants, relationships and direction, containment, and the **stated meaning** of every class — shipped with the reading contract. Accepts a `.svg` too, recovering the source from its metadata and reporting the artifact stale if the `.fd` has moved on. A `sequence` section reports its own collections (lifelines, messages, states, fragments, operands) and gets one extra contract line, because in that genre a message is **not** an edge and array order **is** the meaning. |
|
|
91
91
|
| `figdown_reference` | The per-genre reference, so an agent knows what a genre may and may not say. The grammar is **closed** and the parser **never warns about portability**, so this is the only place the frozen / EXPERIMENTAL split is visible. |
|
|
92
92
|
|
|
93
93
|
### `figdown_build`
|
|
@@ -118,8 +118,11 @@ than guessing at the picture.
|
|
|
118
118
|
### `figdown_reference`
|
|
119
119
|
|
|
120
120
|
`name` — a genre (`block`, `bitfield`, `table`, `topology`, `flowchart`,
|
|
121
|
-
`statechart`, `timing`), a task (`reading`, `transcribe`), or
|
|
122
|
-
for the index.
|
|
121
|
+
`statechart`, `timing`, `sequence`), a task (`reading`, `transcribe`), or
|
|
122
|
+
`skill`. Omit it for the index. The list is not restated here or in the tool's
|
|
123
|
+
schema as the authority — it is **parsed out of `SKILL.md`'s router at call
|
|
124
|
+
time**, so a genre that lands without a router row is invisible to this tool
|
|
125
|
+
and `gate:mcp` fails rather than an agent receiving nothing. `experimental: true` adds the genre's EXPERIMENTAL files, which sit
|
|
123
126
|
outside the v0.1 conformance surface and its compatibility promise.
|
|
124
127
|
|
|
125
128
|
## A parse error is a result, not an exception
|
|
@@ -168,7 +171,10 @@ node integrations/mcp-server/test.js # npm run gate:mcp
|
|
|
168
171
|
It speaks real JSON-RPC to a spawned subprocess (framing is the half that was
|
|
169
172
|
hand-rolled, and an in-process test cannot catch a framing bug), then exercises
|
|
170
173
|
every tool against real figures from `examples/` — a multi-section topology, an
|
|
171
|
-
experimental `figdown 0.2 statechart`, a
|
|
174
|
+
experimental `figdown 0.2 statechart`, a `figdown 0.4 sequence` ladder whose
|
|
175
|
+
model has no nodes and no edges at all, a published artifact read back through
|
|
172
176
|
its own metadata, and a deliberately broken document. Two of its assertions are
|
|
173
177
|
drift guards rather than feature tests: every genre in SKILL.md's router must
|
|
174
|
-
still resolve to files that exist
|
|
178
|
+
still resolve to files that exist — `sequence` by name, since a genre that
|
|
179
|
+
lands without a router row is the failure this guard exists for — and this
|
|
180
|
+
server must contain no engine copy.
|
|
@@ -307,7 +307,12 @@ function toolRead(args) {
|
|
|
307
307
|
const shape = docs.map((d, i) =>
|
|
308
308
|
' section ' + (i + 1) + ': figdown ' + d.version + ' ' + d.genre
|
|
309
309
|
+ (d.title ? ' title ' + JSON.stringify(d.title) : '')
|
|
310
|
-
|
|
310
|
+
// The collection list is per-genre, not scene-only: a `sequence` document
|
|
311
|
+
// carries no nodes and no edges (a message is NOT an edge — SEQUENCE-ORDER-MODEL), so a
|
|
312
|
+
// scene-only list reported a ladder of seventeen messages as "[3 classes]"
|
|
313
|
+
// and the shape line said nothing about the figure at all.
|
|
314
|
+
+ ' [' + ['nodes', 'edges', 'groups', 'classes', 'blocks', 'boundaries',
|
|
315
|
+
'lifelines', 'messages', 'states', 'fragments', 'operands']
|
|
311
316
|
.filter(k => Array.isArray(d[k]) && d[k].length)
|
|
312
317
|
.map(k => d[k].length + ' ' + k).join(', ') + ']').join('\n');
|
|
313
318
|
|
|
@@ -327,7 +332,18 @@ function toolRead(args) {
|
|
|
327
332
|
' parsable — infer no participant, edge or category from them.',
|
|
328
333
|
' * Array order is not ranking or priority (§12.7).',
|
|
329
334
|
' * Everything below `layout` is geometry with no meaning. Skip it.',
|
|
330
|
-
|
|
335
|
+
// One conditional line. `sequence` is the single genre where two of the
|
|
336
|
+
// rules above read as false: it has no nodes and no edges, and its array
|
|
337
|
+
// order IS the figure's meaning. Adding it unconditionally would charge
|
|
338
|
+
// every other reader for a genre they are not holding.
|
|
339
|
+
docs.some(d => d.genre === 'sequence')
|
|
340
|
+
? ' * THIS DOCUMENT HAS A `sequence` SECTION, where two of the rules above invert:\n'
|
|
341
|
+
+ ' participants are `lifelines`, a `message` is an occurrence in time and NOT an\n'
|
|
342
|
+
+ ' edge (which is why `edges` is empty), and `messages` ∪ `states` array order IS\n'
|
|
343
|
+
+ ' the order of the exchange. A `state` names the condition its lifeline is in\n'
|
|
344
|
+
+ ' from that point on. Nothing in the layout zone draws here.'
|
|
345
|
+
: null,
|
|
346
|
+
].filter(Boolean).join('\n');
|
|
331
347
|
|
|
332
348
|
const head = [
|
|
333
349
|
'ok: true ' + docs.length + ' section(s) in ' + s.label,
|
|
@@ -483,7 +499,7 @@ const TOOLS = [
|
|
|
483
499
|
inputSchema: {
|
|
484
500
|
type: 'object',
|
|
485
501
|
properties: {
|
|
486
|
-
name: { type: 'string', description: 'A genre (block, bitfield, table, topology, flowchart, statechart, timing), a task (reading, transcribe), or "skill". Omit for the index.' },
|
|
502
|
+
name: { type: 'string', description: 'A genre (block, bitfield, table, topology, flowchart, statechart, timing, sequence), a task (reading, transcribe), or "skill". Omit for the index.' },
|
|
487
503
|
experimental: { type: 'boolean', description: 'Include the genre\'s EXPERIMENTAL (experimental) files — outside the v0.1 conformance surface and its compatibility promise.' },
|
|
488
504
|
},
|
|
489
505
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "figdown",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "A missing edge still looks fine. Text doesn't. A closed, deterministic figure language for Markdown whose source states the meaning, so the next reader can check the figure instead of only looking at it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "FigDown <hello@figdown.org>",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"gate:registry": "node tools/registry-check.js --strict",
|
|
46
46
|
"gate:capability": "node tools/capability-coverage.js --strict",
|
|
47
47
|
"gate:archive": "node tools/archive-check.js --strict",
|
|
48
|
+
"gate:standards": "node tools/standards-check.js --strict",
|
|
48
49
|
"gate:plugin": "node tools/plugin-check.js --strict",
|
|
49
50
|
"gate:mcp": "node integrations/mcp-server/test.js",
|
|
50
51
|
"gates:list": "node -e \"const s=require(process.cwd()+'/package.json').scripts||{};const g=Object.keys(s).filter(k=>k.startsWith('gate:'));if(!g.length){console.error('gates:list — no gate:* scripts found');process.exit(2);}if(process.env.GATES_JSON)console.log(JSON.stringify(g));else g.forEach(k=>console.log(k+' -> '+s[k]));\"",
|
package/skill/figdown/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: figdown
|
|
3
|
-
description: Draw a figure a human can read and an agent can read too — FigDown .fd text that states the meaning, with a deterministic SVG embedded in Markdown. Use when asked to create, edit, fix, or read diagrams/figures in docs — block and architecture diagrams, topologies, flowcharts, bit-level layouts (packet headers, hardware registers), tables, timing waveforms — or when a .md contains an SVG with a "source: *.fd" footer.
|
|
3
|
+
description: Draw a figure a human can read and an agent can read too — FigDown .fd text that states the meaning, with a deterministic SVG embedded in Markdown. Use when asked to create, edit, fix, or read diagrams/figures in docs — block and architecture diagrams, topologies, flowcharts, state machines, message-sequence exchanges between parties, bit-level layouts (packet headers, hardware registers), tables, timing waveforms — or when a .md contains an SVG with a "source: *.fd" footer.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# FigDown — figures as text, one source, two readers
|
|
@@ -67,7 +67,11 @@ everything it produces is a hypothesis until checked.
|
|
|
67
67
|
is not a graph: it is a `table`. Conditions with one outcome per combination is
|
|
68
68
|
a `table`, not a `flowchart` — drawn as a chart it buys crossing edges and
|
|
69
69
|
nothing else. Two mirrored mechanisms are two figures. A run in which most of a
|
|
70
|
-
page's figures become tables is a correct run.
|
|
70
|
+
page's figures become tables is a correct run. **The SHAPES in the source
|
|
71
|
+
drawing are the tempting signal and they carry nothing** — the original author
|
|
72
|
+
picked them by habit, by stencil and by tool default, and no two drawings agree;
|
|
73
|
+
what decides the genre is the ROLES and the QUESTIONS in it, which is what the
|
|
74
|
+
extraction gives you and the picture does not.
|
|
71
75
|
|
|
72
76
|
**3 — Separate what the source states from what you concluded.** Write the
|
|
73
77
|
stated thing; for the rest, leave a `#` comment at the point of doubt and list
|
|
@@ -113,27 +117,51 @@ so you know which file you need before you need it.
|
|
|
113
117
|
| `flowchart` | `reference/experimental/flowchart.md`, `reference/layout.md` | `reference/experimental/chart.md` |
|
|
114
118
|
| `statechart` | `reference/experimental/statechart.md`, `reference/layout.md` | `reference/experimental/chart.md` |
|
|
115
119
|
| `timing` | — | `reference/experimental/timing.md` |
|
|
120
|
+
| `sequence` — message exchanges over time | — | `reference/experimental/sequence.md` |
|
|
121
|
+
|
|
122
|
+
**`sequence` draws a ladder**, and you place nothing in it: participants are
|
|
123
|
+
columns and time runs down the page, both in the order the source declares
|
|
124
|
+
them, so no key in that genre moves a coordinate — not even a layout-zone one,
|
|
125
|
+
which parses and changes nothing there. Its file is the whole of its
|
|
126
|
+
vocabulary, and it is the one genre where the layout file buys you nothing.
|
|
127
|
+
Three constructs an author arriving from another genre reaches for were
|
|
128
|
+
argued and REFUSED there — a time-gap line, a band over participants, and a
|
|
129
|
+
per-message "sent but never delivered" flag; each is a line error that names
|
|
130
|
+
its ground and the spelling to write instead, so write the source and read
|
|
131
|
+
the message rather than guessing. Its file says how.
|
|
116
132
|
|
|
117
133
|
Two more files answer a **task** rather than a genre:
|
|
118
134
|
|
|
119
135
|
- **Reading a `.fd` someone else wrote, to summarise or answer from it** →
|
|
120
136
|
`reference/reading.md`. It is the contract for what you may conclude and
|
|
121
137
|
what you must not infer, and it is all you need: a reader can skip every
|
|
122
|
-
genre file above. **
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
keyword down is …"
|
|
126
|
-
layout **namespace** — its one member is `pin`
|
|
127
|
-
appears**, because a `pin` may legally sit *before* the
|
|
128
|
-
about half of them do. Membership decides, not position.
|
|
129
|
-
|
|
130
|
-
under "layout and pin".
|
|
138
|
+
genre file above. **The correction that used to be printed here is now in the
|
|
139
|
+
file itself**, so read `reference/reading.md` as it stands. If you ever meet
|
|
140
|
+
an OLDER copy of that contract — one that says *"Ignore the layout zone.
|
|
141
|
+
Everything from the `layout` keyword down is …"* — do not read that as a rule
|
|
142
|
+
about **position**. Ignore the layout **namespace** — its one member is `pin`
|
|
143
|
+
— **wherever a member appears**, because a `pin` may legally sit *before* the
|
|
144
|
+
`layout` line, and about half of them do. Membership decides, not position.
|
|
145
|
+
It is the same rule stated above under "layout and pin".
|
|
131
146
|
- **Transcribing an existing figure** — a drawing, a screenshot, another
|
|
132
147
|
format → `reference/transcribe.md`.
|
|
133
148
|
|
|
134
149
|
Pick the genre by what the figure IS, not by its subject: the left-hand
|
|
135
|
-
column above says what each is for.
|
|
136
|
-
|
|
150
|
+
column above says what each is for.
|
|
151
|
+
|
|
152
|
+
**Where portability and that answer disagree, the ANSWER decides.** `block`,
|
|
153
|
+
`bitfield` and `table` are the portable three — inside the v0.1 conformance
|
|
154
|
+
surface and its compatibility promise — and taking one of them *against* the
|
|
155
|
+
answer is a trade you STATE, never a default. State it in a `#` comment that
|
|
156
|
+
names the genre you did not write and what the reader must not conclude from
|
|
157
|
+
the one you did. Two different prices hide under the word "portable" and they
|
|
158
|
+
are charged by different rows: `topology` and `flowchart` are dispatchable at
|
|
159
|
+
`figdown 0.1`, so choosing them moves no version and only carries the EXPERIMENTAL
|
|
160
|
+
withdrawal risk; `statechart` and `sequence` move the declared version as well.
|
|
161
|
+
"It must render on any released version" is therefore not an argument for
|
|
162
|
+
leaving the first two. An approximation you do not state is indistinguishable
|
|
163
|
+
from a judgement about what the figure is, so the reader inherits the wrong
|
|
164
|
+
one.
|
|
137
165
|
|
|
138
166
|
**Load the genre file BEFORE you write line 2, not only when something
|
|
139
167
|
fails.** A scene genre may spell the thing and the line with **its own
|
|
@@ -175,11 +203,14 @@ figdown 0.1 block # REQUIRED first significant line; comments and
|
|
|
175
203
|
# A later `figdown 0.1 <genre>` starts a new
|
|
176
204
|
# section with its own genre; one file still
|
|
177
205
|
# renders to one SVG.
|
|
178
|
-
# The VERSION is `0.1` or `0.
|
|
179
|
-
# one that carries what the figure needs
|
|
180
|
-
#
|
|
181
|
-
# `0.
|
|
182
|
-
#
|
|
206
|
+
# The VERSION is `0.1`, `0.2` or `0.4`. Write the
|
|
207
|
+
# LOWEST one that carries what the figure needs.
|
|
208
|
+
# `0.1` carries every other genre. `0.2` is the
|
|
209
|
+
# floor for `statechart` and `0.4` the floor for
|
|
210
|
+
# `sequence`: neither exists at `0.1`, and a
|
|
211
|
+
# version below a genre's floor is a line error
|
|
212
|
+
# (`figdown 0.3 sequence` does not parse).
|
|
213
|
+
# Sections may differ; each declares its own.
|
|
183
214
|
title "Some Title" # optional; the quotes are REQUIRED
|
|
184
215
|
# comments start with '#'; inside quotes the only escapes are \n \" \\
|
|
185
216
|
class hot "Congested path" stroke=#dc2626 # meaning + style, declared once
|
|
@@ -38,9 +38,23 @@ function buildOne(engine, fdPath) {
|
|
|
38
38
|
for (const e of errs) console.error(' ' + e);
|
|
39
39
|
return false;
|
|
40
40
|
}
|
|
41
|
+
// GEOMETRY-TIME DIAGNOSTICS gate the artifact exactly as parse errors do.
|
|
42
|
+
// `parse` cannot see a coordinate, so a figure whose SOURCE is impeccable can
|
|
43
|
+
// still draw a false statement — a `group` band is the bounding box of its
|
|
44
|
+
// members, and a non-member sitting inside it tells the reader it is one.
|
|
45
|
+
// The engine reports those from `render`; writing the `.svg` anyway would
|
|
46
|
+
// publish the picture the engine has just said is wrong, which is the whole
|
|
47
|
+
// failure this gate exists to stop.
|
|
48
|
+
const rendered = docs.map(d => engine.render(d, RENDER_OPTS));
|
|
49
|
+
const gerrs = rendered.reduce((a, r) => a.concat(r.errs || []), []);
|
|
50
|
+
if (gerrs.length) {
|
|
51
|
+
console.error(fdPath + ':');
|
|
52
|
+
for (const e of gerrs) console.error(' ' + e);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
41
55
|
const svg = docs.length > 1
|
|
42
|
-
? engine.stackSectionSvgs(
|
|
43
|
-
:
|
|
56
|
+
? engine.stackSectionSvgs(rendered)
|
|
57
|
+
: rendered[0].svg;
|
|
44
58
|
const hash = crypto.createHash('sha256').update(src, 'utf8').digest('hex');
|
|
45
59
|
// The artifact records three things (spec core §7): the SHA-256 OF THE
|
|
46
60
|
// SOURCE, the ENGINE VERSION that rendered it, and any non-default render
|