@entropicwarrior/sdoc 0.1.18 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -10
- package/docs/reference/sdoc-authoring.sdoc +126 -7
- package/lexica/specification.sdoc +197 -29
- package/package.json +3 -3
- package/src/notion-renderer.js +3 -0
- package/src/sdoc.js +376 -132
- package/src/slide-renderer.js +21 -6
- package/tools/sdoc2html +32 -0
package/README.md
CHANGED
|
@@ -87,20 +87,26 @@ All `.sdoc` files are designed for progressive disclosure — read the `@about`
|
|
|
87
87
|
{
|
|
88
88
|
Unlimited nesting. Each scope is independently addressable.
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
```python
|
|
91
|
+
def hello():
|
|
92
|
+
print("Hello from SDOC")
|
|
93
|
+
```
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
#
|
|
96
|
+
# Status :example
|
|
97
97
|
{
|
|
98
|
-
{[
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
{[table 60% center]
|
|
99
|
+
Endpoint | Status
|
|
100
|
+
/v2/api | {+Active+}
|
|
101
|
+
/v1/api | {-Deprecated-}
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
|
|
105
|
+
# Internal Notes :comment
|
|
106
|
+
{
|
|
107
|
+
This scope is invisible in rendered output but
|
|
108
|
+
stays in the AST for tooling and agents.
|
|
109
|
+
}
|
|
104
110
|
}
|
|
105
111
|
```
|
|
106
112
|
|
|
@@ -153,7 +159,7 @@ Markdown-style images with optional width and alignment:
|
|
|
153
159
|
|
|
154
160
|
### Tables
|
|
155
161
|
|
|
156
|
-
Pipe-delimited tables with optional `borderless` and `
|
|
162
|
+
Pipe-delimited tables with optional flags for appearance (`borderless`, `headerless`), width (`auto`, `60%`, `400px`), and alignment (`left`, `center`, `right`). All flags compose freely.
|
|
157
163
|
|
|
158
164
|
### Lists
|
|
159
165
|
|
|
@@ -167,6 +173,18 @@ Tag any section with `@id` and cross-reference it anywhere with `@id` — render
|
|
|
167
173
|
|
|
168
174
|
Turn any SDOC file into an HTML slide deck with themes, layouts (center, two-column), speaker notes, and PDF export.
|
|
169
175
|
|
|
176
|
+
### Scope Types
|
|
177
|
+
|
|
178
|
+
Classify scopes with a `:type` annotation — `:schema`, `:warning`, `:deprecated`, `:example`, or any custom label. Types render as `data-scope-type` attributes and CSS classes for styling.
|
|
179
|
+
|
|
180
|
+
### Data Blocks
|
|
181
|
+
|
|
182
|
+
Tag a JSON code fence with `:data` and the parser validates and stores the parsed result on the AST node. `extractDataBlocks()` gives programmatic access. Ideal for embedding schemas, configs, and structured metadata alongside prose.
|
|
183
|
+
|
|
184
|
+
### Comment Scopes
|
|
185
|
+
|
|
186
|
+
A `:comment` scope is excluded from rendered output but stays in the AST — perfect for agent instructions, internal notes, and build metadata that readers shouldn't see.
|
|
187
|
+
|
|
170
188
|
### Custom Styling
|
|
171
189
|
|
|
172
190
|
Per-folder `sdoc.config.json` or per-file `@meta` scope for custom CSS, headers, footers, and confidentiality banners. Configs cascade from workspace root to file.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
{
|
|
5
5
|
type: skill
|
|
6
6
|
|
|
7
|
-
sdoc-version: 0.
|
|
7
|
+
sdoc-version: 0.2
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
# About @about
|
|
@@ -178,7 +178,7 @@ Content of Section B.
|
|
|
178
178
|
|
|
179
179
|
**Important:** \`{[.]}\` with the closing brace on the same line creates an empty list — the brace closes the block immediately. Always put the closing \`}\` on a separate line after the items.
|
|
180
180
|
|
|
181
|
-
Implicit lists
|
|
181
|
+
Implicit lists work for both bullet (\`-\`) and numbered (\`1.\`, \`2.\`, etc.) items. Each item must be a single line. For multi-line item titles or item body content, use the explicit \`{[.]}\` or \`{[#]\` block form.
|
|
182
182
|
|
|
183
183
|
**Task lists** — checkbox syntax inside explicit list blocks:
|
|
184
184
|
|
|
@@ -244,6 +244,27 @@ Content of Section B.
|
|
|
244
244
|
```
|
|
245
245
|
|
|
246
246
|
\`borderless\` removes borders and row striping. \`headerless\` treats all rows as data (no header). Flags combine in any order.
|
|
247
|
+
|
|
248
|
+
Width and alignment flags control table sizing and position:
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
{[table 60% center]
|
|
252
|
+
Endpoint | Status
|
|
253
|
+
/v2/weather | Active
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
{[table auto]
|
|
257
|
+
Key | Value
|
|
258
|
+
Version | 2.0
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
{[table 400px right borderless]
|
|
262
|
+
v2.0 | Current
|
|
263
|
+
v1.0 | Deprecated
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Width: \`auto\` (shrink to content), \`NN%\` (percentage), or \`NNpx\` (pixels). Default is 100%. Alignment: \`left\` (default), \`center\`, or \`right\`. All flags compose freely in any order.
|
|
247
268
|
}
|
|
248
269
|
|
|
249
270
|
# Inline Formatting @inline-formatting
|
|
@@ -265,7 +286,7 @@ Content of Section B.
|
|
|
265
286
|
\`\{~text~\}\` | Highlight (yellow)
|
|
266
287
|
}
|
|
267
288
|
|
|
268
|
-
Links: \`[Link text](https://example.com)\`
|
|
289
|
+
Links: \`[Link text](https://example.com)\` or \`[Other doc](./other-file.sdoc)\`. Relative paths resolve from the document's directory.
|
|
269
290
|
|
|
270
291
|
Images: \`\`
|
|
271
292
|
|
|
@@ -279,7 +300,7 @@ Content of Section B.
|
|
|
279
300
|
 
|
|
280
301
|
```
|
|
281
302
|
|
|
282
|
-
Autolinks: \`\<https://example.com\>\`
|
|
303
|
+
Autolinks: \`\<https://example.com\>\` — angle brackets are optional; bare URLs starting with \`http://\`, \`https://\`, or \`mailto:\` are also auto-linked.
|
|
283
304
|
|
|
284
305
|
Math: Use \`\$...\$\` for inline math and \`\$\$...\$\$\` for display math. Use \`\\\`\\\`\\\`math\` code fences for multi-line equations. A plain \`\$\` followed by a digit (e.g. \`\$100\`) does not trigger math mode.
|
|
285
306
|
}
|
|
@@ -380,7 +401,7 @@ Content of Section B.
|
|
|
380
401
|
@meta {
|
|
381
402
|
type: doc
|
|
382
403
|
|
|
383
|
-
sdoc-version: 0.
|
|
404
|
+
sdoc-version: 0.2
|
|
384
405
|
|
|
385
406
|
company: Irreversible Inc.
|
|
386
407
|
|
|
@@ -420,7 +441,7 @@ Content of Section B.
|
|
|
420
441
|
|
|
421
442
|
\`tags: tag1, tag2\` — comma-separated tags.
|
|
422
443
|
|
|
423
|
-
\`sdoc-version: 0.
|
|
444
|
+
\`sdoc-version: 0.2\` — SDOC format version. A parser warning is
|
|
424
445
|
emitted when this key is missing from \`@meta\`.
|
|
425
446
|
}
|
|
426
447
|
|
|
@@ -441,7 +462,7 @@ Content of Section B.
|
|
|
441
462
|
{
|
|
442
463
|
Backslash escapes special characters: \`\\\\\` \`\\{\` \`\\}\` \`\\@\`
|
|
443
464
|
\`\\[\` \`\\]\` \`\\(\` \`\\)\` \`\\*\` \`\\~\` \`\\#\` \`\\!\` \`\\\<\`
|
|
444
|
-
\`\\\>\` \`\\\$\` \`\\+\` \`\\=\` \`\\-\` \`\\^\`
|
|
465
|
+
\`\\\>\` \`\\\$\` \`\\+\` \`\\=\` \`\\-\` \`\\^\` \`\\?\`
|
|
445
466
|
|
|
446
467
|
A line starting with \`\\#\` renders as a literal \`#\` (not a heading). Use \`\\\$\` to prevent a dollar sign from starting math mode.
|
|
447
468
|
}
|
|
@@ -454,6 +475,89 @@ Content of Section B.
|
|
|
454
475
|
- **Commas:** commas between list items or scopes are allowed but ignored — use them if you find them readable.
|
|
455
476
|
}
|
|
456
477
|
}
|
|
478
|
+
|
|
479
|
+
# Scope Types @scope-types
|
|
480
|
+
{
|
|
481
|
+
A \`:type\` annotation on a heading gives the scope semantic meaning. Place it after the optional \`@id\`:
|
|
482
|
+
|
|
483
|
+
```
|
|
484
|
+
# User Authentication @auth :requirement
|
|
485
|
+
{
|
|
486
|
+
The system shall authenticate users via OAuth 2.0.
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
# OAuth Flow :specification @oauth-flow
|
|
490
|
+
{
|
|
491
|
+
Implements @auth using the authorization code flow.
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
# Deprecation Notice :warning
|
|
495
|
+
{
|
|
496
|
+
This API will be removed in v3.0.
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
{[.]
|
|
501
|
+
- Syntax: \`# Title @id :type\` or \`# Title :type @id\` or \`# Title :type\` — both orderings work
|
|
502
|
+
- The colon requires whitespace before it: \`# Note: Important\` is NOT a type — the colon is part of the title
|
|
503
|
+
- Any string is valid. Well-known types: \`schema\`, \`example\`, \`requirement\`, \`specification\`, \`definition\`, \`note\`, \`warning\`, \`test\`, \`task\`, \`api\`, \`config\`, \`deprecated\`, \`comment\`
|
|
504
|
+
- Works with K&R style: \`# Title :warning {\`
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
The special type \`:comment\` makes a scope that is in the AST but not rendered — useful for editorial notes and AI agent instructions:
|
|
508
|
+
|
|
509
|
+
```
|
|
510
|
+
# TODO :comment
|
|
511
|
+
{
|
|
512
|
+
Rewrite this section after the API stabilises.
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
# Data Blocks @data-blocks
|
|
518
|
+
{
|
|
519
|
+
Add \`:data\` to a JSON code fence to have the parser parse the content into structured data on the AST node:
|
|
520
|
+
|
|
521
|
+
`````
|
|
522
|
+
```json :data
|
|
523
|
+
{
|
|
524
|
+
"name": "SDOC",
|
|
525
|
+
"version": "0.2",
|
|
526
|
+
"features": ["scopes", "lists", "tables"]
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
`````
|
|
530
|
+
|
|
531
|
+
{[.]
|
|
532
|
+
- Syntax: \` \`\`\`json :data \` — the \`:data\` flag follows the language tag
|
|
533
|
+
- Invalid JSON produces a parse error
|
|
534
|
+
- JSON-only in v0.2
|
|
535
|
+
- Without \`:data\`, JSON code blocks remain raw text (existing behaviour)
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
# Comments @comments
|
|
540
|
+
{
|
|
541
|
+
**Line comments** — \`//\` at the start of a line (after optional indentation) skips the line entirely. Not in the AST, not rendered:
|
|
542
|
+
|
|
543
|
+
```
|
|
544
|
+
# Config @config
|
|
545
|
+
{
|
|
546
|
+
// TODO: add validation
|
|
547
|
+
The config file uses JSON format.
|
|
548
|
+
|
|
549
|
+
// hidden from output
|
|
550
|
+
See @setup for details.
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
{[.]
|
|
555
|
+
- Mid-line \`//\` has no effect — URLs like \`https://example.com\` are safe
|
|
556
|
+
- Inside code blocks: \`//\` has no special meaning
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
**Comment scopes** — use the \`:comment\` scope type for structured non-rendered content (see @scope-types above).
|
|
560
|
+
}
|
|
457
561
|
}
|
|
458
562
|
|
|
459
563
|
# Common Mistakes @common-mistakes
|
|
@@ -567,5 +671,20 @@ Content of Section B.
|
|
|
567
671
|
}
|
|
568
672
|
```
|
|
569
673
|
}
|
|
674
|
+
|
|
675
|
+
# @References Inside Link Labels @refs-in-link-labels
|
|
676
|
+
{
|
|
677
|
+
Inline \`@references\` are parsed everywhere, including inside link labels. If you mention a scope ID in a link label, escape the \`@\` to prevent it being treated as a reference to the current document:
|
|
678
|
+
|
|
679
|
+
**Wrong:** \`[See domain-model.sdoc @my-section](./domain-model.sdoc#my-section)\`
|
|
680
|
+
|
|
681
|
+
The \`@my-section\` is parsed as a reference and flagged as broken (it does not exist in *this* file).
|
|
682
|
+
|
|
683
|
+
**Right:** \`[See domain-model.sdoc \\@my-section](./domain-model.sdoc#my-section)\`
|
|
684
|
+
|
|
685
|
+
Or simply omit the \`@\` from the label — the URL fragment already carries the target:
|
|
686
|
+
|
|
687
|
+
**Also right:** \`[See domain-model.sdoc § my-section](./domain-model.sdoc#my-section)\`
|
|
688
|
+
}
|
|
570
689
|
}
|
|
571
690
|
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
# SDOC Specification v0.
|
|
1
|
+
# SDOC Specification v0.2 @sdoc-spec
|
|
2
2
|
{
|
|
3
3
|
# Meta @meta
|
|
4
4
|
{
|
|
5
5
|
type: doc
|
|
6
6
|
|
|
7
|
-
sdoc-version: 0.
|
|
7
|
+
sdoc-version: 0.2
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
# About @about
|
|
11
11
|
{
|
|
12
|
-
The formal SDOC v0.
|
|
13
|
-
lists, tables, code blocks, inline formatting, references,
|
|
14
|
-
the meta scope
|
|
15
|
-
|
|
16
|
-
user-facing
|
|
12
|
+
The formal SDOC v0.2 specification. Defines syntax for scopes,
|
|
13
|
+
lists, tables, code blocks, inline formatting, references,
|
|
14
|
+
the meta scope, scope types, data blocks, and comments.
|
|
15
|
+
Includes the formal EBNF grammar. Read for edge cases and
|
|
16
|
+
parser behaviour questions. For a friendlier user-facing
|
|
17
|
+
reference, see \`docs/reference/syntax.sdoc\`.
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
# Overview @overview
|
|
@@ -51,12 +52,17 @@
|
|
|
51
52
|
{
|
|
52
53
|
```
|
|
53
54
|
# Title text @id
|
|
55
|
+
# Title text @id :type
|
|
56
|
+
# Title text :type @id
|
|
57
|
+
# Title text :type
|
|
54
58
|
```
|
|
55
59
|
|
|
56
60
|
{[.]
|
|
57
61
|
- The line must start with `#` (after optional indentation)
|
|
58
62
|
- Multiple `#` characters are allowed but do not affect depth. Depth comes only from scope nesting
|
|
59
|
-
- The optional `@id` must appear at the end of the line, separated by whitespace
|
|
63
|
+
- The optional `@id` must appear at the end of the line (or before `:type`), separated by whitespace
|
|
64
|
+
- The optional `:type` assigns a scope type (see @scope-types). Both `@id :type` and `:type @id` orderings are valid
|
|
65
|
+
- `:type` requires whitespace before the colon, so `# Note: Important` is NOT a scope type — the colon is part of the title
|
|
60
66
|
- If no `@id` is present, the scope has no ID
|
|
61
67
|
- If you need a literal `@` in the title, escape it (`\@`)
|
|
62
68
|
}
|
|
@@ -122,7 +128,7 @@ Content of Section B.
|
|
|
122
128
|
- The opener must be the last token on the line (trailing whitespace is allowed)
|
|
123
129
|
- Applies to `{`, `{[.]`, `{[#]`, `{[table]`, and `{[table <flags>]`
|
|
124
130
|
- Also works on list-item shorthand lines (e.g., `- Item {`)
|
|
125
|
-
- Table options (e.g., `{[table borderless]`) work in K&R style: `# Data {[table borderless]`
|
|
131
|
+
- Table options (e.g., `{[table borderless]`, `{[table 60% center]`) work in K&R style: `# Data {[table borderless]`
|
|
126
132
|
- Escaped braces (`\{`) are not treated as openers
|
|
127
133
|
- The closing `}` must still appear on its own line
|
|
128
134
|
- Inline blocks (`{ content }`) are not affected; a line ending with `}` is not treated as K&R
|
|
@@ -344,9 +350,33 @@ Content of Section B.
|
|
|
344
350
|
{[.]
|
|
345
351
|
- `borderless` removes all table borders and row striping
|
|
346
352
|
- `headerless` treats the first row as data (no header row)
|
|
347
|
-
-
|
|
353
|
+
- `auto` sets width to shrink-to-content
|
|
354
|
+
- A percentage value (e.g. `60%`, `33.3%`) sets an explicit percentage width
|
|
355
|
+
- A pixel value (e.g. `400px`) sets an explicit pixel width
|
|
356
|
+
- `center` centers the table (auto margins)
|
|
357
|
+
- `right` right-aligns the table (margin-left: auto)
|
|
358
|
+
- `left` left-aligns the table (default, no extra styles)
|
|
359
|
+
- Default width is 100%; default alignment is left
|
|
360
|
+
- All flags can be combined in any order
|
|
348
361
|
- Works with both Allman and K&R brace styles
|
|
349
362
|
}
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
{[table 60% center]
|
|
366
|
+
Endpoint | Status
|
|
367
|
+
/v2/weather | Active
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
{[table auto right borderless]
|
|
371
|
+
Key | Value
|
|
372
|
+
Version | 2.0
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
{[table 400px]
|
|
376
|
+
Name | Age
|
|
377
|
+
Alice | 30
|
|
378
|
+
}
|
|
379
|
+
```
|
|
350
380
|
}
|
|
351
381
|
}
|
|
352
382
|
|
|
@@ -365,16 +395,26 @@ Content of Section B.
|
|
|
365
395
|
- A reference is `@id` in text (unescaped)
|
|
366
396
|
- References link to the scope with that ID
|
|
367
397
|
- ID uniqueness is strongly recommended; tooling may warn on duplicates
|
|
398
|
+
- References are parsed inside link labels — use `\@` to include a literal `@` in a link label without triggering a reference
|
|
368
399
|
}
|
|
369
400
|
}
|
|
370
401
|
|
|
371
|
-
#
|
|
402
|
+
# Links @links
|
|
372
403
|
{
|
|
373
|
-
Markdown-style links:
|
|
404
|
+
Markdown-style links with absolute URLs or relative file paths:
|
|
374
405
|
|
|
375
406
|
```
|
|
376
407
|
[label](https://example.com)
|
|
408
|
+
[other doc](./other-file.sdoc)
|
|
409
|
+
[parent doc](../guide/intro.sdoc)
|
|
377
410
|
```
|
|
411
|
+
|
|
412
|
+
{[.]
|
|
413
|
+
- Absolute URLs (any scheme) open externally
|
|
414
|
+
- Relative paths are resolved from the document's directory
|
|
415
|
+
- Fragments (`./file.sdoc#section`) and query strings are stripped for file resolution
|
|
416
|
+
- Tooling may warn on broken relative links (target file does not exist)
|
|
417
|
+
}
|
|
378
418
|
}
|
|
379
419
|
|
|
380
420
|
# Autolinks @autolinks
|
|
@@ -387,6 +427,8 @@ Content of Section B.
|
|
|
387
427
|
```
|
|
388
428
|
|
|
389
429
|
Only `http`, `https`, and `mailto` schemes are recognised.
|
|
430
|
+
|
|
431
|
+
Bare URLs starting with `http://`, `https://`, or `mailto:` are also auto-linked without angle brackets.
|
|
390
432
|
}
|
|
391
433
|
|
|
392
434
|
# Images @images
|
|
@@ -466,7 +508,7 @@ Content of Section B.
|
|
|
466
508
|
|
|
467
509
|
# Escaping @escaping
|
|
468
510
|
{
|
|
469
|
-
In normal text (including headings and paragraphs), a backslash escapes: `\\` `\{` `\}` `\@` `\[` `\]` `\(` `\)` `\*` `\~` `\#` `\!` `\<` `\>` `\$` `\+` `\=` `\-` `\^` and `` \` ``.
|
|
511
|
+
In normal text (including headings and paragraphs), a backslash escapes: `\\` `\{` `\}` `\@` `\[` `\]` `\(` `\)` `\*` `\~` `\#` `\!` `\<` `\>` `\$` `\+` `\=` `\-` `\^` `\?` and `` \` ``.
|
|
470
512
|
|
|
471
513
|
Escapes are processed before reference detection.
|
|
472
514
|
|
|
@@ -516,6 +558,115 @@ Content of Section B.
|
|
|
516
558
|
}
|
|
517
559
|
}
|
|
518
560
|
}
|
|
561
|
+
|
|
562
|
+
# Scope Types @scope-types
|
|
563
|
+
{
|
|
564
|
+
A scope type annotation provides semantic meaning to a scope. The type is specified with `:typename` on the heading line, after optional `@id`:
|
|
565
|
+
|
|
566
|
+
```
|
|
567
|
+
# User Authentication @auth :requirement
|
|
568
|
+
{
|
|
569
|
+
The system shall authenticate users via OAuth 2.0.
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
# OAuth Flow :specification @oauth-flow
|
|
573
|
+
{
|
|
574
|
+
Implements @auth using the authorization code flow.
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
# Important :warning
|
|
578
|
+
{
|
|
579
|
+
This API is deprecated and will be removed in v3.0.
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
{[.]
|
|
584
|
+
- Syntax: `# Title @id :type` or `# Title :type @id` or `# Title :type` — both orderings of `@id` and `:type` are supported
|
|
585
|
+
- The colon in `:type` requires whitespace before it, so `# Note: Important` is NOT a scope type — the colon is part of the title text
|
|
586
|
+
- Any string is valid as a type name
|
|
587
|
+
- Well-known types: `schema`, `example`, `requirement`, `specification`, `definition`, `note`, `warning`, `test`, `task`, `api`, `config`, `deprecated`, `comment`
|
|
588
|
+
- The type is stored on the AST node and available to renderers and tooling
|
|
589
|
+
- Scope types enable AI agents to filter and navigate by semantic meaning (e.g., "show all requirements", "find tests for this spec")
|
|
590
|
+
- Works with K&R brace style: `# Title :warning {`
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
# Comment Scopes @comment-scopes
|
|
594
|
+
{
|
|
595
|
+
The `:comment` scope type creates a scope that is present in the AST but not rendered in the document output. This is useful for editorial annotations, AI agent instructions, and internal notes:
|
|
596
|
+
|
|
597
|
+
```
|
|
598
|
+
# TODO :comment
|
|
599
|
+
{
|
|
600
|
+
Rewrite this section after the API stabilises.
|
|
601
|
+
@alice please review the error handling.
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
# Agent Instructions :comment
|
|
605
|
+
{
|
|
606
|
+
When summarising this document, focus on the
|
|
607
|
+
requirements and skip the implementation details.
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
{[.]
|
|
612
|
+
- Comment scopes use the standard scope type system — no special syntax beyond `:comment`
|
|
613
|
+
- The scope is parsed into the AST (agents and tooling can extract it)
|
|
614
|
+
- The scope is not rendered in HTML, PDF, or other visual outputs
|
|
615
|
+
- Comment scopes can contain any valid SDOC content (paragraphs, lists, code blocks, nested scopes)
|
|
616
|
+
- Useful for structured annotations that are too complex for line comments
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
# Data Blocks @data-blocks
|
|
622
|
+
{
|
|
623
|
+
A code fence with the `:data` flag causes the parser to parse the content as structured data and attach it to the AST node:
|
|
624
|
+
|
|
625
|
+
`````
|
|
626
|
+
```json :data
|
|
627
|
+
{
|
|
628
|
+
"name": "SDOC",
|
|
629
|
+
"version": "0.2",
|
|
630
|
+
"features": ["scopes", "lists", "tables"]
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
`````
|
|
634
|
+
|
|
635
|
+
{[.]
|
|
636
|
+
- Syntax: ` ```json :data ` — the `:data` flag follows the language tag on the opening fence line
|
|
637
|
+
- The parser parses the JSON content and stores the result on the AST node
|
|
638
|
+
- Invalid JSON produces a parse error
|
|
639
|
+
- JSON is the only supported data format in v0.2
|
|
640
|
+
- Without the `:data` flag, JSON code blocks are treated as raw text (existing behaviour)
|
|
641
|
+
- Data blocks render visually the same as regular code blocks, but the parsed data is available to tooling and agents
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
# Line Comments @line-comments
|
|
646
|
+
{
|
|
647
|
+
A line starting with `//` (after optional indentation) is a line comment. The line is skipped entirely — it does not appear in the AST and is not rendered:
|
|
648
|
+
|
|
649
|
+
```
|
|
650
|
+
# Configuration @config
|
|
651
|
+
{
|
|
652
|
+
// TODO: add validation rules
|
|
653
|
+
The config file uses JSON format.
|
|
654
|
+
|
|
655
|
+
// This paragraph is hidden from output
|
|
656
|
+
// but visible in the source file.
|
|
657
|
+
|
|
658
|
+
See @setup for installation steps.
|
|
659
|
+
}
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
{[.]
|
|
663
|
+
- The `//` must be at the start of the line (after optional whitespace)
|
|
664
|
+
- Comment lines are discarded during parsing — they are not present in the AST
|
|
665
|
+
- Inside code blocks: `//` has no special meaning (code block content is raw)
|
|
666
|
+
- Mid-line `//` has no special meaning — URLs like `https://example.com` are safe
|
|
667
|
+
- Use line comments for quick annotations; use comment scopes (@comment-scopes) for structured non-rendered content
|
|
668
|
+
}
|
|
669
|
+
}
|
|
519
670
|
}
|
|
520
671
|
|
|
521
672
|
# Styles, Header, and Footer @styling
|
|
@@ -585,7 +736,7 @@ Content of Section B.
|
|
|
585
736
|
date: 2026-02-09
|
|
586
737
|
version: 1.0
|
|
587
738
|
status: Draft
|
|
588
|
-
sdoc-version: 0.
|
|
739
|
+
sdoc-version: 0.2
|
|
589
740
|
}
|
|
590
741
|
```
|
|
591
742
|
|
|
@@ -593,7 +744,7 @@ Content of Section B.
|
|
|
593
744
|
- Key matching is case-insensitive
|
|
594
745
|
- The pattern requires at least one space after the colon (`key: value`, not `key:value`)
|
|
595
746
|
- Well-known keys: `style`, `styleappend`/`style-append`, `header`, `footer`, `sdoc-version`
|
|
596
|
-
- `sdoc-version` identifies the SDOC format version the document targets (current: `0.
|
|
747
|
+
- `sdoc-version` identifies the SDOC format version the document targets (current: `0.2`). A parser warning is emitted when this key is missing.
|
|
597
748
|
- All other keys are stored as custom properties (e.g., `author`, `date`, `version`, `status`, `tags`)
|
|
598
749
|
- Sub-scope syntax takes precedence: if both `# Style { path }` and `style: path` exist, the sub-scope value wins
|
|
599
750
|
- Key:value and sub-scope syntax can be mixed freely in the same meta scope
|
|
@@ -713,6 +864,7 @@ Content of Section A.
|
|
|
713
864
|
- `>` blockquote line
|
|
714
865
|
- `---` / `***` / `___` horizontal rule
|
|
715
866
|
- `` ``` `` code fence
|
|
867
|
+
- `//` line comment (discarded, not in AST)
|
|
716
868
|
}
|
|
717
869
|
|
|
718
870
|
Blank lines are allowed anywhere and are ignored.
|
|
@@ -731,25 +883,34 @@ Content of Section A.
|
|
|
731
883
|
scope = heading ws? block
|
|
732
884
|
| heading ws? braceless_body
|
|
733
885
|
| heading_with_opener block_body "}" ;
|
|
734
|
-
heading = "#" { "#" } ws title
|
|
735
|
-
|
|
886
|
+
heading = "#" { "#" } ws title
|
|
887
|
+
((ws id)? (ws scope_type)? | (ws scope_type)? (ws id)?) ;
|
|
888
|
+
heading_with_opener = "#" { "#" } ws title
|
|
889
|
+
((ws id)? (ws scope_type)? | (ws scope_type)? (ws id)?) ws block_opener ;
|
|
736
890
|
id = "@" ident ;
|
|
891
|
+
scope_type = ":" ident ; (* id and scope_type may appear in either order *)
|
|
737
892
|
block_opener = "{" | "{[.]" | "{[#]" | table_open ;
|
|
738
893
|
block = "{" ws? block_body "}" ;
|
|
739
|
-
braceless_body = { paragraph | code_block |
|
|
740
|
-
|
|
|
741
|
-
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
894
|
+
braceless_body = { paragraph | code_block | data_block | blockquote
|
|
895
|
+
| implicit_list | horizontal_rule | headingless_scope
|
|
896
|
+
| list_scope | table_scope | bare_directive
|
|
897
|
+
| line_comment | blank } ;
|
|
898
|
+
|
|
899
|
+
bare_directive = "@" ("meta" | "about") (ws block | braceless_body) ;
|
|
900
|
+
block_body = { blank | line_comment | paragraph | scope
|
|
901
|
+
| headingless_scope | list_scope | table_scope
|
|
745
902
|
| implicit_list | blockquote | horizontal_rule
|
|
746
|
-
| code_block | comma_sep } ;
|
|
903
|
+
| code_block | data_block | bare_directive | comma_sep } ;
|
|
747
904
|
headingless_scope = "{" ws? block_body "}" ;
|
|
748
905
|
list_scope = list_open ws? list_body "}" ;
|
|
749
906
|
list_open = "{[.]" | "{[#]" ;
|
|
750
907
|
table_scope = table_open ws? table_body "}" ;
|
|
751
908
|
table_open = "{[table" { ws table_flag } "]" ;
|
|
752
|
-
table_flag = "borderless" | "headerless"
|
|
909
|
+
table_flag = "borderless" | "headerless"
|
|
910
|
+
| "auto" | percentage | pixels
|
|
911
|
+
| "left" | "center" | "right" ;
|
|
912
|
+
percentage = digit { digit } [ "." digit { digit } ] "%" ;
|
|
913
|
+
pixels = digit { digit } "px" ;
|
|
753
914
|
table_body = table_row { table_row } ;
|
|
754
915
|
table_row = cell { "|" cell } ;
|
|
755
916
|
list_body = { blank | comma_sep | scope | list_item_shorthand
|
|
@@ -767,15 +928,19 @@ Content of Section A.
|
|
|
767
928
|
paragraph = text_line { ws? text_line } ;
|
|
768
929
|
text_line = line_not_starting_with_command ;
|
|
769
930
|
|
|
770
|
-
blockquote = quote_line { quote_line
|
|
931
|
+
blockquote = quote_line { quote_line } ;
|
|
771
932
|
quote_line = ">" text_line ;
|
|
772
933
|
|
|
773
934
|
horizontal_rule = "---" | "***" | "___" ;
|
|
774
935
|
|
|
775
936
|
code_block = fence_open raw_text fence_close ;
|
|
937
|
+
data_block = data_fence_open raw_text fence_close ;
|
|
776
938
|
fence_open = "```" [lang] [ws "src:" path] [ws "lines:" range] newline ;
|
|
939
|
+
data_fence_open = "```" lang ws ":data" newline ;
|
|
777
940
|
fence_close = "```" newline ;
|
|
778
941
|
|
|
942
|
+
line_comment = "//" { any_char } newline ; (* discarded, not in AST *)
|
|
943
|
+
|
|
779
944
|
comma_sep = "," ;
|
|
780
945
|
blank = newline ;
|
|
781
946
|
|
|
@@ -783,7 +948,10 @@ Content of Section A.
|
|
|
783
948
|
```
|
|
784
949
|
|
|
785
950
|
{[.]
|
|
786
|
-
- `title` is the remainder of the heading line, excluding the optional trailing `@id`
|
|
951
|
+
- `title` is the remainder of the heading line, excluding the optional trailing `@id` and `:type`
|
|
952
|
+
- `scope_type` and `id` may appear in either order after the title
|
|
953
|
+
- `line_comment` lines are discarded during parsing and do not appear in the AST
|
|
954
|
+
- `data_block` content is parsed as JSON; invalid JSON produces a parse error
|
|
787
955
|
- If a line starts with a command token, it is not a paragraph line
|
|
788
956
|
- The grammar is line-oriented; practical parsers should operate on lines
|
|
789
957
|
}
|
|
@@ -792,10 +960,10 @@ Content of Section A.
|
|
|
792
960
|
# Open Questions @open-questions
|
|
793
961
|
{
|
|
794
962
|
{[.]
|
|
795
|
-
- Comment syntax (if any)
|
|
796
963
|
- Duplicate ID resolution (error vs warning vs nearest-scope)
|
|
797
|
-
- Additional list types (
|
|
964
|
+
- Additional list types (alpha, roman)
|
|
798
965
|
- Additional inline formatting (underline)
|
|
966
|
+
- Additional data block formats beyond JSON (YAML, TOML)
|
|
799
967
|
}
|
|
800
968
|
}
|
|
801
969
|
}
|
package/package.json
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"name": "@entropicwarrior/sdoc",
|
|
3
3
|
"displayName": "SDOC",
|
|
4
4
|
"description": "A plain-text documentation format with explicit brace scoping — deterministic parsing, AI-agent efficiency, and 10-50x token savings vs Markdown.",
|
|
5
|
-
"version": "0.1
|
|
5
|
+
"version": "0.2.1",
|
|
6
6
|
"publisher": "entropicwarrior",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/entropicwarrior/sdoc"
|
|
10
|
+
"url": "git+https://github.com/entropicwarrior/sdoc.git"
|
|
11
11
|
},
|
|
12
12
|
"homepage": "https://github.com/entropicwarrior/sdoc",
|
|
13
13
|
"bugs": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"ai-agent"
|
|
23
23
|
],
|
|
24
24
|
"bin": {
|
|
25
|
-
"sdoc-sync-notion": "
|
|
25
|
+
"sdoc-sync-notion": "tools/sync-notion.js"
|
|
26
26
|
},
|
|
27
27
|
"exports": {
|
|
28
28
|
".": "./index.js",
|
package/src/notion-renderer.js
CHANGED
|
@@ -246,6 +246,7 @@ function renderNotionBlocks(nodes) {
|
|
|
246
246
|
// Unwrap document scope wrapper (single root scope)
|
|
247
247
|
if (nodes.length === 1 && nodes[0].type === "scope" && nodes[0].children) {
|
|
248
248
|
const doc = nodes[0];
|
|
249
|
+
if (doc.scopeType === "comment") return [];
|
|
249
250
|
if (doc.hasHeading && doc.title) {
|
|
250
251
|
// Document title scope: render as top-level toggle heading
|
|
251
252
|
const childBlocks = renderChildren(doc.children, 2, 1);
|
|
@@ -290,6 +291,8 @@ function renderNode(node, depth, nestLevel) {
|
|
|
290
291
|
}
|
|
291
292
|
|
|
292
293
|
function renderScope(scope, depth, nestLevel) {
|
|
294
|
+
if (scope.scopeType === "comment") return [];
|
|
295
|
+
|
|
293
296
|
const level = Math.min(3, Math.max(1, depth));
|
|
294
297
|
|
|
295
298
|
if (scope.hasHeading === false) {
|