sdoc-editor-cli 0.10.2 → 0.12.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.
package/README.md CHANGED
@@ -143,7 +143,7 @@ sdoc capabilities --human
143
143
  },
144
144
  "commands": ["capabilities", "inspect", "validate", "apply", "rename-heading", "set-document-title", "create"],
145
145
  "projections": ["catalog", "target", "section", "document"],
146
- "catalogKinds": ["blocks", "outline", "references", "referenceables"],
146
+ "catalogKinds": ["blocks", "outline", "references", "referenceables", "endnotes"],
147
147
  "builtInTemplateIds": ["builtin:blank", "builtin:technical-report", "builtin:design-specification", "builtin:verification-report"]
148
148
  }
149
149
  ```
@@ -189,9 +189,18 @@ slash-delimited content path. For example, `/1/0` selects
189
189
 
190
190
  Copy `target.operationTarget` directly instead of assembling a snapshot
191
191
  target. Every `blocks[]` entry also includes its canonical `operationTarget`.
192
- Referenceable nodes receive an ID target; other blocks receive a snapshot
193
- target. A provisional ID is valid only for that inspected revision; applying
194
- it persists the ID.
192
+ A block with a persistent ID receives an ID target. A non-referenceable block
193
+ without one receives a snapshot target. Provisional IDs remain limited to
194
+ referenceable nodes, are valid only for the inspected revision, and persist
195
+ when applied. Inspection does not add IDs to the source document.
196
+
197
+ Optional stable IDs are omitted when absent; `attrs.id: null` is invalid for
198
+ these newly identity-bearing blocks. When present, newly authored stable IDs
199
+ are non-empty Unicode strings of at most 128 code points and cannot use the
200
+ reserved `provisional:` prefix. Korean and
201
+ numeric-leading heading slugs are valid. Existing `.sdoc` 1.0 referenceable
202
+ anchors remain loadable because that schema already allowed arbitrary strings;
203
+ they are not silently migrated on inspect or save.
195
204
 
196
205
  An explicit `--projection` selects the additive bounded `sdoc.read/1`
197
206
  contract. Projected JSON still uses `contract: "sdoc.cli.response/1"` as its
@@ -228,6 +237,10 @@ projection. Catalog, section, and document results can return
228
237
  `page.complete` is true. Cursors bind the exact source bytes and query scope.
229
238
  They are opaque integrity tokens, not authentication credentials.
230
239
 
240
+ The `endnotes` catalog reports canonical inline notes in body order as
241
+ `id`, `number`, `body`, and `path`. Endnotes are inline content rather than
242
+ operation block targets, and they do not enter the `referenceables` catalog.
243
+
231
244
  ### `validate`
232
245
 
233
246
  Checks the persisted document contract and semantic invariants without writing:
@@ -384,8 +397,15 @@ or revision-scoped provisional IDs:
384
397
  { "kind": "id", "id": "intro", "expectedType": "heading" }
385
398
  ```
386
399
 
387
- Other mutable blocks, including `paragraph`, `codeBlock`, and `diagram`, use a
388
- protected snapshot locator:
400
+ Identity-bearing and referenceable are not synonyms. `paragraph`, `bulletList`,
401
+ `orderedList`, `listItem`, `taskList`, `codeBlock`, `blockquote`, `callout`, and `diagram`
402
+ may carry an optional stable `attrs.id`. When present, inspection exposes the
403
+ persistent ID and returns an ID operation target, but the block does not enter
404
+ the `referenceables` catalog and cannot be an xref target. No IDs are inserted
405
+ automatically into existing documents.
406
+
407
+ A mutable non-referenceable block without a persistent ID uses a protected
408
+ snapshot locator:
389
409
 
390
410
  ```json
391
411
  {
@@ -397,8 +417,9 @@ protected snapshot locator:
397
417
  ```
398
418
 
399
419
  Prefer the ready-to-use `operationTarget` returned by `inspect` over copying
400
- these fields manually. A `diagram` is snapshot-targeted; it is not a
401
- persistent-ID node.
420
+ these fields manually. For example, a `diagram` with `attrs.id` is ID-targeted;
421
+ without one it is snapshot-targeted. In both cases it remains
422
+ non-referenceable.
402
423
 
403
424
  Snapshot targets and provisional IDs are revision-scoped. Re-inspect after any
404
425
  source-byte change. A batch resolves all targets before applying its first
@@ -500,21 +521,29 @@ the operation-relevant node types and required attributes:
500
521
  | Nodes | Required attributes | Target kind and notes |
501
522
  |---|---|---|
502
523
  | `heading` | `attrs.level` (1-6) | ID target; rename with `renameHeading`, and move/delete as a complete section |
503
- | `paragraph`, `blockquote`, `bulletList`, `orderedList`, `taskList`, `taskItem` | None | Snapshot target |
504
- | `codeBlock` | None (`attrs.language` optional) | Snapshot target |
524
+ | `paragraph`, `blockquote`, `bulletList`, `orderedList`, `listItem`, `taskList` | None (`attrs.id` optional) | ID target when `attrs.id` is present; otherwise snapshot target; not referenceable |
525
+ | `taskItem` | None | Snapshot target |
526
+ | `codeBlock` | None (`attrs.language` and `attrs.id` optional) | ID target when `attrs.id` is present; otherwise snapshot target; not referenceable |
505
527
  | `table` | None | ID target |
506
528
  | `tableCell`, `tableHeader` | None | Snapshot target; span, width, and alignment attrs are optional |
507
529
  | `image` | None | ID target; a new `src`, when present, must be portable |
508
530
  | `mathBlock` | `attrs.latex` | ID target |
509
- | `diagram` | `attrs.language`, `attrs.code` | Snapshot target; stores source such as Mermaid, PlantUML, or D2 |
510
- | `horizontalRule`, `hardBreak`, `callout` | None | Snapshot target; callout `variant` is optional |
511
- | `listItem`, `tableRow` | None | Structural container, not an operation block target |
531
+ | `diagram` | `attrs.language`, `attrs.code` (`attrs.id` optional) | ID target when `attrs.id` is present; otherwise snapshot target; not referenceable; stores source such as Mermaid, PlantUML, or D2 |
532
+ | `callout` | None (`attrs.variant` and `attrs.id` optional) | ID target when `attrs.id` is present; otherwise snapshot target; not referenceable |
533
+ | `horizontalRule` | None | Snapshot target; a historical ASCII-shaped `attrs.id` is preserved and collision-reserved, but is not exposed as identity or xref anchor |
534
+ | `hardBreak` | None | Snapshot target |
535
+ | `tableRow` | None | Structural container, not an operation block target |
512
536
  | `text`, `mathInline` | `mathInline.attrs.latex` only | Inline content, not an operation target |
513
537
 
514
538
  `updateBlockAttrs` accepts only the attrs defined for that node type.
515
539
  `replaceBlock` must preserve the node type, while headings require the
516
540
  heading/section operations.
517
541
 
542
+ Stable IDs may key provenance records in an external sidecar. Provenance is not
543
+ part of the core `.sdoc` schema. The engineering-canonical profile tracked in
544
+ #183 may build on this identity contract, but that profile is outside the CLI
545
+ contract documented here.
546
+
518
547
  Portable image assets use `./images/...`. Draw.io content is an `image` node
519
548
  whose `src` is under `./drawio/` and ends in `.drawio.svg`; it is not a
520
549
  `diagram` node. The CLI validates document structure and portable references
@@ -612,7 +641,7 @@ must branch on `diagnostics[].code` from explicit `--json` output.
612
641
  | `INVALID_HEADING_LEVEL`, `SECTION_LEVEL_OUT_OF_RANGE` | A requested heading level is outside 1-6, or shifting the section would push a descendant outside that range | Choose a valid target level that keeps every descendant heading within 1-6 |
613
642
  | `FORMATTED_HEADING` | Replacing a rich heading would discard marks or inline nodes | Preserve it, or explicitly use `discardFormatting` / `--discard-formatting` |
614
643
  | `ATTRIBUTE_NOT_ALLOWED`, `NODE_TYPE_CHANGE` | An attr is not allowed for the node, or replacement changes its type | Consult the node catalog/schema and keep replacements type-compatible |
615
- | `ID_RENAME_NOT_SUPPORTED`, `ID_RENAME_REQUIRES_EXISTING_ID`, `INVALID_NEW_ID` | An ID rename targeted an unsupported or provisional node, or supplied an invalid new ID | Target an inspected heading/table with an existing persistent ID and choose a unique non-reserved ID |
644
+ | `ID_RENAME_NOT_SUPPORTED`, `ID_RENAME_REQUIRES_EXISTING_ID`, `INVALID_NEW_ID` | An ID creation or rename targeted an unsupported/provisional node or supplied an invalid ID | For renames, target an inspected heading/table; choose a unique, bounded, non-reserved ID for renames and inserted sections |
616
645
  | `NEW_NONPORTABLE_ASSET`, `NEW_DANGLING_REFERENCE`, `NEW_UNSAFE_LINK` | The batch introduces an invalid asset path, missing internal target, or unsafe link | Use `./images/...` or `./drawio/*.drawio.svg`, create referenced IDs, and use a safe URL |
617
646
  | `DUPLICATE_ID` | The document contains conflicting persistent IDs | Assign unique IDs before retrying |
618
647
  | `CLI_TEMPLATE_INVALID` | An explicit template has malformed UTF-8, invalid JSON, or violates the template contract | Repair or re-encode the template as UTF-8; creation leaves the destination absent |
@@ -82,6 +82,7 @@
82
82
  "outline",
83
83
  "references",
84
84
  "referenceables",
85
+ "endnotes",
85
86
  "blocks",
86
87
  "blockCount",
87
88
  "blocksTruncated",
@@ -99,6 +100,7 @@
99
100
  "outline": { "type": "array" },
100
101
  "references": { "type": "array" },
101
102
  "referenceables": { "type": "array" },
103
+ "endnotes": { "type": "array" },
102
104
  "blocks": { "type": "array" },
103
105
  "blockCount": { "type": "integer", "minimum": 0 },
104
106
  "blocksTruncated": { "type": "boolean" },
@@ -139,7 +141,7 @@
139
141
  "type": "object",
140
142
  "required": ["kind", "items"],
141
143
  "properties": {
142
- "kind": { "enum": ["blocks", "outline", "references", "referenceables"] },
144
+ "kind": { "enum": ["blocks", "outline", "references", "referenceables", "endnotes"] },
143
145
  "items": { "type": "array", "items": { "type": "object" } }
144
146
  },
145
147
  "additionalProperties": true
@@ -421,7 +423,7 @@
421
423
  "type": "array",
422
424
  "minItems": 1,
423
425
  "uniqueItems": true,
424
- "items": { "enum": ["blocks", "outline", "references", "referenceables"] }
426
+ "items": { "enum": ["blocks", "outline", "references", "referenceables", "endnotes"] }
425
427
  },
426
428
  "builtInTemplateIds": {
427
429
  "type": "array",
@@ -16,6 +16,13 @@
16
16
  },
17
17
  "additionalProperties": false,
18
18
  "definitions": {
19
+ "authoredPersistentId": {
20
+ "type": "string",
21
+ "minLength": 1,
22
+ "maxLength": 128,
23
+ "not": { "pattern": "^provisional:" },
24
+ "description": "A non-empty Unicode ID of at most 128 code points outside the reserved provisional: namespace."
25
+ },
19
26
  "sha256": {
20
27
  "type": "string",
21
28
  "pattern": "^sha256:[0-9a-f]{64}$"
@@ -128,7 +135,7 @@
128
135
  "op": { "const": "insertSection" },
129
136
  "target": { "$ref": "#/definitions/target" },
130
137
  "title": { "type": "string" },
131
- "id": { "type": "string" },
138
+ "id": { "$ref": "#/definitions/authoredPersistentId" },
132
139
  "position": { "enum": ["child", "before", "after"] },
133
140
  "blocks": {
134
141
  "type": "array",
@@ -201,12 +208,7 @@
201
208
  "properties": {
202
209
  "op": { "const": "renameBlockId" },
203
210
  "target": { "$ref": "#/definitions/target" },
204
- "newId": {
205
- "type": "string",
206
- "minLength": 1,
207
- "maxLength": 128,
208
- "not": { "pattern": "^provisional:" }
209
- }
211
+ "newId": { "$ref": "#/definitions/authoredPersistentId" }
210
212
  },
211
213
  "additionalProperties": false
212
214
  },
@@ -55,7 +55,7 @@
55
55
  "properties": {
56
56
  "contract": { "const": "sdoc.read/1" },
57
57
  "projection": { "const": "catalog" },
58
- "kind": { "enum": ["blocks", "outline", "references", "referenceables"] },
58
+ "kind": { "enum": ["blocks", "outline", "references", "referenceables", "endnotes"] },
59
59
  "limit": { "type": "integer", "minimum": 1, "maximum": 10000 },
60
60
  "cursor": { "$ref": "#/definitions/cursor" },
61
61
  "maxBytes": { "type": "integer", "minimum": 1, "maximum": 33554432 },
@@ -78,6 +78,25 @@
78
78
  "additionalProperties": false
79
79
  },
80
80
 
81
+ "legacyReferenceId": {
82
+ "type": ["string", "null"],
83
+ "description": "Backward-compatible cross-reference anchor. Existing .sdoc 1.0 documents allowed any string; newly authored IDs should follow stableId."
84
+ },
85
+
86
+ "stableId": {
87
+ "type": "string",
88
+ "minLength": 1,
89
+ "maxLength": 128,
90
+ "not": { "pattern": "^provisional:" },
91
+ "description": "Optional stable block identity. Omit attrs.id when absent; when present, authored values are non-empty strings of at most 128 Unicode code points and cannot use the reserved provisional: namespace."
92
+ },
93
+
94
+ "historicalHorizontalRuleId": {
95
+ "type": ["string", "null"],
96
+ "pattern": "^[A-Za-z][A-Za-z0-9._:-]*$",
97
+ "description": "Historical horizontal-rule ID shape retained for .sdoc 1.0 compatibility; it is collision-reserved but is not an operation identity or cross-reference anchor."
98
+ },
99
+
81
100
  "docNode": {
82
101
  "type": "object",
83
102
  "required": ["type"],
@@ -114,6 +133,7 @@
114
133
  "oneOf": [
115
134
  { "$ref": "#/definitions/textNode" },
116
135
  { "$ref": "#/definitions/mathInlineNode" },
136
+ { "$ref": "#/definitions/endnoteNode" },
117
137
  { "$ref": "#/definitions/hardBreakNode" }
118
138
  ]
119
139
  },
@@ -128,7 +148,7 @@
128
148
  "required": ["level"],
129
149
  "properties": {
130
150
  "level": { "type": "integer", "minimum": 1, "maximum": 6 },
131
- "id": { "type": ["string", "null"], "description": "Anchor id for cross-references." },
151
+ "id": { "$ref": "#/definitions/legacyReferenceId" },
132
152
  "textAlign": { "type": ["string", "null"], "enum": ["left", "center", "right", "justify", null] },
133
153
  "numbered": { "type": ["boolean", "null"], "description": "false면 자동 번호 매김에서 제외 (예: Introduction, Glossary, References). 생략 또는 null이면 번호 매김 대상." }
134
154
  },
@@ -150,7 +170,8 @@
150
170
  "attrs": {
151
171
  "type": "object",
152
172
  "properties": {
153
- "textAlign": { "type": ["string", "null"], "enum": ["left", "center", "right", "justify", null] }
173
+ "textAlign": { "type": ["string", "null"], "enum": ["left", "center", "right", "justify", null] },
174
+ "id": { "$ref": "#/definitions/stableId" }
154
175
  },
155
176
  "additionalProperties": false
156
177
  },
@@ -167,6 +188,13 @@
167
188
  "required": ["type"],
168
189
  "properties": {
169
190
  "type": { "const": "bulletList" },
191
+ "attrs": {
192
+ "type": "object",
193
+ "properties": {
194
+ "id": { "$ref": "#/definitions/stableId" }
195
+ },
196
+ "additionalProperties": false
197
+ },
170
198
  "content": {
171
199
  "type": "array",
172
200
  "items": { "$ref": "#/definitions/listItemNode" }
@@ -184,7 +212,8 @@
184
212
  "type": "object",
185
213
  "properties": {
186
214
  "start": { "type": ["integer", "null"], "default": 1 },
187
- "type": { "type": ["string", "null"] }
215
+ "type": { "type": ["string", "null"] },
216
+ "id": { "$ref": "#/definitions/stableId" }
188
217
  },
189
218
  "additionalProperties": false
190
219
  },
@@ -201,6 +230,13 @@
201
230
  "required": ["type"],
202
231
  "properties": {
203
232
  "type": { "const": "listItem" },
233
+ "attrs": {
234
+ "type": "object",
235
+ "properties": {
236
+ "id": { "$ref": "#/definitions/stableId" }
237
+ },
238
+ "additionalProperties": false
239
+ },
204
240
  "content": {
205
241
  "type": "array",
206
242
  "items": { "$ref": "#/definitions/blockNode" }
@@ -214,6 +250,13 @@
214
250
  "required": ["type"],
215
251
  "properties": {
216
252
  "type": { "const": "taskList" },
253
+ "attrs": {
254
+ "type": "object",
255
+ "properties": {
256
+ "id": { "$ref": "#/definitions/stableId" }
257
+ },
258
+ "additionalProperties": false
259
+ },
217
260
  "content": {
218
261
  "type": "array",
219
262
  "items": { "$ref": "#/definitions/taskItemNode" }
@@ -249,7 +292,8 @@
249
292
  "attrs": {
250
293
  "type": "object",
251
294
  "properties": {
252
- "language": { "type": ["string", "null"] }
295
+ "language": { "type": ["string", "null"] },
296
+ "id": { "$ref": "#/definitions/stableId" }
253
297
  },
254
298
  "additionalProperties": false
255
299
  },
@@ -272,7 +316,7 @@
272
316
  "caption": { "type": ["string", "null"], "description": "Table caption text." },
273
317
  "align": { "type": "string", "enum": ["left", "center", "right"], "default": "left" },
274
318
  "width": { "type": "string", "description": "CSS width value (e.g., '100%', '50%', 'auto').", "default": "auto" },
275
- "id": { "type": ["string", "null"], "description": "Anchor id for cross-references." }
319
+ "id": { "$ref": "#/definitions/legacyReferenceId" }
276
320
  },
277
321
  "additionalProperties": false
278
322
  },
@@ -363,7 +407,7 @@
363
407
  "align": { "type": "string", "enum": ["left", "center", "right"], "default": "center" },
364
408
  "width": { "type": ["string", "number", "null"] },
365
409
  "height": { "type": ["string", "number", "null"] },
366
- "id": { "type": ["string", "null"], "description": "Anchor id for cross-references." }
410
+ "id": { "$ref": "#/definitions/legacyReferenceId" }
367
411
  },
368
412
  "additionalProperties": false
369
413
  }
@@ -381,7 +425,7 @@
381
425
  "required": ["latex"],
382
426
  "properties": {
383
427
  "latex": { "type": "string", "description": "LaTeX math expression." },
384
- "id": { "type": ["string", "null"], "description": "Anchor id for cross-references." }
428
+ "id": { "$ref": "#/definitions/legacyReferenceId" }
385
429
  },
386
430
  "additionalProperties": false
387
431
  }
@@ -406,6 +450,28 @@
406
450
  "additionalProperties": false
407
451
  },
408
452
 
453
+ "endnoteNode": {
454
+ "type": "object",
455
+ "required": ["type", "attrs"],
456
+ "properties": {
457
+ "type": { "const": "endnote" },
458
+ "attrs": {
459
+ "type": "object",
460
+ "required": ["id", "body"],
461
+ "properties": {
462
+ "id": { "$ref": "#/definitions/stableId" },
463
+ "body": {
464
+ "type": "string",
465
+ "pattern": "^[^\\r\\n]*$",
466
+ "description": "Single-line plain-text endnote body. The document-end list is derived from this canonical value."
467
+ }
468
+ },
469
+ "additionalProperties": false
470
+ }
471
+ },
472
+ "additionalProperties": false
473
+ },
474
+
409
475
  "diagramNode": {
410
476
  "type": "object",
411
477
  "required": ["type"],
@@ -416,7 +482,8 @@
416
482
  "required": ["language", "code"],
417
483
  "properties": {
418
484
  "language": { "type": "string", "description": "Diagram language, e.g. mermaid, plantuml, d2." },
419
- "code": { "type": "string", "description": "Diagram source code." }
485
+ "code": { "type": "string", "description": "Diagram source code." },
486
+ "id": { "$ref": "#/definitions/stableId" }
420
487
  },
421
488
  "additionalProperties": false
422
489
  }
@@ -441,10 +508,7 @@
441
508
  "attrs": {
442
509
  "type": "object",
443
510
  "properties": {
444
- "id": {
445
- "type": ["string", "null"],
446
- "pattern": "^[A-Za-z][A-Za-z0-9._:-]*$"
447
- }
511
+ "id": { "$ref": "#/definitions/historicalHorizontalRuleId" }
448
512
  },
449
513
  "additionalProperties": false
450
514
  }
@@ -457,6 +521,13 @@
457
521
  "required": ["type"],
458
522
  "properties": {
459
523
  "type": { "const": "blockquote" },
524
+ "attrs": {
525
+ "type": "object",
526
+ "properties": {
527
+ "id": { "$ref": "#/definitions/stableId" }
528
+ },
529
+ "additionalProperties": false
530
+ },
460
531
  "content": {
461
532
  "type": "array",
462
533
  "items": { "$ref": "#/definitions/blockNode" }
@@ -477,7 +548,8 @@
477
548
  "type": "string",
478
549
  "enum": ["note", "info", "tip", "warning", "danger"],
479
550
  "description": "Visual style of the callout block."
480
- }
551
+ },
552
+ "id": { "$ref": "#/definitions/stableId" }
481
553
  },
482
554
  "additionalProperties": false
483
555
  },