@rse/specbook 0.9.4 → 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 (53) hide show
  1. package/README.md +64 -319
  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-value.js +1 -1
  41. package/dst/specbook-parse.d.ts +4 -4
  42. package/dst/specbook-parse.js +3 -3
  43. package/dst/specbook-verbose.d.ts +2 -0
  44. package/dst/specbook-verbose.js +16 -0
  45. package/package.json +6 -11
  46. package/dst/specbook-cmd-edit.d.ts +0 -9
  47. package/dst/specbook-cmd-edit.js +0 -74
  48. package/dst/specbook-cmd-import.d.ts +0 -9
  49. package/dst/specbook-cmd-import.js +0 -78
  50. package/dst/specbook-export-logo.svg +0 -35
  51. package/dst/specbook-llm.d.ts +0 -13
  52. package/dst/specbook-llm.js +0 -51
  53. package/dst/specbook-struct-spec.d.ts +0 -31
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- <img src="https://raw.githubusercontent.com/rse/specbook/master/etc/specbook-logo.svg" width="400" align="right" alt=""/>
2
+ <img src="https://raw.githubusercontent.com/rse/specbook/master/etc/specbook-logo-blue-light.svg" width="400" align="right" alt=""/>
3
3
 
4
4
  SpecBook
5
5
  ========
@@ -13,307 +13,44 @@ About
13
13
  format* which can be configured for the specifications of particular
14
14
  contexts through a YAML-based *schema configuration*. SpecBook allows
15
15
  specifications to be *initialized*, *linted*, *exported* (JSON, JSON5,
16
- YAML, TOON, HTML, PDF, and normalized Markdown), *described* to LLMs,
17
- and LLM-assisted *imported* and *edited* -- through an API class with
18
- methods `<cmd>()`, a CLI with commands `specbook <cmd>`, and an MCP
19
- service with tools `specbook_<cmd>()`.
16
+ YAML, TOON, HTML, PDF, and normalized Markdown), and *described* to
17
+ LLMs.
20
18
 
21
- **SpecBook** provides the following distinct features:
22
-
23
- - hierarchical object structure
24
- - strict Markdown-based input format
25
- - complex and concise object to Markdown mappings
26
- - Wiki-style hierarchy-aware object hyperlinks
27
- - canonical Markdown output format
28
- - AST-based JSON, JSON5, YAML, and TOON output formats
29
- - HTML output format for online reading
30
- - PDF output format for printing and archiving
31
- - CLI, MCP, and API interfaces
32
-
33
- Specification Format
34
- --------------------
35
-
36
- ### Artifact Files
37
-
38
- A specification consists of Markdown *artifact* files, which are
39
- scanned recursively from a base directory. Every file has to start with
40
- a frontmatter block carrying the `Created:` and `Modified:` timestamps
41
- (format `yyyy-LL-dd HH:mm`):
42
-
43
- ```
44
- ---
45
- Created: 2026-06-18 10:18
46
- Modified: 2026-06-18 10:18
47
- ---
48
- ```
49
-
50
- Every level 1 heading opens a new *artifact*, so usually each file
51
- carries exactly one. Both ATX headings (`# <text/>`, `## <text/>`, ...)
52
- and Setext headings (`<text/>` underlined with `===` or `---`) are
53
- supported.
54
-
55
- ### Object Model
56
-
57
- An artifact carries a tree of *objects*. Every object has a mandatory
58
- *kind* (e.g. `ENTITY`), a mandatory *name*, a unique anchor *id*
59
- (explicitly given or derived from the name), an optional *primary*
60
- marker, optional *properties* (key/value pairs), an optional
61
- *description* statement with an optional rationale, and optional *child*
62
- objects. Which kinds, properties, and nestings are allowed is defined by
63
- the schema configuration (see below); object kinds and property keys are
64
- case-sensitive and have to be written exactly as configured. Backquotes
65
- in names and property values are preserved as code markup for rendering,
66
- but are ignored when matching names, references, and constraints.
67
-
68
- Three concrete syntaxes exist:
69
-
70
- ### Complex Format
71
-
72
- Usually used on object hierarchy levels 1-3 (`#`, `##`, `###`). A
73
- heading opens the object, an optional unordered list carries its
74
- properties, and the remaining content up to the next heading is its
75
- description:
76
-
77
- ```
78
- # <kind/>: <name/> (<id/>)
79
-
80
- - <key/>: <value/>
81
- - [...]
82
-
83
- <statement/>, BECAUSE <rationale/>.
84
- ```
85
-
86
- The heading level directly reflects the nesting level: a `##` object
87
- becomes a child of the preceding `#` object, a `###` object a child of
88
- the preceding `##` object, etc. Skipping a level (a heading without a
89
- parent object on the level above) is an error.
90
-
91
- ### Concise Format
19
+ **SpecBook** ships with an API class with methods `<xxx>()`, a
20
+ CLI with commands `specbook <xxx>`, and an MCP service with tools
21
+ `specbook_<xxx>()`.
92
22
 
93
- Usually used on level 4 and deeper. A single unordered list item carries
94
- the entire object as `;`-separated segments -- the `<kind/>: <name/>`
95
- head, the `<key/>: <value/>` properties, and the trailing description --
96
- and may wrap over multiple (indented) lines:
97
-
98
- ```
99
- - <kind/>: <name/>; <key/>: <value/>; [...];
100
- <statement/>, BECAUSE <rationale/>.
101
- ```
23
+ **SpecBook** provides the following distinct features:
102
24
 
103
- A list item is recognized as a Concise Format object (instead of a
104
- property of the enclosing object) by carrying at least one `;` on its
105
- first line. Every segment of the form `<key/>: <value/>` becomes a
106
- property (unless it contains a ` BECAUSE `), all remaining segments are
107
- joined into the description. Child objects nest as indented list items
108
- below their parent item, where key/value items become properties and
109
- concise items become child objects of that parent:
25
+ - Hierarchical Object Model
26
+ - Versatile Object to Markdown Mapping
27
+ - Wiki-Style Object Linking
28
+ - Strict Validation with Custom Schema
29
+ - Object Model Diagram Visualisation
30
+ - CLI, MCP, and API Interface
31
+ - AST Exports for AI/LLMs
32
+ - HTML Export for Developers
33
+ - PDF Export for Customers
110
34
 
111
- ```
112
- - <kind/>: <name/>; <key/>: <value/>;
113
- <statement/>, BECAUSE <rationale/>.
114
- - <kind/>: <name/>; <key/>: <value/>;
115
- <statement/>, BECAUSE <rationale/>.
116
- ```
35
+ Overview Poster
36
+ ---------------
117
37
 
118
- ### Grouped Format
38
+ [![poster](etc/poster.png)](etc/poster.pdf)
119
39
 
120
- A heading carrying just a *kind* and no `<kind/>: <name/>` pair opens a
121
- *grouping container* instead of an object. The list items below it are
122
- Concise Format items whose kind comes from that heading, so they start
123
- with the (optionally backquoted) name directly:
40
+ Rendering Example
41
+ -----------------
124
42
 
125
- ```
126
- ### <kinds/>
43
+ ### HTML Rendering (Light Theme)
127
44
 
128
- - `<name/>`; <key/>: <value/>; [...];
129
- <statement/>, BECAUSE <rationale/>.
130
- ```
45
+ ![screenshot-light](etc/screenshot-light.png)
131
46
 
132
- The heading kind is singularized by stripping one trailing `S`, so both
133
- `### STATES` and `### STATE` group objects of kind `STATE`. The grouped
134
- objects become children of the object the heading is nested under. An
135
- unquoted name must not contain a `:` (else the item would look like a
136
- key/value pair) -- backquote it in this case.
137
-
138
- When **SpecBook** itself generates specification Markdown, it emits
139
- the Complex Format on levels 1-3 and the Concise Format from level 4
140
- upwards. When **SpecBook** edits existing files, it mirrors the format
141
- each existing object already uses.
142
-
143
- ### Names, Anchors, and Ids
144
-
145
- In all formats, the `<name/>` may carry trailing decorations, in any
146
- order:
147
-
148
- - `{{<id/>}}`: the explicit Wiki-style anchor, setting the object id
149
- (e.g. `## ENTITY: Attendee Browser {{attendee}}`).
150
-
151
- - `(*)`: the *primary* marker, flagging the object as primary (e.g.
152
- the primary attributes of an entity).
153
-
154
- - `(<token/>)`: a parenthesized token with three possible roles: on
155
- level 1 it becomes the artifact id (e.g. `# SPEC: Data Model (DM)`);
156
- on deeper levels it is either accepted as the value of a still
157
- missing configured property whose constraint it matches (e.g.
158
- `### SCENARIO: Successful Token Login (Main)` satisfying an
159
- `enum(Main,Alternative,Exceptional)` property), or else acts as the
160
- implicit anchor id.
161
-
162
- - `<a id="<id/>"></a>`: an HTML anchor at the very end of a heading,
163
- as an alternative to `{{<id/>}}` (emitted by **SpecBook** itself
164
- when exporting normalized Markdown).
165
-
166
- When no explicit id is given, the id is derived by slugifying the name
167
- (lowercased, with non-alphanumeric character runs dashed). An explicit
168
- `{{<id/>}}` anchor always takes precedence over a `(<token/>)` id. When
169
- the schema configures a fixed `id` for an object, it has to be
170
- explicitly written in the input, via either `{{<id/>}}` or `(<id/>)`.
171
-
172
- ### Properties
173
-
174
- In the Complex Format, each property is an unordered list item of the
175
- form `<key/>: <value/>`, whose value may continue on the following
176
- indented lines of the item (joined with spaces). In the Concise Format,
177
- each property is a `<key/>: <value/>` segment. A property key may carry
178
- a trailing parenthesized annotation (e.g. `WHEN (Context)`), which is
179
- kept for rendering but ignored when matching the key against the schema.
180
-
181
- A multi-token convenience exists for pattern-constrained properties:
182
- when a property value fails its (regex or enum) constraint as a whole,
183
- but consists of multiple whitespace-separated tokens of which one
184
- matches the property's own constraint, that token is kept as the value
185
- and the remaining tokens are distributed (case-insensitively) across the
186
- other still unset pattern-constrained properties of the object. Tokens
187
- assignable to no property are reported as errors.
188
-
189
- ### Descriptions and Rationales
190
-
191
- The description of a Complex Format object is all block content below
192
- its heading (up to the next heading) except the property list:
193
- paragraphs, blockquotes, ordered lists (e.g. scenario steps), and
194
- fenced code blocks. A fenced code block
195
- of language `gradia` is skipped, as it is the derived diagram which
196
- **SpecBook** itself emits into exported Markdown and which must not
197
- become authored content on a re-parse. In the Concise Format, the
198
- description is formed by the non-property segments of the item.
199
-
200
- A description is split into its *statement* and *rationale* at the first
201
- `, BECAUSE ` (or `, **BECAUSE** `) occurrence:
47
+ ### HTML Rendering (Dark Theme)
202
48
 
203
- ```
204
- The event is created and configured but not visible to attendees,
205
- BECAUSE an event needs a private setup phase.
206
- ```
49
+ ![screenshot-dark](etc/screenshot-dark.png)
207
50
 
208
- ### Wiki-Style References
209
-
210
- A `[[<reference/>]]` anywhere in a name, property value, description, or
211
- rationale is a Wiki-style reference to another object, which is checked
212
- for unique resolvability and rendered as a hyperlink in the HTML/PDF
213
- exports. A reference is a `.`-separated path of segments, where each
214
- segment matches a single object as:
215
-
216
- - `<id/>`: the object id or explicit anchor (e.g. `[[attendee]]`)
217
- - `<name/>`: the object name (e.g. `[[Attendee Browser]]`)
218
- - `<kind/>:<name-or-id/>`: kind-qualified (e.g. `[[ENTITY:Event]]`)
219
- - `<kind/>:*` or `*`: wildcards (for match sets, see below)
220
-
221
- Each part can be double-quoted to allow spaces, dots, and colons inside
222
- it (e.g. `[["Vue.js"]]`, whose dot would otherwise be taken as a path
223
- separator). A multi-segment path matches object chains connected by
224
- direct parent-to-child steps, where the leading segments up to the root
225
- may be omitted (e.g. `[[FV.relay]]` matches the object `relay` directly
226
- below the artifact with id `FV`).
227
-
228
- A reference has to resolve to exactly one object: a single-segment
229
- reference tries the variants (1) id/anchor, (2) name, and (3)
230
- `<kind/>:<name-or-id/>` in order, where the first variant yielding any
231
- matches decides; several matches are an ambiguity and zero matches an
232
- unresolvable reference, both reported by the linter. Wildcard references
233
- resolve into match *sets* and are used in the schema configuration to
234
- constrain reference-valued properties (e.g. `[[PERSONA:*]]` or
235
- `[[DM.ENTITY:*]]`).
236
-
237
- ### Image Embeddings
238
-
239
- A Markdown image `![<alt/>](<file/>)` inside a description or property
240
- value embeds a local image file, resolved relative to the artifact file:
241
- SVG files are inlined as-is; PNG/JPEG files are embedded as base64
242
- `data:` URLs. URLs and other file types are left untouched.
243
-
244
- ### Normalization
245
-
246
- The semantic phase validates the parsed objects against the schema
247
- configuration (unknown kinds and properties, missing required properties
248
- and child kinds, and violated value constraints are reported with
249
- file/line-precise diagnostics) and then normalizes the specification:
250
- artifacts, child objects, and properties are stably reordered along the
251
- schema definition, so the exports always present them in their
252
- configured canonical order.
253
-
254
- Schema Configuration
255
- --------------------
256
-
257
- The YAML schema configuration (see
258
- [`ase.specbook.yaml`](smp/ase.specbook.yaml) for a comprehensive
259
- example) defines which domain-specific objects are allowed: the artifact
260
- kinds with their `file` mapping on the first level, the object kinds of
261
- the deeper levels, and their properties with optional value constraints:
262
- regular expressions `/xxx/`, object references `[[xxx]]`, enumerations
263
- `enum(xxx,yyy)`, tag sets `tags(xxx,yyy)`, and lists `list(xxx[, ...])`
264
- of such alternatives. The `file` field is only allowed on the first
265
- (artifact) level.
266
-
267
- An object kind can additionally carry a `diagram` field, which derives a
268
- [Gradia](https://github.com/rse/gradia) diagram for every object of that
269
- kind. Its `type` selects the diagram shape (`graph`, `hub`, or `grid`),
270
- `nodes` and `edges` select the participating objects through
271
- comma-separated `[[xxx]]` reference patterns (`nodes` defaults to the
272
- object itself plus all objects below it), `center` names the object a
273
- `hub` is projected onto (default: `self`), `onlyConnected` drops the
274
- edge-less nodes of a `graph`, `collapse` (default: `true`) omits a
275
- degenerated diagram consisting of a single node only, `qualified` labels
276
- every node with its object kind, `properties` lists the property names
277
- whose values are attached to the nodes as key/value annotations (with
278
- every `[[xxx]]` reference stripped to its target object name), and
279
- `config` passes arbitrary Gradia rendering options (e.g.
280
- `grid-columns-max: 5`) through to the diagram.
281
-
282
- The edges are derived from the `[[xxx]]` references of the node objects
283
- -- from their property values only, or, with `links: all`, from their
284
- descriptions as well. As the *nesting* of the objects carries no such
285
- reference, `hierarchy: true` additionally derives a containment edge
286
- from every node object to each of its child objects which is part of the
287
- node set. This is what turns a parent object into the hub of its own
288
- children, as the `TIER` objects of the sample schema configuration
289
- demonstrate:
290
-
291
- ```yaml
292
- - kind: TIER
293
- diagram:
294
- type: hub
295
- center: self
296
- hierarchy: true
297
- ```
51
+ ### PDF Rendering
298
52
 
299
- A `grid` is edge-less by definition and hence accepts neither `edges`
300
- nor `hierarchy`.
301
-
302
- An object kind can furthermore carry a `format` field, which controls
303
- how the HTML/PDF export renders the child objects of every object of
304
- that kind. Its `type` selects the rendering: `complex` (nested
305
- sections), `concise` (compact per-kind tables), or the default `auto`,
306
- which collapses only the deepest level into tables. Below a `concise`
307
- object, unconfigured children implicitly stay `concise` and render as
308
- sub-tables inside the description cells, while an explicitly configured
309
- `type` is always honored, even a `complex` rendering pressed into a
310
- cell. `maxTableColumns` (default: `4`) bounds the columns of the compact
311
- tables below the object (wider groups chunk their properties into
312
- embedded per-object tables), and `withUnusedProps` (default: `false`)
313
- unconditionally renders the defined but unused properties of that kind,
314
- as empty property table lines or table columns. Objects collapsed into
315
- tables carry no headings and hence leave the table of contents and the
316
- PDF outline.
53
+ ![screenshot-print](etc/screenshot-print.png)
317
54
 
318
55
  Usage
319
56
  -----
@@ -321,17 +58,22 @@ Usage
321
58
  ### CLI
322
59
 
323
60
  ```
324
- $ specbook init [-v] [-c <yaml-file>] [-b <basedir>]
325
- $ specbook lint [-v] [-c <yaml-file>] [-b <basedir>]
326
- $ specbook export [-v] [-c <yaml-file>] [-b <basedir>] [-o [<format>:]<output-file>] [...]
327
- $ specbook describe [-v] [-c <yaml-file>] [-o <markdown-file>]
328
- $ specbook import [-v] [-c <yaml-file>] [-b <basedir>] <input-files...>
329
- $ specbook edit [-v] [-c <yaml-file>] [-b <basedir>] <query>
61
+ $ specbook init [-v] -c <yaml-file> [-b <basedir>]
62
+ $ specbook lint [-v] -c <yaml-file> [-b <basedir>]
63
+ $ specbook export [-v] -c <yaml-file> [-b <basedir>] [-o [<format>:]<output-file>] [...]
64
+ $ specbook describe [-v] [-c <yaml-file>] [-b <basedir>] [-e] [-o <markdown-file>]
330
65
  $ specbook mcp [-v]
331
66
  ```
332
67
 
333
- The base directory `-b`/`--basedir` (default: `.`) is scanned
334
- recursively for the specification Markdown files, and generated
68
+ The YAML schema configuration `-c`/`--config` is mandatory for `init`,
69
+ `lint`, and `export`, as it determines the specification: exactly the
70
+ artifact files its `file` fields reference are loaded and parsed, all
71
+ other Markdown files below the base directory are ignored. A referenced
72
+ file which is absent is reported, unless all of its artifacts are
73
+ `optional`.
74
+
75
+ The base directory `-b`/`--basedir` (default: `.`) is the directory the
76
+ referenced artifact files are resolved against, and generated
335
77
  specification Markdown files are placed inside it, too.
336
78
 
337
79
  The export output option `-o`/`--output` (default: `-` for stdout) can
@@ -340,41 +82,44 @@ occur multiple times. The format (`json`, `json5`, `yaml`, `toon`,
340
82
  it is explicitly given as a `<format>:` prefix, and plain `-` (stdout)
341
83
  defaults to JSON.
342
84
 
85
+ The `describe` command outputs the description of the SpecBook models
86
+ and formats. If the configuration file `-c`/`--config` or the base
87
+ directory `-b`/`--basedir` is given, it additionally appends a *SpecBook
88
+ Project Instantiation* section which points to these artifacts of the
89
+ particular project. With `-e`/`--embed` the YAML schema configuration is
90
+ embedded verbatim instead of just being referenced, so the resulting
91
+ document describes the specification format of the project entirely on
92
+ its own.
93
+
343
94
  The default value of every CLI option `--xxx` can be overridden by a
344
95
  corresponding `SPECBOOK_XXX` environment variable (e.g.
345
96
  `SPECBOOK_BASEDIR`, `SPECBOOK_CONFIG`, `SPECBOOK_OUTPUT`,
346
- `SPECBOOK_VERBOSE`), while an explicitly supplied option always wins.
97
+ `SPECBOOK_VERBOSE`), while an explicitly supplied option always wins. A
98
+ `SPECBOOK_CONFIG` value also satisfies the otherwise mandatory
99
+ `-c`/`--config` option.
347
100
 
348
101
  ### API
349
102
 
350
103
  ```ts
351
- import { SpecBook } from "@rse/specbook"
104
+ import { SpecBook, renderVerbose } from "@rse/specbook"
352
105
 
353
- const specbook = new SpecBook({ verbose: (msg) => console.error(msg) })
354
- const result = await specbook.lint({ config: "smp/ase.specbook.yaml", basedir: "smp/broadcast" })
106
+ const specbook = new SpecBook({
107
+ verbose: (cmd, msg) => console.error(`specbook: ${cmd}: ${renderVerbose(msg)}`)
108
+ })
109
+ const result = await specbook.lint({
110
+ config: "smp/ase.specbook.yaml",
111
+ basedir: "smp/broadcast"
112
+ })
355
113
  ```
356
114
 
357
115
  ### MCP
358
116
 
359
- Run `specbook mcp` to serve the tools `specbook_init`,
360
- `specbook_lint`, `specbook_export`, `specbook_describe`,
361
- `specbook_import`, and `specbook_edit` over stdio.
362
-
363
- ### AI Configuration
364
-
365
- The commands `import` and `edit` use an LLM. The environment variables
366
- `SPECBOOK_AI_PROVIDER` (`anthropic`, `openai`, `openrouter`, or
367
- `ollama`) and
368
- `SPECBOOK_AI_MODEL` provide the defaults, which the CLI options
369
- `--provider`/`--model` (respectively the MCP/API parameters `provider`/
370
- `model`) override. The provider API keys come from the standard
371
- environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`,
372
- `OPENROUTER_API_KEY`), while `ollama` needs no key and uses the local
373
- Ollama service.
117
+ Run `specbook mcp` to serve the tools `specbook_init`, `specbook_lint`,
118
+ `specbook_export`, and `specbook_describe` over stdio.
374
119
 
375
120
  License
376
121
  -------
377
122
 
378
- Copyright &copy; 2026 Dr. Ralf S. Engelschall (http://engelschall.com/)<br/>
379
- Licensed under Apache 2.0 (https://spdx.org/licenses/Apache-2.0)
123
+ Copyright &copy; 2026 [Dr. Ralf S. Engelschall](http://engelschall.com/)<br/>
124
+ Licensed under [Apache 2.0](https://spdx.org/licenses/Apache-2.0)
380
125
 
@@ -1,47 +1,40 @@
1
1
  import { renderDiagnostic, renderDiagnosticVerbose, type Diagnostic } from "./specbook-diagnostic.js";
2
2
  import { type LintResult } from "./specbook-cmd-lint.js";
3
3
  import { parseOutputSpec, formats, type ExportFormat } from "./specbook-cmd-export.js";
4
+ import { literal, renderVerbose } from "./specbook-verbose.js";
5
+ export { literal, renderVerbose };
4
6
  export { formats, parseOutputSpec, type ExportFormat };
5
7
  export { renderDiagnostic, renderDiagnosticVerbose, type Diagnostic };
6
8
  export { type LintResult };
7
- export type { Specification, Artifact, Object, Description, Property } from "./specbook-struct-spec.js";
8
- export type { SchemaSpecification, SchemaObject, SchemaProperty } from "./specbook-struct-schema.js";
9
+ export type { Spec, SpecArtifact, SpecObject, SpecDescription, SpecProperty } from "./specbook-format-spec.js";
10
+ export type { Schema, SchemaObject, SchemaProperty } from "./specbook-format-schema.js";
9
11
  export declare const version: string;
12
+ export type VerboseSink = (cmd: string, msg: string) => void;
10
13
  export interface SpecBookOptions {
11
- verbose?: (msg: string) => void;
14
+ verbose?: VerboseSink;
12
15
  }
13
16
  export declare class SpecBook {
14
17
  private verbose;
15
18
  constructor(options?: SpecBookOptions);
19
+ private verboseOf;
20
+ private requireConfigFile;
16
21
  private requireConfig;
17
22
  init(options: {
18
- config?: string;
23
+ config: string;
19
24
  basedir?: string;
20
25
  }): Promise<string[]>;
21
26
  lint(options: {
22
- config?: string;
27
+ config: string;
23
28
  basedir?: string;
24
29
  }): Promise<LintResult>;
25
30
  export(options: {
26
- config?: string;
31
+ config: string;
27
32
  basedir?: string;
28
33
  formats?: ExportFormat[];
29
34
  }): Promise<Buffer[]>;
30
35
  describe(options: {
31
36
  config?: string;
32
- }): Promise<string>;
33
- import(options: {
34
- config?: string;
35
37
  basedir?: string;
36
- inputs: string[];
37
- provider?: string;
38
- model?: string;
39
- }): Promise<string[]>;
40
- edit(options: {
41
- config?: string;
42
- basedir?: string;
43
- query: string;
44
- provider?: string;
45
- model?: string;
46
- }): Promise<string[]>;
38
+ embed?: boolean;
39
+ }): Promise<string>;
47
40
  }
@@ -11,10 +11,10 @@ import { renderDiagnostic, renderDiagnosticVerbose } from "./specbook-diagnostic
11
11
  import { initSpecification } from "./specbook-cmd-init.js";
12
12
  import { lint } from "./specbook-cmd-lint.js";
13
13
  import { exportSpecification, parseOutputSpec, formats } from "./specbook-cmd-export.js";
14
- import { describeConfiguration } from "./specbook-cmd-describe.js";
15
- import { importSpecification } from "./specbook-cmd-import.js";
16
- import { editSpecification } from "./specbook-cmd-edit.js";
14
+ import { describeFormat } from "./specbook-cmd-describe.js";
15
+ import { literal, renderVerbose } from "./specbook-verbose.js";
17
16
  /* re-export the central types for API consumers */
17
+ export { literal, renderVerbose };
18
18
  export { formats, parseOutputSpec };
19
19
  export { renderDiagnostic, renderDiagnosticVerbose };
20
20
  /* the own version, taken from the package manifest, which resides one
@@ -29,11 +29,20 @@ export class SpecBook {
29
29
  constructor(options = {}) {
30
30
  this.verbose = options.verbose ?? (() => { });
31
31
  }
32
- /* load a mandatory YAML schema configuration, failing on any problem */
33
- requireConfig(file) {
32
+ /* bind the verbose sink to a particular command */
33
+ verboseOf(cmd) {
34
+ return (msg) => this.verbose(cmd, msg);
35
+ }
36
+ /* require the file of the mandatory YAML schema configuration */
37
+ requireConfigFile(file) {
34
38
  if (file === undefined)
35
39
  throw new Error("YAML schema configuration required");
36
- this.verbose(`loading configuration "${file}"`);
40
+ return file;
41
+ }
42
+ /* load a mandatory YAML schema configuration, failing on any problem */
43
+ requireConfig(file, verbose) {
44
+ file = this.requireConfigFile(file);
45
+ verbose(`loading configuration "${literal(file)}"`);
37
46
  const { config, diagnostics } = loadConfig(file);
38
47
  if (config === null)
39
48
  throw new Error("invalid configuration:\n" +
@@ -43,12 +52,14 @@ export class SpecBook {
43
52
  /* initialize the configured specification artifact files
44
53
  below the base directory */
45
54
  async init(options) {
55
+ const verbose = this.verboseOf("init");
46
56
  return initSpecification({ ...options,
47
- config: this.requireConfig(options.config), verbose: this.verbose });
57
+ config: this.requireConfig(options.config, verbose), verbose });
48
58
  }
49
59
  /* lint the specification Markdown files below the base directory */
50
60
  async lint(options) {
51
- return lint({ config: options.config, basedir: options.basedir ?? ".", verbose: this.verbose });
61
+ return lint({ config: this.requireConfigFile(options.config),
62
+ basedir: options.basedir ?? ".", verbose: this.verboseOf("lint") });
52
63
  }
53
64
  /* export the specification Markdown files below the base directory
54
65
  as JSON, JSON5, YAML, TOON, HTML, PDF, or normalized Markdown,
@@ -56,31 +67,23 @@ export class SpecBook {
56
67
  requested format (best-effort: diagnostics do not prevent the
57
68
  export, as validation is the concern of lint) */
58
69
  async export(options) {
59
- const result = lint({ config: options.config, basedir: options.basedir ?? ".", verbose: this.verbose });
70
+ const verbose = this.verboseOf("export");
71
+ const result = lint({ config: this.requireConfigFile(options.config),
72
+ basedir: options.basedir ?? ".", verbose });
60
73
  for (const diagnostic of result.diagnostics)
61
- this.verbose(`diagnostic: ${renderDiagnostic(diagnostic)}`);
74
+ verbose(`diagnostic: ${renderDiagnostic(diagnostic)}`);
62
75
  if (result.specification.artifacts.length === 0)
63
76
  throw new Error("unexportable specification:\n" +
64
77
  result.diagnostics.map(renderDiagnostic).join("\n"));
65
78
  const buffers = new Array();
66
79
  for (const format of options.formats ?? ["json"])
67
- buffers.push(await exportSpecification(result.specification, format, this.verbose, result.config));
80
+ buffers.push(await exportSpecification(result.specification, format, verbose, result.config));
68
81
  return buffers;
69
82
  }
70
- /* describe the configured specification format as Markdown */
83
+ /* describe the generic SpecBook models and formats as Markdown,
84
+ optionally pointing to the artifacts of the particular project */
71
85
  async describe(options) {
72
- return describeConfiguration(this.requireConfig(options.config));
73
- }
74
- /* import foreign sources into the specification artifact files
75
- below the base directory */
76
- async import(options) {
77
- return importSpecification({ ...options,
78
- config: this.requireConfig(options.config), verbose: this.verbose });
79
- }
80
- /* apply an edit request to the specification artifact files
81
- below the base directory */
82
- async edit(options) {
83
- return editSpecification({ ...options,
84
- config: this.requireConfig(options.config), verbose: this.verbose });
86
+ this.verboseOf("describe")("describing the SpecBook models and formats");
87
+ return describeFormat(options);
85
88
  }
86
89
  }