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