@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 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
- {[code lang=python]
91
- def hello():
92
- print("Hello from SDOC")
93
- }
90
+ ```python
91
+ def hello():
92
+ print("Hello from SDOC")
93
+ ```
94
94
  }
95
95
 
96
- # A List
96
+ # Status :example
97
97
  {
98
- {[.]
99
- - First item
100
- - Second item with **bold**
101
- - Third item
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 `headerless` flags.
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.1
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 only work for bullet lists (\`-\`) where every item is a single line. For numbered lists, always use the explicit \`{[#]\` block form.
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: \`![Alt text](path/to/image.png)\`
271
292
 
@@ -279,7 +300,7 @@ Content of Section B.
279
300
  ![A](a.png =48%) ![B](b.png =48%)
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.1
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.1\` — SDOC format version. A parser warning is
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-spec
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.1
7
+ sdoc-version: 0.2
8
8
  }
9
9
 
10
10
  # About @about
11
11
  {
12
- The formal SDOC v0.1 specification. Defines syntax for scopes,
13
- lists, tables, code blocks, inline formatting, references, and
14
- the meta scope. Includes the formal EBNF grammar. Read for
15
- edge cases and parser behaviour questions. For a friendlier
16
- user-facing reference, see \`docs/reference/syntax.sdoc\`.
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
- - Flags can be combined in any order
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
- # External Links @external-links
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.1
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.1`). A parser warning is emitted when this key is missing.
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 (ws id)? ;
735
- heading_with_opener = "#" { "#" } ws title (ws id)? ws block_opener ;
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 | blockquote | implicit_list
740
- | horizontal_rule | headingless_scope | table_scope
741
- | blank } ;
742
-
743
- block_body = { blank | paragraph | scope | headingless_scope
744
- | list_scope | table_scope
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 | blank } ;
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 (checkboxes, alpha, roman)
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.18",
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": "./tools/sync-notion.js"
25
+ "sdoc-sync-notion": "tools/sync-notion.js"
26
26
  },
27
27
  "exports": {
28
28
  ".": "./index.js",
@@ -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) {