@entropicwarrior/sdoc 0.1.17 → 0.2.0

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.
@@ -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
@@ -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
@@ -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
 
@@ -387,6 +417,8 @@ Content of Section B.
387
417
  ```
388
418
 
389
419
  Only `http`, `https`, and `mailto` schemes are recognised.
420
+
421
+ Bare URLs starting with `http://`, `https://`, or `mailto:` are also auto-linked without angle brackets.
390
422
  }
391
423
 
392
424
  # Images @images
@@ -466,7 +498,7 @@ Content of Section B.
466
498
 
467
499
  # Escaping @escaping
468
500
  {
469
- In normal text (including headings and paragraphs), a backslash escapes: `\\` `\{` `\}` `\@` `\[` `\]` `\(` `\)` `\*` `\~` `\#` `\!` `\<` `\>` `\$` `\+` `\=` `\-` `\^` and `` \` ``.
501
+ In normal text (including headings and paragraphs), a backslash escapes: `\\` `\{` `\}` `\@` `\[` `\]` `\(` `\)` `\*` `\~` `\#` `\!` `\<` `\>` `\$` `\+` `\=` `\-` `\^` `\?` and `` \` ``.
470
502
 
471
503
  Escapes are processed before reference detection.
472
504
 
@@ -516,6 +548,115 @@ Content of Section B.
516
548
  }
517
549
  }
518
550
  }
551
+
552
+ # Scope Types @scope-types
553
+ {
554
+ A scope type annotation provides semantic meaning to a scope. The type is specified with `:typename` on the heading line, after optional `@id`:
555
+
556
+ ```
557
+ # User Authentication @auth :requirement
558
+ {
559
+ The system shall authenticate users via OAuth 2.0.
560
+ }
561
+
562
+ # OAuth Flow :specification @oauth-flow
563
+ {
564
+ Implements @auth using the authorization code flow.
565
+ }
566
+
567
+ # Important :warning
568
+ {
569
+ This API is deprecated and will be removed in v3.0.
570
+ }
571
+ ```
572
+
573
+ {[.]
574
+ - Syntax: `# Title @id :type` or `# Title :type @id` or `# Title :type` — both orderings of `@id` and `:type` are supported
575
+ - The colon in `:type` requires whitespace before it, so `# Note: Important` is NOT a scope type — the colon is part of the title text
576
+ - Any string is valid as a type name
577
+ - Well-known types: `schema`, `example`, `requirement`, `specification`, `definition`, `note`, `warning`, `test`, `task`, `api`, `config`, `deprecated`, `comment`
578
+ - The type is stored on the AST node and available to renderers and tooling
579
+ - Scope types enable AI agents to filter and navigate by semantic meaning (e.g., "show all requirements", "find tests for this spec")
580
+ - Works with K&R brace style: `# Title :warning {`
581
+ }
582
+
583
+ # Comment Scopes @comment-scopes
584
+ {
585
+ 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:
586
+
587
+ ```
588
+ # TODO :comment
589
+ {
590
+ Rewrite this section after the API stabilises.
591
+ @alice please review the error handling.
592
+ }
593
+
594
+ # Agent Instructions :comment
595
+ {
596
+ When summarising this document, focus on the
597
+ requirements and skip the implementation details.
598
+ }
599
+ ```
600
+
601
+ {[.]
602
+ - Comment scopes use the standard scope type system — no special syntax beyond `:comment`
603
+ - The scope is parsed into the AST (agents and tooling can extract it)
604
+ - The scope is not rendered in HTML, PDF, or other visual outputs
605
+ - Comment scopes can contain any valid SDOC content (paragraphs, lists, code blocks, nested scopes)
606
+ - Useful for structured annotations that are too complex for line comments
607
+ }
608
+ }
609
+ }
610
+
611
+ # Data Blocks @data-blocks
612
+ {
613
+ A code fence with the `:data` flag causes the parser to parse the content as structured data and attach it to the AST node:
614
+
615
+ `````
616
+ ```json :data
617
+ {
618
+ "name": "SDOC",
619
+ "version": "0.2",
620
+ "features": ["scopes", "lists", "tables"]
621
+ }
622
+ ```
623
+ `````
624
+
625
+ {[.]
626
+ - Syntax: ` ```json :data ` — the `:data` flag follows the language tag on the opening fence line
627
+ - The parser parses the JSON content and stores the result on the AST node
628
+ - Invalid JSON produces a parse error
629
+ - JSON is the only supported data format in v0.2
630
+ - Without the `:data` flag, JSON code blocks are treated as raw text (existing behaviour)
631
+ - Data blocks render visually the same as regular code blocks, but the parsed data is available to tooling and agents
632
+ }
633
+ }
634
+
635
+ # Line Comments @line-comments
636
+ {
637
+ 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:
638
+
639
+ ```
640
+ # Configuration @config
641
+ {
642
+ // TODO: add validation rules
643
+ The config file uses JSON format.
644
+
645
+ // This paragraph is hidden from output
646
+ // but visible in the source file.
647
+
648
+ See @setup for installation steps.
649
+ }
650
+ ```
651
+
652
+ {[.]
653
+ - The `//` must be at the start of the line (after optional whitespace)
654
+ - Comment lines are discarded during parsing — they are not present in the AST
655
+ - Inside code blocks: `//` has no special meaning (code block content is raw)
656
+ - Mid-line `//` has no special meaning — URLs like `https://example.com` are safe
657
+ - Use line comments for quick annotations; use comment scopes (@comment-scopes) for structured non-rendered content
658
+ }
659
+ }
519
660
  }
520
661
 
521
662
  # Styles, Header, and Footer @styling
@@ -585,7 +726,7 @@ Content of Section B.
585
726
  date: 2026-02-09
586
727
  version: 1.0
587
728
  status: Draft
588
- sdoc-version: 0.1
729
+ sdoc-version: 0.2
589
730
  }
590
731
  ```
591
732
 
@@ -593,7 +734,7 @@ Content of Section B.
593
734
  - Key matching is case-insensitive
594
735
  - The pattern requires at least one space after the colon (`key: value`, not `key:value`)
595
736
  - 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.
737
+ - `sdoc-version` identifies the SDOC format version the document targets (current: `0.2`). A parser warning is emitted when this key is missing.
597
738
  - All other keys are stored as custom properties (e.g., `author`, `date`, `version`, `status`, `tags`)
598
739
  - Sub-scope syntax takes precedence: if both `# Style { path }` and `style: path` exist, the sub-scope value wins
599
740
  - Key:value and sub-scope syntax can be mixed freely in the same meta scope
@@ -713,6 +854,7 @@ Content of Section A.
713
854
  - `>` blockquote line
714
855
  - `---` / `***` / `___` horizontal rule
715
856
  - `` ``` `` code fence
857
+ - `//` line comment (discarded, not in AST)
716
858
  }
717
859
 
718
860
  Blank lines are allowed anywhere and are ignored.
@@ -731,25 +873,34 @@ Content of Section A.
731
873
  scope = heading ws? block
732
874
  | heading ws? braceless_body
733
875
  | heading_with_opener block_body "}" ;
734
- heading = "#" { "#" } ws title (ws id)? ;
735
- heading_with_opener = "#" { "#" } ws title (ws id)? ws block_opener ;
876
+ heading = "#" { "#" } ws title
877
+ ((ws id)? (ws scope_type)? | (ws scope_type)? (ws id)?) ;
878
+ heading_with_opener = "#" { "#" } ws title
879
+ ((ws id)? (ws scope_type)? | (ws scope_type)? (ws id)?) ws block_opener ;
736
880
  id = "@" ident ;
881
+ scope_type = ":" ident ; (* id and scope_type may appear in either order *)
737
882
  block_opener = "{" | "{[.]" | "{[#]" | table_open ;
738
883
  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
884
+ braceless_body = { paragraph | code_block | data_block | blockquote
885
+ | implicit_list | horizontal_rule | headingless_scope
886
+ | list_scope | table_scope | bare_directive
887
+ | line_comment | blank } ;
888
+
889
+ bare_directive = "@" ("meta" | "about") (ws block | braceless_body) ;
890
+ block_body = { blank | line_comment | paragraph | scope
891
+ | headingless_scope | list_scope | table_scope
745
892
  | implicit_list | blockquote | horizontal_rule
746
- | code_block | comma_sep } ;
893
+ | code_block | data_block | bare_directive | comma_sep } ;
747
894
  headingless_scope = "{" ws? block_body "}" ;
748
895
  list_scope = list_open ws? list_body "}" ;
749
896
  list_open = "{[.]" | "{[#]" ;
750
897
  table_scope = table_open ws? table_body "}" ;
751
898
  table_open = "{[table" { ws table_flag } "]" ;
752
- table_flag = "borderless" | "headerless" ;
899
+ table_flag = "borderless" | "headerless"
900
+ | "auto" | percentage | pixels
901
+ | "left" | "center" | "right" ;
902
+ percentage = digit { digit } [ "." digit { digit } ] "%" ;
903
+ pixels = digit { digit } "px" ;
753
904
  table_body = table_row { table_row } ;
754
905
  table_row = cell { "|" cell } ;
755
906
  list_body = { blank | comma_sep | scope | list_item_shorthand
@@ -767,15 +918,19 @@ Content of Section A.
767
918
  paragraph = text_line { ws? text_line } ;
768
919
  text_line = line_not_starting_with_command ;
769
920
 
770
- blockquote = quote_line { quote_line | blank } ;
921
+ blockquote = quote_line { quote_line } ;
771
922
  quote_line = ">" text_line ;
772
923
 
773
924
  horizontal_rule = "---" | "***" | "___" ;
774
925
 
775
926
  code_block = fence_open raw_text fence_close ;
927
+ data_block = data_fence_open raw_text fence_close ;
776
928
  fence_open = "```" [lang] [ws "src:" path] [ws "lines:" range] newline ;
929
+ data_fence_open = "```" lang ws ":data" newline ;
777
930
  fence_close = "```" newline ;
778
931
 
932
+ line_comment = "//" { any_char } newline ; (* discarded, not in AST *)
933
+
779
934
  comma_sep = "," ;
780
935
  blank = newline ;
781
936
 
@@ -783,7 +938,10 @@ Content of Section A.
783
938
  ```
784
939
 
785
940
  {[.]
786
- - `title` is the remainder of the heading line, excluding the optional trailing `@id`
941
+ - `title` is the remainder of the heading line, excluding the optional trailing `@id` and `:type`
942
+ - `scope_type` and `id` may appear in either order after the title
943
+ - `line_comment` lines are discarded during parsing and do not appear in the AST
944
+ - `data_block` content is parsed as JSON; invalid JSON produces a parse error
787
945
  - If a line starts with a command token, it is not a paragraph line
788
946
  - The grammar is line-oriented; practical parsers should operate on lines
789
947
  }
@@ -792,10 +950,10 @@ Content of Section A.
792
950
  # Open Questions @open-questions
793
951
  {
794
952
  {[.]
795
- - Comment syntax (if any)
796
953
  - Duplicate ID resolution (error vs warning vs nearest-scope)
797
- - Additional list types (checkboxes, alpha, roman)
954
+ - Additional list types (alpha, roman)
798
955
  - Additional inline formatting (underline)
956
+ - Additional data block formats beyond JSON (YAML, TOML)
799
957
  }
800
958
  }
801
959
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
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.17",
5
+ "version": "0.2.0",
6
6
  "publisher": "entropicwarrior",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -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) {