@rse/specbook 0.9.5 → 0.9.6

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.
Files changed (52) hide show
  1. package/README.md +58 -328
  2. package/dst/specbook-api.d.ts +13 -20
  3. package/dst/specbook-api.js +28 -25
  4. package/dst/specbook-cli.js +34 -50
  5. package/dst/specbook-cmd-describe.d.ts +5 -2
  6. package/dst/specbook-cmd-describe.js +32 -91
  7. package/dst/specbook-cmd-export.d.ts +3 -3
  8. package/dst/specbook-cmd-export.js +4 -3
  9. package/dst/specbook-cmd-init.d.ts +2 -2
  10. package/dst/specbook-cmd-init.js +35 -13
  11. package/dst/specbook-cmd-lint.d.ts +6 -6
  12. package/dst/specbook-cmd-lint.js +36 -23
  13. package/dst/specbook-config.d.ts +2 -2
  14. package/dst/specbook-config.js +2 -2
  15. package/dst/specbook-diagram.d.ts +4 -4
  16. package/dst/specbook-diagram.js +21 -15
  17. package/dst/specbook-export-ast.d.ts +3 -3
  18. package/dst/specbook-export-common.d.ts +10 -9
  19. package/dst/specbook-export-common.js +23 -9
  20. package/dst/specbook-export-html.css +13 -3
  21. package/dst/specbook-export-html.d.ts +4 -4
  22. package/dst/specbook-export-html.js +61 -32
  23. package/dst/specbook-export-logo-dark.svg +35 -0
  24. package/dst/specbook-export-logo-light.svg +35 -0
  25. package/dst/specbook-export-md.d.ts +3 -3
  26. package/dst/{specbook-struct-schema.d.ts → specbook-format-schema.d.ts} +3 -2
  27. package/dst/{specbook-struct-schema.js → specbook-format-schema.js} +2 -1
  28. package/dst/specbook-format-spec.d.ts +31 -0
  29. package/dst/{specbook-struct-spec.js → specbook-format-spec.js} +10 -10
  30. package/dst/specbook-format.md +734 -0
  31. package/dst/specbook-link.d.ts +2 -2
  32. package/dst/specbook-mcp.d.ts +2 -1
  33. package/dst/specbook-mcp.js +13 -52
  34. package/dst/specbook-parse-common.d.ts +5 -3
  35. package/dst/specbook-parse-common.js +8 -0
  36. package/dst/specbook-parse-semantic.d.ts +4 -4
  37. package/dst/specbook-parse-semantic.js +4 -65
  38. package/dst/specbook-parse-syntax.d.ts +2 -2
  39. package/dst/specbook-parse-syntax.js +18 -14
  40. package/dst/specbook-parse.d.ts +4 -4
  41. package/dst/specbook-parse.js +3 -3
  42. package/dst/specbook-verbose.d.ts +2 -0
  43. package/dst/specbook-verbose.js +16 -0
  44. package/package.json +3 -7
  45. package/dst/specbook-cmd-edit.d.ts +0 -9
  46. package/dst/specbook-cmd-edit.js +0 -74
  47. package/dst/specbook-cmd-import.d.ts +0 -9
  48. package/dst/specbook-cmd-import.js +0 -78
  49. package/dst/specbook-export-logo.svg +0 -35
  50. package/dst/specbook-llm.d.ts +0 -13
  51. package/dst/specbook-llm.js +0 -51
  52. package/dst/specbook-struct-spec.d.ts +0 -31
@@ -0,0 +1,734 @@
1
+
2
+ SpecBook Models and Formats
3
+ ===========================
4
+
5
+ ```
6
+ ╭───────────────────╮ ╭───────────────────╮
7
+ │ SpecBook │ │ SpecBook │
8
+ │ SCHEMA │ │ SPEC │
9
+ │ META Model │ │ META Model │
10
+ │ (internal) │ │ (internal) │
11
+ ╰─────────┬─────────╯ ╰─────────┬─────────╯
12
+ │ │
13
+ │ │
14
+ ╭─────────▼─────────╮ ┏━━━━━━━━━▼━━━━━━━━━┓
15
+ │ SpecBook │ ┃ SpecBook ┃
16
+ │ SCHEMA ├──────▶ SPEC ┃
17
+ │ Model │ ┃ Model ┃
18
+ ╰───────────────────╯ ┗━━━━━━━━━━━━━━━━━━━┛
19
+ ```
20
+
21
+ **SpecBook** deals with four distinct object models and their particular
22
+ format, each represented in a formal language and serving a particular
23
+ task:
24
+
25
+ - **SpecBook SCHEMA Meta Model**:
26
+ This is an object model, described in *TypeScript*, internal
27
+ to **SpecBook**, which defines the format of all **SpecBook**
28
+ *Schemas*. It is generic and applied onto the **SpecBook SCHEMA
29
+ Model** to validate that it is technically well-structured.
30
+
31
+ - **SpecBook SPEC Meta Model**:
32
+ This is an object model, described in *TypeScript*, internal
33
+ to **SpecBook**, which defines the format of all **SpecBook**
34
+ *Specifications*. It is generic and applied onto the **SpecBook SPEC
35
+ Model** to validate that it is technically well-structured.
36
+
37
+ - **SpecBook SCHEMA Model**:
38
+ This is an object model, described in *YAML*, outside of
39
+ **SpecBook**, which defines a particular **SpecBook** *Schema*.
40
+ It is user-supplied and non-generic but reusable and applied onto
41
+ the **SpecBook SPEC Model** to validate that it is domain-wise
42
+ well-structured.
43
+
44
+ - **SpecBook SPEC Model**:
45
+ This is an object model, described in *Markdown*, outside
46
+ of **SpecBook**, which defines a particular **SpecBook**
47
+ *Specification*. It is user-supplied and non-generic and
48
+ non-reusable and is the standalone specification of a particular
49
+ project.
50
+
51
+ SpecBook SCHEMA Meta Model
52
+ --------------------------
53
+
54
+ ### Structure
55
+
56
+ This meta model defines which domain-specific objects are allowed in
57
+ a *Schema*: the object kinds with their `file` mapping on the first
58
+ level, the object kinds of the deeper levels, and their properties
59
+ with optional value constraints: regular expressions `/xxx/`,
60
+ object references `[[xxx]]`, enumerations `enum(xxx,yyy)`, tag sets
61
+ `tags(xxx,yyy)`, and lists `list(xxx[, ...])` of such alternatives.
62
+
63
+ ```ts
64
+ type Schema = SchemaObject[]
65
+ type SchemaObject = {
66
+ kind: string
67
+ name?: string
68
+ id?: string
69
+ file?: string
70
+ desc?: string
71
+ optional?: boolean
72
+ diagram?: SchemaDiagram
73
+ format?: SchemaFormat
74
+ props?: SchemaProperty[]
75
+ childs?: SchemaObject[]
76
+ }
77
+ type SchemaDiagram = {
78
+ type?: "graph" | "hub" | "grid"
79
+ nodes?: string
80
+ edges?: string
81
+ center?: string
82
+ links?: "props" | "all"
83
+ edgeSource?: string
84
+ edgeTarget?: string
85
+ edgeArity?: string
86
+ hierarchy?: boolean
87
+ onlyConnected?: boolean
88
+ collapse?: boolean
89
+ qualified?: boolean
90
+ properties?: string[]
91
+ config?: Partial<GradiaConfig>
92
+ }
93
+ type SchemaFormat = {
94
+ type?: "auto" | "complex" | "concise"
95
+ maxTableColumns?: number
96
+ withUnusedProps?: boolean
97
+ }
98
+ type SchemaProperty = {
99
+ name: string
100
+ desc?: string
101
+ value?: string
102
+ optional?: boolean
103
+ }
104
+ type SchemaGradiaConfig = Partial<{
105
+ "font-family": string
106
+ "font-embed": boolean
107
+ "color-node-regular-name": string
108
+ "color-node-regular-box": string
109
+ "color-node-regular-border": string
110
+ "color-node-primary-name": string
111
+ "color-node-primary-box": string
112
+ "color-node-primary-border": string
113
+ "color-node-ghost-name": string
114
+ "color-node-ghost-box": string
115
+ "color-node-ghost-border": string
116
+ "color-group-name": string
117
+ "color-group-box": string
118
+ "color-group-border": string
119
+ "color-edge-line": string
120
+ "color-edge-name": string
121
+ "color-edge-arity": string
122
+ "color-edge-halo": string
123
+ "size-canvas-margin": number
124
+ "size-node-width-min": number
125
+ "size-node-width-max": number
126
+ "size-node-height-scale": number
127
+ "size-edge-corner-radius": number
128
+ "size-edge-hop-radius": number
129
+ "size-edge-track-gap": number
130
+ "size-edge-port-gap": number
131
+ "group-box-padding": number
132
+ "group-box-gap": number
133
+ "graph-columns-max": number
134
+ "graph-channel-width-max": number
135
+ "graph-channel-width-min": number
136
+ "graph-gutter-height-max": number
137
+ "graph-gutter-height-min": number
138
+ "graph-node-separation": number
139
+ "graph-rank-separation": number
140
+ "graph-node-degree-max": number
141
+ "hub-channel-width-max": number
142
+ "hub-channel-width-min": number
143
+ "hub-node-gap": number
144
+ "hub-node-degree-max": number
145
+ "grid-columns-max": number
146
+ "grid-columns-min": number
147
+ "grid-gap-horizontal": number
148
+ "grid-gap-vertical": number
149
+ "grid-node-width-equal": boolean
150
+ }>
151
+ ```
152
+
153
+ - `Schema`:
154
+ whole schema: the object kinds the corpus consists of,
155
+ BECAUSE this configures SpecBook for a particular context
156
+
157
+ - `SchemaObject`:
158
+ object kind: an artifact (level 1) or an object nested below it,
159
+ BECAUSE artifacts and their content follow one recursive structure
160
+
161
+ - `SchemaObject.kind: string`:
162
+ kind name of the object (e.g. `SPEC`, `REQUIREMENT`),
163
+ BECAUSE it discriminates objects and anchors `[[xxx]]` references
164
+
165
+ - `SchemaObject.name?: string`:
166
+ exact artifact name (level 1) or name regex (deeper levels),
167
+ BECAUSE artifacts are fixed documents, deeper names need a rule only
168
+
169
+ - `SchemaObject.id?: string`:
170
+ short locally-unique identifier the object explicitly carries in its heading,
171
+ BECAUSE stable ids keep `[[xxx]]` references free of prose names
172
+
173
+ - `SchemaObject.file?: string`:
174
+ Markdown file the level 1 artifact is generated into and resides in,
175
+ optionally shared with the other artifacts carrying the same file,
176
+ BECAUSE artifacts must map deterministically onto files
177
+
178
+ - `SchemaObject.desc?: string`:
179
+ prose description of the object kind,
180
+ BECAUSE it guides the authors and instructs the AI
181
+
182
+ - `SchemaObject.optional?: boolean`:
183
+ whether the kind may be absent below its parent (default: `false`),
184
+ BECAUSE not every kind is mandatory below every parent object
185
+
186
+ - `SchemaObject.diagram?: SchemaDiagram`:
187
+ diagram generated for every object of this kind,
188
+ BECAUSE relations buried in references are graspable only when drawn
189
+
190
+ - `SchemaObject.format?: SchemaFormat`:
191
+ HTML/PDF rendering format of the child objects of this kind,
192
+ BECAUSE rich objects need sections, uniform ones read as tables
193
+
194
+ - `SchemaObject.props?: SchemaProperty[]`:
195
+ properties allowed on objects of this kind,
196
+ BECAUSE they carry the structured, machine-checkable content
197
+
198
+ - `SchemaObject.childs?: SchemaObject[]`:
199
+ object kinds allowed one level below this object (RECURSION),
200
+ BECAUSE the nesting defines and bounds the document structure
201
+
202
+ - `SchemaDiagram`:
203
+ diagram derived for every object of an object kind,
204
+ BECAUSE the relations of a specification are graspable only when drawn
205
+
206
+ - `SchemaDiagram.type?: "graph" | "hub" | "grid"`:
207
+ diagram shape: `graph` (free), `hub` (centered), `grid` (edge-less),
208
+ BECAUSE one object set tells different stories as different shapes
209
+
210
+ - `SchemaDiagram.nodes?: string`:
211
+ comma-separated `[[xxx]]` patterns of nodes (default: self and descendants),
212
+ BECAUSE a diagram is readable only when reduced to relevant objects
213
+
214
+ - `SchemaDiagram.edges?: string`:
215
+ comma-separated `[[xxx]]` patterns of the objects acting as edges, not nodes,
216
+ BECAUSE some relations are objects, yet still connect two nodes
217
+
218
+ - `SchemaDiagram.center?: string`:
219
+ object a `hub` diagram is projected onto (default: `self`),
220
+ BECAUSE a hub needs one focus, not always the owning object
221
+
222
+ - `SchemaDiagram.links?: "props" | "all"`:
223
+ edge source: property values (`props`) or also texts (`all`),
224
+ BECAUSE prose references are incidental and need an opt-in
225
+
226
+ - `SchemaDiagram.edgeSource?: string`:
227
+ edge property naming the source node (default: the parent object),
228
+ BECAUSE an edge object is not always nested below its own source
229
+
230
+ - `SchemaDiagram.edgeTarget?: string`:
231
+ edge property naming the target node (default: the first non-source
232
+ property whose value is a single reference),
233
+ BECAUSE the convention breaks on multiple references per object
234
+
235
+ - `SchemaDiagram.edgeArity?: string`:
236
+ edge property carrying the edge arity (default: `ARITY`),
237
+ BECAUSE cardinality labels are named domain-specifically
238
+
239
+ - `SchemaDiagram.hierarchy?: boolean`:
240
+ whether containment edges derive from the nesting (default: `false`),
241
+ BECAUSE the nesting carries no reference and would stay invisible
242
+
243
+ - `SchemaDiagram.onlyConnected?: boolean`:
244
+ whether a `graph` drops its edge-less nodes (default: `false`),
245
+ BECAUSE isolated nodes dilute a diagram about relationships
246
+
247
+ - `SchemaDiagram.collapse?: boolean`:
248
+ whether a single-node diagram is omitted (default: `true`),
249
+ BECAUSE it carries no information but still consumes space
250
+
251
+ - `SchemaDiagram.qualified?: boolean`:
252
+ whether every node is labeled with its kind (default: `false`),
253
+ BECAUSE names alone are ambiguous across multiple object kinds
254
+
255
+ - `SchemaDiagram.properties?: string[]`:
256
+ property names attached to the nodes as key/value annotations,
257
+ BECAUSE the node name alone rarely makes the nodes comparable
258
+
259
+ - `SchemaDiagram.config?: SchemaGradiaConfig`:
260
+ arbitrary Gradia rendering options (e.g. `grid-columns-max: 5`),
261
+ BECAUSE layout tuning is cosmetics, not a schema concern
262
+
263
+ - `SchemaFormat`:
264
+ HTML/PDF rendering of the child objects of an object kind,
265
+ BECAUSE one uniform rendering fits neither rich nor numerous objects
266
+
267
+ - `SchemaFormat.type?: "auto" | "complex" | "concise"`:
268
+ rendering: `complex` (sections), `concise` (tables), or `auto`,
269
+ BECAUSE sections bury small objects, tables truncate rich ones
270
+
271
+ - `SchemaFormat.maxTableColumns?: number`:
272
+ maximum column count of the tables below (default: `4`),
273
+ BECAUSE the fixed print/PDF page width bounds the table width
274
+
275
+ - `SchemaFormat.withUnusedProps?: boolean`:
276
+ whether unused properties still render (default: `false`),
277
+ BECAUSE empty entries are noise, unless the gap is the message
278
+
279
+ - `SchemaProperty`:
280
+ property allowed on the objects of an object kind,
281
+ BECAUSE properties carry the structured content beside the prose
282
+
283
+ - `SchemaProperty.name: string`:
284
+ property key as it occurs in the Markdown content,
285
+ BECAUSE the key binds the authored content to its constraint
286
+
287
+ - `SchemaProperty.desc?: string`:
288
+ prose description of the property,
289
+ BECAUSE it guides the authors and instructs the AI
290
+
291
+ - `SchemaProperty.value?: string`:
292
+ constraint: regexp `/xxx/`, link `[[xxx]]`, `enum(...)`, `tags(...)`, `list(...)`,
293
+ BECAUSE constrained values stay checkable and resolvable
294
+
295
+ - `SchemaProperty.optional?: boolean`:
296
+ whether the property may be missing (default: `false`),
297
+ BECAUSE not every property applies to every object
298
+
299
+ - `SchemaGradiaConfig`:
300
+ the Gradia rendering options, mirrored from `@rse/gradia`,
301
+ BECAUSE the diagram cosmetics remain Gradia's concern, not SpecBook's
302
+
303
+ - `SchemaGradiaConfig."font-*": string | boolean`:
304
+ font of all diagram texts and whether it is embedded into the SVG,
305
+ BECAUSE an embedded font renders alike where it is not installed
306
+
307
+ - `SchemaGradiaConfig."color-node-*": string`:
308
+ colors of the node boxes in their regular, primary, and ghost role,
309
+ BECAUSE the role of a node has to be readable at a glance
310
+
311
+ - `SchemaGradiaConfig."color-group-*", "color-edge-*": string`:
312
+ colors of the group boxes and of the edges and their labels,
313
+ BECAUSE both have to stay visible yet recede behind the nodes
314
+
315
+ - `SchemaGradiaConfig."size-*", "group-box-*": number`:
316
+ geometry of the canvas, the node boxes, and the edge routing,
317
+ BECAUSE the geometry has to fit both the page and the text lengths
318
+
319
+ - `SchemaGradiaConfig."graph-*": number`:
320
+ layout of a `graph`: columns, channel and gutter bounds, and separations,
321
+ BECAUSE a free network needs room for its edges, not just its nodes
322
+
323
+ - `SchemaGradiaConfig."hub-*": number`:
324
+ layout of a `hub`: channel widths, node gap, and node degree,
325
+ BECAUSE a hub grows sideways and has to be bounded per side
326
+
327
+ - `SchemaGradiaConfig."grid-*": number | boolean`:
328
+ layout of a `grid`: column bounds, tile gaps, and tile width,
329
+ BECAUSE a catalog reads best as evenly sized tiles in stable columns
330
+
331
+ ### Links
332
+
333
+ The edges are derived from the `[[xxx]]` references of the node objects
334
+ -- from their property values only, or, with `links: all`, from their
335
+ descriptions as well.
336
+
337
+ An object acting as an edge (`edges`) connects its parent object to the
338
+ object its target property references. Where an edge object is a sibling
339
+ of its own source instead of a child of it, `edgeSource` names the
340
+ property carrying that source.
341
+
342
+ As the *nesting* of the objects carries no such reference, `hierarchy:
343
+ true` additionally derives a containment edge from every node object to
344
+ each of its child objects which is part of the node set.
345
+
346
+ This is what turns a parent object into the hub of its own children. A
347
+ `grid` is edge-less by definition and hence accepts neither `edges` nor
348
+ `hierarchy`.
349
+
350
+ ### Diagrams
351
+
352
+ An object kind can additionally carry a `diagram` field, which derives
353
+ a [Gradia](https://github.com/rse/gradia) diagram for every object of
354
+ that kind. Its `type` selects the diagram shape (`graph`, `hub`, or
355
+ `grid`), `nodes` and `edges` select the participating objects through
356
+ comma-separated `[[xxx]]` reference patterns (`nodes` defaults to the
357
+ object itself plus all objects below it), and `center` names the object
358
+ a `hub` is projected onto (default: `self`).
359
+
360
+ `onlyConnected` drops the edge-less nodes of a `graph`, `collapse`
361
+ (default: `true`) omits a degenerated diagram consisting of a single
362
+ node only, `qualified` labels every node with its object kind,
363
+ `properties` lists the property names whose values are attached to the
364
+ nodes as key/value annotations (with every `[[xxx]]` reference stripped
365
+ to its target object name), and `config` passes arbitrary Gradia
366
+ rendering options (e.g. `grid-columns-max: 5`) through to the diagram.
367
+
368
+ ### Format
369
+
370
+ An object kind can furthermore carry a `format` field, which controls
371
+ how the HTML/PDF export renders the child objects of every object of
372
+ that kind. Its `type` selects the rendering: `complex` (nested
373
+ sections), `concise` (compact per-kind tables), or the default `auto`,
374
+ which collapses only the deepest level into tables.
375
+
376
+ Below a `concise` object, unconfigured children implicitly stay
377
+ `concise` and render as sub-tables inside the description cells, while
378
+ an explicitly configured `type` is always honored, even a `complex`
379
+ rendering pressed into a cell. `maxTableColumns` (default: `4`) bounds
380
+ the columns of the compact tables below the object (wider groups chunk
381
+ their properties into embedded per-object tables), and `withUnusedProps`
382
+ (default: `false`) unconditionally renders the defined but unused
383
+ properties of that kind, as empty property table lines or table columns.
384
+ Objects collapsed into tables carry no headings and hence leave the
385
+ table of contents and the PDF outline.
386
+
387
+ SpecBook SPEC Meta Model
388
+ ------------------------
389
+
390
+ ### Structure
391
+
392
+ This meta model defines which domain-specific objects are allowed in a
393
+ *Specification*.
394
+
395
+ ```ts
396
+ type Spec = {
397
+ artifacts: SpecArtifact[]
398
+ }
399
+ type SpecArtifact = {
400
+ created: Date
401
+ modified: Date
402
+ objects: SpecObject[]
403
+ }
404
+ type SpecObject = {
405
+ kind: string
406
+ id: string
407
+ anchor?: string
408
+ paren?: string
409
+ name: string
410
+ primary?: boolean
411
+ description?: SpecDescription
412
+ properties: SpecProperty[]
413
+ childs: SpecObject[]
414
+ }
415
+ type SpecDescription = {
416
+ description: string
417
+ rationale?: string
418
+ embedding?: string[]
419
+ }
420
+ type SpecProperty = {
421
+ key: string
422
+ value: string
423
+ embedding?: string[]
424
+ }
425
+ ```
426
+
427
+ - `Spec`:
428
+ whole specification: the artifacts the corpus consists of,
429
+ BECAUSE a specification is one corpus of separately authored files
430
+
431
+ - `Spec.artifacts: SpecArtifact[]`:
432
+ all artifacts parsed from the Markdown files the SCHEMA references,
433
+ BECAUSE the corpus is parsed as a whole to resolve references
434
+
435
+ - `SpecArtifact`:
436
+ single artifact: one level 1 heading plus its file timestamps,
437
+ BECAUSE artifacts are the unit of authoring, review, and generation
438
+
439
+ - `SpecArtifact.created: Date`:
440
+ creation timestamp from the `Created:` "frontmatter" field,
441
+ BECAUSE an origin has to survive Git checkouts and file copies
442
+
443
+ - `SpecArtifact.modified: Date`:
444
+ last modification timestamp from the `Modified:` "frontmatter" field,
445
+ BECAUSE freshness steers review and reconciliation of an artifact
446
+
447
+ - `SpecArtifact.objects: SpecObject[]`:
448
+ root objects of the artifact, opened by its level 1 heading,
449
+ BECAUSE an artifact is the tree below exactly one root object
450
+
451
+ - `SpecObject`:
452
+ object of the artifact tree: the atom of a specification,
453
+ BECAUSE every statement needs an addressable, kind-typed carrier
454
+
455
+ - `SpecObject.kind: string`:
456
+ kind name of the object (e.g. `ENTITY`), case-sensitive,
457
+ BECAUSE the kind decides which schema rules apply to the object
458
+
459
+ - `SpecObject.id: string`:
460
+ locally-unique anchor id, explicitly given or "slugified" from the name,
461
+ BECAUSE every object has to be linkable by a stable handle
462
+
463
+ - `SpecObject.anchor?: string`:
464
+ explicit `{{xxx}}` anchor given in the heading, if any,
465
+ BECAUSE an explicit anchor keeps ids stable when names change
466
+
467
+ - `SpecObject.paren?: string`:
468
+ parenthesized `(xxx)` token trailing the name, if any,
469
+ BECAUSE one token serves as artifact id, property value, or anchor
470
+
471
+ - `SpecObject.name: string`:
472
+ name of the object, as written in its heading or list item,
473
+ BECAUSE the name is the human handle and a reference target
474
+
475
+ - `SpecObject.primary?: boolean`:
476
+ whether the `(*)` marker flags the object as primary,
477
+ BECAUSE some kinds have a distinguished member among their peers
478
+
479
+ - `SpecObject.description?: SpecDescription`:
480
+ description statement of the object, with its rationale,
481
+ BECAUSE the WHAT alone leaves the WHY of an object unrecorded
482
+
483
+ - `SpecObject.properties: SpecProperty[]`:
484
+ key/value properties of the object, in canonical order,
485
+ BECAUSE properties carry the structured, machine-checkable content
486
+
487
+ - `SpecObject.childs: SpecObject[]`:
488
+ child objects nested one level below this object (RECURSION),
489
+ BECAUSE a specification is a tree, not a flat list of statements
490
+
491
+ - `SpecDescription`:
492
+ description of an object: statement, rationale, and images,
493
+ BECAUSE the WHAT and the WHY are stored apart to be rendered apart
494
+
495
+ - `SpecDescription.description: string`:
496
+ statement text of the description (the WHAT),
497
+ BECAUSE the WHAT is the substance every object has to carry
498
+
499
+ - `SpecDescription.rationale?: string`:
500
+ rationale text behind the `, BECAUSE ` split (the WHY),
501
+ BECAUSE a statement without its WHY cannot be judged or revised
502
+
503
+ - `SpecDescription.embedding?: string[]`:
504
+ image files embedded via `![xxx](yyy)`, inlined at parse time,
505
+ BECAUSE the exports have to stand alone, without the image files
506
+
507
+ - `SpecProperty`:
508
+ key/value property attached to an object,
509
+ BECAUSE the structured facts of an object belong beside its prose
510
+
511
+ - `SpecProperty.key: string`:
512
+ property key, as written in the Markdown content,
513
+ BECAUSE the key selects the schema rule the value is checked by
514
+
515
+ - `SpecProperty.value: string`:
516
+ property value, joined from its (possibly wrapped) lines,
517
+ BECAUSE constraints and references are checked against this text
518
+
519
+ - `SpecProperty.embedding?: string[]`:
520
+ image files embedded in the value, inlined at parse time,
521
+ BECAUSE the exports have to stand alone, without the image files
522
+
523
+ SpecBook SCHEMA Model
524
+ ---------------------
525
+
526
+ ### Artifacts
527
+
528
+ A schema consists of a YAML file, which is exactly a serialization of
529
+ the object model defined by the **SpecBook SCHEMA Meta Model**.
530
+
531
+ SpecBook SPEC Model
532
+ -------------------
533
+
534
+ ### Artifacts
535
+
536
+ A specification consists of Markdown *artifact* files, which the SCHEMA
537
+ Model references by their `file` fields and which are resolved against
538
+ a base directory. Every file has to start with
539
+ a "frontmatter" block carrying the `Created:` and `Modified:` timestamps
540
+ (format `yyyy-LL-dd HH:mm`):
541
+
542
+ ```
543
+ ---
544
+ Created: 2026-06-18 10:18
545
+ Modified: 2026-06-18 10:18
546
+ ---
547
+ ```
548
+
549
+ Every level 1 heading opens a new *artifact*, so usually each file
550
+ carries exactly one. Several artifacts nevertheless reside in one file
551
+ whenever the schema configures the same `file` for them: they then
552
+ follow each other on level 1, below the single frontmatter block of that
553
+ file. Both ATX headings (`# <text/>`, `## <text/>`, ...) and Setext
554
+ headings (`<text/>` underlined with `===` or `---`) are supported.
555
+
556
+ ### Object Model
557
+
558
+ An artifact carries a tree of *objects*. Every object has a mandatory
559
+ *kind* (e.g. `ENTITY`), a mandatory *name*, a unique anchor *id*
560
+ (explicitly given or derived from the name), an optional *primary*
561
+ marker, optional *properties* (key/value pairs), an optional
562
+ *description* statement with an optional rationale, and optional *child*
563
+ objects. Which kinds, properties, and nestings are allowed is defined by
564
+ the schema.
565
+
566
+ ### Format Variant: Complex Format
567
+
568
+ This Markdown format to represent objects is usually used on object
569
+ hierarchy levels 1-3 (`#`, `##`, `###`). A heading opens the object,
570
+ an optional unordered list carries its properties, and the remaining
571
+ content up to the next heading is its description:
572
+
573
+ ```
574
+ # <kind/>: <name/>
575
+
576
+ - <key/>: <value/>
577
+ - [...]
578
+
579
+ <statement/>, BECAUSE <rationale/>.
580
+ ```
581
+
582
+ The heading level directly reflects the nesting level: a `##` object
583
+ becomes a child of the preceding `#` object, a `###` object a child of
584
+ the preceding `##` object, etc. Skipping a level (a heading without a
585
+ parent object on the level above) is an error.
586
+
587
+ ### Format Variant: Concise Format
588
+
589
+ This Markdown format to represent objects is usually used on level 4
590
+ and deeper. A single unordered list item carries the entire object as
591
+ `;`-separated segments -- the `<kind/>: <name/>` head, the `<key/>:
592
+ <value/>` properties, and the trailing description -- and may wrap over
593
+ multiple (indented) lines:
594
+
595
+ ```
596
+ - <kind/>: <name/>; <key/>: <value/>; [...];
597
+ <statement/>, BECAUSE <rationale/>.
598
+ ```
599
+
600
+ A list item is recognized as a Concise Format object (instead of a
601
+ property of the enclosing object) by carrying at least one `;` on its
602
+ first line. Every segment of the form `<key/>: <value/>` becomes a
603
+ property (unless it contains a ` BECAUSE `); all remaining segments are
604
+ joined into the description. Child objects nest as indented list items
605
+ below their parent item, where key/value items become properties and
606
+ concise items become child objects of that parent:
607
+
608
+ ```
609
+ - <kind/>: <name/>; <key/>: <value/>;
610
+ <statement/>, BECAUSE <rationale/>.
611
+
612
+ - <kind/>: <name/>; <key/>: <value/>;
613
+ <statement/>, BECAUSE <rationale/>.
614
+ ```
615
+
616
+ ### Format Variant: Grouped Format
617
+
618
+ This Markdown format to represent objects is a special variant of the
619
+ Concise Format. Here, a heading carrying just a *kind* and no `<kind/>:
620
+ <name/>` pair opens a *grouping container* instead of an object. The
621
+ list items below it are Concise Format items whose kind comes from that
622
+ heading, so they start with the (optionally backquoted) name directly:
623
+
624
+ ```
625
+ ### <kind/>
626
+
627
+ - `<name/>`; <key/>: <value/>; [...];
628
+ <statement/>, BECAUSE <rationale/>.
629
+ ```
630
+
631
+ The heading kind is taken literally, so `### STATE` groups objects of
632
+ kind `STATE`. The grouped objects become children of the object the
633
+ heading is nested under. An unquoted name must not contain a `:` (else
634
+ the item would look like a key/value pair) -- backquote it in this case.
635
+
636
+ ### Format Usage
637
+
638
+ When **SpecBook** itself generates specification Markdown, it emits
639
+ the Complex Format on levels 1-3 and the Concise Format from level 4
640
+ upwards. When **SpecBook** edits existing files, it mirrors the format
641
+ each existing object already uses.
642
+
643
+ ### Names, Anchors, and Ids
644
+
645
+ In all formats, the `<name/>` may carry trailing decorations, in any
646
+ order:
647
+
648
+ - `{{<id/>}}`: the explicit Wiki-style anchor, setting the object id
649
+ (e.g. `## ENTITY: Attendee Browser {{attendee}}`).
650
+
651
+ - `(<token/>)`: a parenthesized token with three possible roles: on
652
+ level 1 it becomes the artifact id (e.g. `# SPEC: Data Model (DM)`);
653
+ on deeper levels it acts as the implicit anchor id.
654
+
655
+ - `(*)`: the *primary* marker, flagging the object as primary (e.g.
656
+ the primary attributes of an entity).
657
+
658
+ When no explicit id is given, the id is derived by "slugifying" the name
659
+ (lowercased, with non-alphanumeric character runs dashed). An explicit
660
+ `{{<id/>}}` anchor always takes precedence over a `(<token/>)` id. When
661
+ the schema configures a fixed `id` for an object, it has to be
662
+ explicitly written in the input, via either `{{<id/>}}` or `(<id/>)`.
663
+
664
+ ### Properties
665
+
666
+ In the Complex Format, each property is an unordered list item of the
667
+ form `<key/>: <value/>`, whose value may continue on the following
668
+ indented lines of the item (joined with spaces). In the Concise Format,
669
+ each property is a `<key/>: <value/>` segment. The key is matched
670
+ against the schema exactly as written, and the value has to satisfy the
671
+ configured constraint as a whole.
672
+
673
+ ### Descriptions and Rationales
674
+
675
+ The description of a Complex Format object is all block content below
676
+ its heading (up to the next heading) except the property list:
677
+ paragraphs, blockquotes, ordered lists (e.g. scenario steps), and
678
+ fenced code blocks. A fenced code block
679
+ of language `gradia` is skipped, as it is the derived diagram which
680
+ **SpecBook** itself emits into exported Markdown and which must not
681
+ become authored content on a re-parse. In the Concise Format, the
682
+ description is formed by the non-property segments of the item.
683
+
684
+ A description is split into its *statement* and *rationale* at the first
685
+ `, BECAUSE ` (or `, **BECAUSE** `) occurrence:
686
+
687
+ ```
688
+ The event is created and configured but not visible to attendees,
689
+ BECAUSE an event needs a private setup phase.
690
+ ```
691
+
692
+ ### Wiki-Style References
693
+
694
+ A `[[<reference/>]]` anywhere in a name, property value, description, or
695
+ rationale is a Wiki-style reference to another object, which is checked
696
+ for unique resolvability and rendered as a hyperlink in the HTML/PDF
697
+ exports. A reference is a `.`-separated path of segments, where each
698
+ segment matches a single object as:
699
+
700
+ - `<id/>`: the object id or explicit anchor (e.g. `[[attendee]]`)
701
+ - `<name/>`: the object name (e.g. `[[Attendee Browser]]`)
702
+ - `<kind/>:<name-or-id/>`: kind-qualified (e.g. `[[ENTITY:Event]]`)
703
+ - `<kind/>:*` or `*`: wildcards (for match sets, see below)
704
+
705
+ Each part can be double-quoted to allow spaces, dots, and colons inside
706
+ it (e.g. `[["Vue.js"]]`, whose dot would otherwise be taken as a path
707
+ separator). A multi-segment path matches object chains connected by
708
+ direct parent-to-child steps, where the leading segments up to the root
709
+ may be omitted (e.g. `[[FV.relay]]` matches the object `relay` directly
710
+ below the artifact with id `FV`).
711
+
712
+ A reference has to resolve to exactly one object: a single-segment
713
+ reference tries the variants (1) id/anchor, (2) name, and (3)
714
+ `<kind/>:<name-or-id/>` in order, where the first variant yielding any
715
+ matches decides; several matches are an ambiguity and zero matches an
716
+ unresolvable reference, both reported by the linter. Wildcard references
717
+ resolve into match *sets* and are used in the schema configuration to
718
+ constrain reference-valued properties (e.g. `[[PERSONA:*]]` or
719
+ `[[DM.ENTITY:*]]`).
720
+
721
+ ### Image Embeddings
722
+
723
+ A Markdown image `![<alt/>](<file/>)` inside a description or property
724
+ value embeds a local image file, resolved relative to the artifact file:
725
+ SVG files are inlined as-is; PNG/JPEG files are embedded as base64
726
+ `data:` URLs. URLs and other file types are left untouched.
727
+
728
+ A reference carrying the `{theme}` placeholder -- e.g.
729
+ `![Logo](logo-{theme}.svg)` -- is a *theme-aware* embedding: it expands
730
+ into the two variants `light` and `dark`, both of which have to exist and
731
+ both of which are embedded, as two consecutive entries in this order. The
732
+ HTML export shows just the variant matching the color theme currently
733
+ active in the document, while the PDF export (like print in general)
734
+ always uses the `light` variant.