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.
@@ -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 `skill`. Omit it
122
- for the index. `experimental: true` adds the genre's EXPERIMENTAL files, which sit
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 published artifact read back through
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, and this server must contain no engine copy.
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
- + ' [' + ['nodes', 'edges', 'groups', 'classes', 'blocks', 'boundaries']
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
- ].join('\n');
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.2",
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]));\"",
@@ -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. **One correction applies to it.** `reference/reading.md` is
123
- a byte-frozen copy of a released reading contract, and one sentence in it is
124
- wrong: where it says *"Ignore the layout zone. Everything from the `layout`
125
- keyword down is …"*, do not read it as a rule about **position**. Ignore the
126
- layout **namespace** — its one member is `pin` — **wherever a member
127
- appears**, because a `pin` may legally sit *before* the `layout` line, and
128
- about half of them do. Membership decides, not position. The frozen file is
129
- left as-is on purpose; apply this rule instead, the same one stated above
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. Prefer the first three — they are the
136
- portable ones.
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.2`. Write the LOWEST
179
- # one that carries what the figure needs — `0.2`
180
- # only for `statechart`, which does not exist at
181
- # `0.1`. Sections may differ; each declares its
182
- # own.
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(docs.map(d => engine.render(d, RENDER_OPTS)))
43
- : engine.render(docs[0], RENDER_OPTS).svg;
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