@heroiclands/package-build 20.3.1 → 20.5.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.
Files changed (195) hide show
  1. package/CHANGELOG.md +351 -0
  2. package/CONTENT.md +322 -110
  3. package/README.md +21 -10
  4. package/bin/content-build.mjs +204 -92
  5. package/bin/package-build.mjs +48 -15
  6. package/config.mjs +6 -8
  7. package/content-config.mjs +518 -151
  8. package/docs/api.md +1352 -0
  9. package/docs/commands.md +1609 -0
  10. package/docs/configuration.md +1432 -0
  11. package/docs/content-format.md +73 -74
  12. package/docs/diagnostics.md +356 -0
  13. package/docs/getting-started.md +813 -0
  14. package/docs/project-setup.md +469 -0
  15. package/e2e.mjs +4 -4
  16. package/engine/actor-compiler.mjs +64 -61
  17. package/engine/address-charset.mjs +6 -6
  18. package/engine/address-diff.mjs +57 -52
  19. package/engine/anchored-sections.mjs +1 -1
  20. package/engine/anchors.mjs +1 -1
  21. package/engine/base-compiler.mjs +42 -38
  22. package/engine/bundle-notes.mjs +4 -4
  23. package/engine/bundles.mjs +17 -8
  24. package/engine/code-fences.mjs +3 -3
  25. package/engine/compendiums.mjs +8 -8
  26. package/engine/compile-corpus.mjs +4 -5
  27. package/engine/content-address.mjs +24 -24
  28. package/engine/content-charset.mjs +1 -1
  29. package/engine/content-format-check.mjs +24 -25
  30. package/engine/content-format.mjs +8 -8
  31. package/engine/content-html.mjs +202 -0
  32. package/engine/content-icons.mjs +196 -165
  33. package/engine/content-index.mjs +61 -40
  34. package/engine/content-links.mjs +74 -41
  35. package/engine/content-lint.mjs +64 -34
  36. package/engine/content-package.mjs +7 -9
  37. package/engine/content-slug.mjs +2 -2
  38. package/engine/content-tables.mjs +38 -34
  39. package/engine/content-tree.mjs +1 -1
  40. package/engine/derived-fields.mjs +174 -0
  41. package/engine/diagnostics.mjs +3 -4
  42. package/engine/document-subtypes.mjs +6 -7
  43. package/engine/field-reference.mjs +8 -8
  44. package/engine/field-spec.mjs +29 -30
  45. package/engine/folder-notes.mjs +18 -16
  46. package/engine/foreign-catalog.mjs +8 -9
  47. package/engine/foundry-entries.mjs +19 -20
  48. package/engine/frontmatter-lint.mjs +95 -103
  49. package/engine/frontmatter.mjs +11 -11
  50. package/engine/generate.mjs +44 -39
  51. package/engine/helpers.mjs +147 -84
  52. package/engine/homepage.mjs +20 -22
  53. package/engine/ids.mjs +15 -15
  54. package/engine/index-records.mjs +4 -4
  55. package/engine/index.mjs +26 -10
  56. package/engine/item-compiler.mjs +54 -29
  57. package/engine/item-docs.mjs +11 -13
  58. package/engine/item-registry.mjs +13 -13
  59. package/engine/journals.mjs +24 -15
  60. package/engine/macros.mjs +3 -3
  61. package/engine/map-notes.mjs +6 -6
  62. package/engine/metadata-index.mjs +5 -5
  63. package/engine/note-claims.mjs +51 -41
  64. package/engine/note-ids.mjs +5 -5
  65. package/engine/note-package.mjs +4 -4
  66. package/engine/note-renames.mjs +3 -3
  67. package/engine/note-schemas.mjs +3 -8
  68. package/engine/note-vocabulary.mjs +56 -54
  69. package/engine/pack-config.mjs +39 -25
  70. package/engine/pack-router.mjs +0 -0
  71. package/engine/pdf-build.mjs +464 -0
  72. package/engine/pdf-fonts.mjs +420 -0
  73. package/engine/pdf-render.mjs +798 -0
  74. package/engine/pdf-toc.mjs +525 -0
  75. package/engine/prose-config.mjs +9 -9
  76. package/engine/prose-lint.mjs +12 -13
  77. package/engine/region-events.mjs +2 -2
  78. package/engine/retired-fields.mjs +24 -22
  79. package/engine/runtime-only-fields.mjs +1 -1
  80. package/engine/scene-levels.mjs +1 -1
  81. package/engine/scenes.mjs +33 -25
  82. package/engine/schema-check.mjs +21 -21
  83. package/engine/schema-extract.mjs +1 -1
  84. package/engine/site-build.mjs +66 -50
  85. package/engine/site-index.mjs +17 -18
  86. package/engine/sql-tables.mjs +21 -13
  87. package/engine/subtype-registry.mjs +5 -4
  88. package/engine/system-block.mjs +18 -19
  89. package/engine/systems.mjs +2 -2
  90. package/engine/web-wikilinks.mjs +41 -38
  91. package/engine/wikilink-syntax.mjs +16 -16
  92. package/engine/wikilinks.mjs +40 -42
  93. package/hm3/actors.mjs +30 -22
  94. package/hm3/document-subtypes.mjs +5 -5
  95. package/hm3/index.mjs +2 -2
  96. package/hm3/item-builders.mjs +1 -1
  97. package/hm3/item-fields.mjs +3 -3
  98. package/hm3/items.mjs +28 -8
  99. package/hm3/template-priority.mjs +2 -2
  100. package/lang.mjs +3 -3
  101. package/manifest.mjs +17 -20
  102. package/package.json +1 -2
  103. package/release.mjs +66 -6
  104. package/sohl/actors.mjs +36 -28
  105. package/sohl/affiliation-standings.mjs +2 -2
  106. package/sohl/being-info.mjs +5 -5
  107. package/sohl/default-item-art.mjs +5 -5
  108. package/sohl/document-subtypes.mjs +5 -5
  109. package/sohl/index.mjs +3 -3
  110. package/sohl/item-builders.mjs +7 -7
  111. package/sohl/item-fields.mjs +8 -9
  112. package/sohl/items.mjs +28 -6
  113. package/sohl/kb-passes.mjs +5 -5
  114. package/sohl/note-schemas.mjs +12 -12
  115. package/sohl/skill-base.mjs +1 -1
  116. package/types/content-config.d.mts +91 -39
  117. package/types/e2e.d.mts +3 -3
  118. package/types/engine/actor-compiler.d.mts +45 -40
  119. package/types/engine/address-charset.d.mts +6 -6
  120. package/types/engine/address-diff.d.mts +60 -6
  121. package/types/engine/base-compiler.d.mts +27 -21
  122. package/types/engine/bundle-notes.d.mts +3 -3
  123. package/types/engine/bundles.d.mts +10 -1
  124. package/types/engine/code-fences.d.mts +3 -3
  125. package/types/engine/compendiums.d.mts +3 -3
  126. package/types/engine/compile-corpus.d.mts +1 -1
  127. package/types/engine/content-address.d.mts +20 -20
  128. package/types/engine/content-format-check.d.mts +6 -6
  129. package/types/engine/content-format.d.mts +2 -2
  130. package/types/engine/content-html.d.mts +78 -0
  131. package/types/engine/content-icons.d.mts +139 -92
  132. package/types/engine/content-index.d.mts +59 -15
  133. package/types/engine/content-links.d.mts +8 -8
  134. package/types/engine/content-lint.d.mts +8 -6
  135. package/types/engine/content-package.d.mts +6 -8
  136. package/types/engine/content-tables.d.mts +49 -18
  137. package/types/engine/derived-fields.d.mts +101 -0
  138. package/types/engine/diagnostics.d.mts +2 -2
  139. package/types/engine/document-subtypes.d.mts +3 -3
  140. package/types/engine/field-spec.d.mts +37 -39
  141. package/types/engine/folder-notes.d.mts +4 -5
  142. package/types/engine/foreign-catalog.d.mts +3 -3
  143. package/types/engine/foundry-entries.d.mts +6 -7
  144. package/types/engine/frontmatter-lint.d.mts +10 -9
  145. package/types/engine/frontmatter.d.mts +7 -7
  146. package/types/engine/generate.d.mts +7 -7
  147. package/types/engine/helpers.d.mts +128 -56
  148. package/types/engine/homepage.d.mts +16 -18
  149. package/types/engine/ids.d.mts +13 -13
  150. package/types/engine/index-records.d.mts +3 -3
  151. package/types/engine/index.d.mts +6 -0
  152. package/types/engine/item-compiler.d.mts +21 -5
  153. package/types/engine/item-docs.d.mts +2 -2
  154. package/types/engine/item-registry.d.mts +6 -6
  155. package/types/engine/journals.d.mts +12 -3
  156. package/types/engine/map-notes.d.mts +2 -2
  157. package/types/engine/metadata-index.d.mts +4 -4
  158. package/types/engine/note-claims.d.mts +30 -22
  159. package/types/engine/note-ids.d.mts +4 -4
  160. package/types/engine/note-package.d.mts +1 -1
  161. package/types/engine/note-renames.d.mts +3 -3
  162. package/types/engine/note-vocabulary.d.mts +31 -203
  163. package/types/engine/pack-config.d.mts +7 -7
  164. package/types/engine/pack-router.d.mts +1 -1
  165. package/types/engine/pdf-build.d.mts +42 -0
  166. package/types/engine/pdf-fonts.d.mts +30 -0
  167. package/types/engine/pdf-render.d.mts +144 -0
  168. package/types/engine/pdf-toc.d.mts +114 -0
  169. package/types/engine/prose-config.d.mts +9 -9
  170. package/types/engine/prose-lint.d.mts +3 -4
  171. package/types/engine/region-events.d.mts +2 -2
  172. package/types/engine/retired-fields.d.mts +10 -9
  173. package/types/engine/scenes.d.mts +10 -1
  174. package/types/engine/schema-check.d.mts +13 -13
  175. package/types/engine/site-build.d.mts +52 -25
  176. package/types/engine/site-index.d.mts +3 -4
  177. package/types/engine/sql-tables.d.mts +11 -5
  178. package/types/engine/subtype-registry.d.mts +3 -3
  179. package/types/engine/system-block.d.mts +3 -3
  180. package/types/engine/web-wikilinks.d.mts +7 -7
  181. package/types/engine/wikilink-syntax.d.mts +17 -17
  182. package/types/engine/wikilinks.d.mts +14 -14
  183. package/types/hm3/actors.d.mts +9 -1
  184. package/types/hm3/document-subtypes.d.mts +1 -1
  185. package/types/hm3/items.d.mts +9 -2
  186. package/types/hm3/template-priority.d.mts +1 -1
  187. package/types/manifest.d.mts +8 -8
  188. package/types/release.d.mts +15 -4
  189. package/types/sohl/actors.d.mts +12 -4
  190. package/types/sohl/affiliation-standings.d.mts +2 -2
  191. package/types/sohl/being-info.d.mts +2 -2
  192. package/types/sohl/document-subtypes.d.mts +1 -1
  193. package/types/sohl/items.d.mts +9 -2
  194. package/types/sohl/note-schemas.d.mts +1 -1
  195. package/MIGRATING.md +0 -608
@@ -13,14 +13,13 @@
13
13
 
14
14
  /**
15
15
  * **The Actor pass, for any system** — the parts of compiling a note into a
16
- * Foundry Actor that belong to the note format rather than to a game system
17
- * (#139).
16
+ * Foundry Actor that belong to the note format rather than to a game system.
18
17
  *
19
18
  * The whole of it lived in `sohl/actors.mjs`, where the system-specific facts
20
- * were already funnelled through one map (`static documentSubtypes`, added by
21
- * #79) and one block constant. A second system needs everything except the
22
- * shape of the `system` block itself, so that everything moved here and each
23
- * half declares what differs:
19
+ * were already funnelled through one map (`static documentSubtypes`) and one
20
+ * block constant. A second system needs everything except the shape of the
21
+ * `system` block itself, so all of it lives here and each half declares what
22
+ * differs:
24
23
  *
25
24
  * | stated by the subclass | what it decides |
26
25
  * | --- | --- |
@@ -35,7 +34,7 @@
35
34
  * - **Reference translation.** A being addresses its embedded items in the
36
35
  * *note* vocabulary and the catalogue is keyed in the *document's*, so
37
36
  * {@link SystemActorCompiler#embeddedSubtype} translates each reference
38
- * forward through this system's map before the lookup (#140).
37
+ * forward through this system's map before the lookup.
39
38
  * - **Embedding.** Merging a note's overlay onto a catalogue entry, deriving a
40
39
  * stable embedded id from the owning actor and the address, and re-keying the
41
40
  * embedded document and its effects for the LevelDB flattening.
@@ -60,15 +59,15 @@ import { BasePackCompiler } from "./base-compiler.mjs";
60
59
  import { contentPackage } from "./content-package.mjs";
61
60
  // Which Foundry Actor subtype a note's `type` compiles into, and which note
62
61
  // types are actors at all. Looked up in the system's declared map, never
63
- // inferred from the type itself (#79).
62
+ // inferred from the type itself.
64
63
  import { mapsNoteType, noteTypesFor, referencedSubtype } from "./document-subtypes.mjs";
65
64
  import { locateFrontmatterKey } from "./retired-fields.mjs";
66
65
  // An `items:` entry's `system:` overlay is merged verbatim, so it reaches the
67
- // document by a path no field declaration sits on — including, until #330, the
66
+ // document by a path no field declaration sits on — including the
68
67
  // fields the document is supposed to write for itself in play.
69
68
  import { itemFields } from "./item-registry.mjs";
70
69
  import { runtimeOnlyIn, runtimeOnlyMessage } from "./runtime-only-fields.mjs";
71
- // A `model:` is an address, read by the same grammar every wikilink is (#336),
70
+ // A `model:` is an address, read by the same grammar every wikilink is,
72
71
  // so an author writes one form and meets one set of messages.
73
72
  import { readQualifier } from "./wikilinks.mjs";
74
73
 
@@ -132,7 +131,7 @@ export function deepMerge(base, overlay) {
132
131
  * vocabulary, so a reference is translated forward through the system's map
133
132
  * before it reaches this function; see
134
133
  * {@link SystemActorCompiler#embeddedSubtype} for why the translation goes that
135
- * way and not the other (#140).
134
+ * way and not the other.
136
135
  *
137
136
  * @param {string} subType - The Foundry Item subtype.
138
137
  * @param {string} shortcode - The item's `system.shortcode`.
@@ -144,7 +143,7 @@ export function itemAddress(subType, shortcode) {
144
143
 
145
144
  /**
146
145
  * The key one predefined item is held under **for the package that publishes
147
- * it** — the address a `model:` naming that package resolves through (#334).
146
+ * it** — the address a `model:` naming that package resolves through.
148
147
  *
149
148
  * The unqualified {@link itemAddress} stays beside it, and the two answer
150
149
  * different questions. A `model` that names no package means *this* one and
@@ -174,11 +173,12 @@ export function packagedItemAddress(pkg, subType, shortcode) {
174
173
  * A shortcode is case-sensitive and routinely mixed — `Clb`, `LtShoe`,
175
174
  * `HsTunic` — while an **address** is not: `readQualifier` normalises what it
176
175
  * reads, and every canonical address is lowercase. So the moment a `model:` is
177
- * read as an address (#334), `weapongear-clb` has to find the document whose
178
- * `system.shortcode` is `Clb`, and an exact match cannot (#346).
176
+ * read as an address, `weapongear-clb` has to find the document whose
177
+ * `system.shortcode` is `Clb`, and an exact match cannot.
179
178
  *
180
179
  * Folding is safe because the fold is already the address: no two items in any
181
- * published tree differ only by the case of their shortcode, and #340 will make
180
+ * published tree differ only by the case of their shortcode, and the rule
181
+ * makes
182
182
  * that impossible rather than merely true.
183
183
  *
184
184
  * **This is not {@link itemAddress}, and must not become it.** That one seeds
@@ -229,13 +229,13 @@ export function embeddedIdentity(item) {
229
229
  * four corpora holds 180 items, at which 64 bits collide with probability
230
230
  * around 10⁻¹⁵.
231
231
  *
232
- * **It takes no index** (#268). Keying on a position meant reordering a being's
232
+ * **It takes no index**. Keying on a position meant reordering a being's
233
233
  * item list renumbered every id after the change, so a re-import created new
234
234
  * documents beside the old ones — while nothing about those documents had
235
235
  * changed, only their neighbours. The identity always exists or must be stated;
236
236
  * see {@link embeddedIdentity}.
237
237
  *
238
- * Keyed by the **document subtype**, so renaming a note type (#78) leaves every
238
+ * Keyed by the **document subtype**, so renaming a note type leaves every
239
239
  * embedded id where it was.
240
240
  *
241
241
  * @param {string} actorId - The owning actor's id.
@@ -277,7 +277,7 @@ export function loadItemsMap(itemsSourceDirs, foreignSourceDirs = []) {
277
277
  const shadowed = [];
278
278
  for (const itemsSourceDir of itemsSourceDirs) {
279
279
  if (!fs.existsSync(itemsSourceDir)) {
280
- // The generator orders the actors pass after every Item pass (#73),
280
+ // The generator orders the actors pass after every Item pass,
281
281
  // so a whole-package build cannot reach this. What can is a run
282
282
  // restricted to this one pack, or a caller constructing the
283
283
  // compiler itself — neither of which reordering a pack list fixes,
@@ -321,13 +321,13 @@ export function loadItemsMap(itemsSourceDirs, foreignSourceDirs = []) {
321
321
  const { _key, ...rest } = doc;
322
322
  map.set(address, rest);
323
323
  // And under this package's own name, so a `model:` that names this
324
- // package explicitly resolves to the same item (#334).
324
+ // package explicitly resolves to the same item.
325
325
  map.set(catalogueKey(doc.type, shortcode, contentPackage()), rest);
326
326
  }
327
327
  }
328
328
  for (const foreignEntry of foreignSourceDirs) {
329
329
  // Each dependency's directory arrives with the package that published
330
- // it (#334), so a foreign template gets its own canonical address
330
+ // it, so a foreign template gets its own canonical address
331
331
  // rather than sharing the local address space.
332
332
  const foreignDir = typeof foreignEntry === "string" ? foreignEntry : foreignEntry.dir;
333
333
  const foreignPackage = typeof foreignEntry === "string" ? null : foreignEntry.package;
@@ -352,7 +352,7 @@ export function loadItemsMap(itemsSourceDirs, foreignSourceDirs = []) {
352
352
  // eslint-disable-next-line no-unused-vars
353
353
  const { _key, ...rest } = doc;
354
354
  // Its own package-qualified address, which a `model:` naming that
355
- // package resolves through and nothing local can shadow (#334).
355
+ // package resolves through and nothing local can shadow.
356
356
  if (foreignPackage) {
357
357
  map.set(catalogueKey(doc.type, shortcode, foreignPackage), rest);
358
358
  }
@@ -411,8 +411,8 @@ export class SystemActorCompiler extends BasePackCompiler {
411
411
  * because {@link SystemActorCompiler#resolveEmbedded} is called per entry
412
412
  * and has no note lifecycle of its own.
413
413
  *
414
- * `frontmatter-lint.mjs` makes the same finding from frontmatter alone
415
- * (#228), and this does not replace it — the lint is a separate command, so
414
+ * `frontmatter-lint.mjs` makes the same finding from frontmatter alone,
415
+ * and this does not replace it — the lint is a separate command, so
416
416
  * without a check here a colliding pair would compile to two documents with
417
417
  * one `_id` and reach the LevelDB packer as an opaque duplicate key.
418
418
  *
@@ -422,12 +422,12 @@ export class SystemActorCompiler extends BasePackCompiler {
422
422
 
423
423
  // An actor's embedded items are resolved against the *output* of the item
424
424
  // passes, so every Item pack compiles before this one. Declared rather than
425
- // left to the order `packs:` happens to list (#73).
425
+ // left to the order `packs:` happens to list.
426
426
  static readsPackOutputOf = Object.freeze(["Item"]);
427
427
 
428
428
  /**
429
429
  * An Actor **is** a system's data, so this pack takes only notes carrying
430
- * this system's block (#58).
430
+ * this system's block.
431
431
  */
432
432
  static requiresSystemBlock = true;
433
433
 
@@ -449,7 +449,7 @@ export class SystemActorCompiler extends BasePackCompiler {
449
449
 
450
450
  /**
451
451
  * Every package a `model:` may name besides this one — the dependencies
452
- * whose item catalogues were supplied (#334).
452
+ * whose item catalogues were supplied.
453
453
  *
454
454
  * @returns {Set<string>} The dependency package ids.
455
455
  */
@@ -465,10 +465,10 @@ export class SystemActorCompiler extends BasePackCompiler {
465
465
  super(options);
466
466
  // Where the items passes wrote their JSON. Stated by the caller rather
467
467
  // than assumed to be this pack's sibling: the packs' locations are
468
- // configuration, and a consumer may put them anywhere (#1508). Every
469
- // Item pack, because a repository may ship more than one (#1566).
468
+ // configuration, and a consumer may put them anywhere. Every
469
+ // Item pack, because a repository may ship more than one.
470
470
  //
471
- // **Optional, and empty is a legitimate package (#49).** This used to
471
+ // **Optional, and empty is a legitimate package.** This used to
472
472
  // throw unless at least one Item pack was declared, which asked a
473
473
  // package to declare the very thing it may exist not to have. An Item
474
474
  // pack is system-bound by construction — Foundry requires `system` on
@@ -560,7 +560,7 @@ export class SystemActorCompiler extends BasePackCompiler {
560
560
  * The Foundry Item subtype an embedded reference's `type` addresses.
561
561
  *
562
562
  * **The reference is in the note vocabulary; the address is in the
563
- * document's** (#140). An actor writes `(type, shortcode)` with the type an
563
+ * document's**. An actor writes `(type, shortcode)` with the type an
564
564
  * author authors, while {@link itemAddress} keys the predefined items by
565
565
  * the subtype each compiled document carries — so exactly one of the two
566
566
  * sides has to translate, and it is this one. The system's map is a
@@ -576,37 +576,10 @@ export class SystemActorCompiler extends BasePackCompiler {
576
576
  return referencedSubtype(this.documentSubtypes, type, "Item");
577
577
  }
578
578
 
579
- /**
580
- * Resolve one embedded item from a `(type, shortcode?, overlay)`
581
- * descriptor. If `shortcode` is given, the predefined item is fetched
582
- * from `itemsMap` and the overlay deep-merged on top. If absent, the
583
- * descriptor must carry enough fields to stand alone. The embedded
584
- * item's `_id` is regenerated deterministically from
585
- * `(actorId, subType, shortcode, indexKey)` so re-exports are stable —
586
- * from the **document subtype**, so that renaming a note type (#78) leaves
587
- * every embedded id exactly where it was.
588
- * Returns null if the descriptor cannot be resolved.
589
- *
590
- * @param {Map<string, object>} itemsMap - The predefined items, by address.
591
- * @param {string} actorId - The owning actor's id, seeding embedded ids.
592
- * @param {string} type - The **note** type the reference names.
593
- * @param {string|null} shortcode - The referenced item's shortcode, or
594
- * `null` for a stand-alone entry.
595
- * @param {object} [overlay] - The entry's remaining properties.
596
- * @param {string} indexKey - Where the reference sits, for a diagnostic.
597
- * It no longer reaches the id (#268) — it names the entry in a message.
598
- * @param {string} ctx - Diagnostic context (the actor's label).
599
- * @param {object} [at] - Where to locate a finding.
600
- * @param {string} [at.fmKey] - The frontmatter key the reference sits
601
- * under, so an unresolved one is reported at the reference rather than
602
- * at the note.
603
- * @returns {object|null} The embedded item, or null when it resolved to
604
- * nothing — always with a finding emitted.
605
- */
606
579
  /**
607
580
  * Read an entry's `model:` — the address of the item it is a copy of.
608
581
  *
609
- * The address grammar is the wikilink one (#336), so a `model` is written at
582
+ * The address grammar is the wikilink one, so a `model` is written at
610
583
  * whatever length says what it means: `skill-wpnc` within this package,
611
584
  * `sohl-sohl-skill-wpnc` to reach another. The system segment defaults from
612
585
  * the block the entry sits in — `<system>.items` — which is what makes the
@@ -615,7 +588,7 @@ export class SystemActorCompiler extends BasePackCompiler {
615
588
  *
616
589
  * It replaced a top-level `shortcode:` that meant something different from
617
590
  * the `system.shortcode` beside it and could not say which package a
618
- * template came from (#334).
591
+ * template came from.
619
592
  *
620
593
  * @param {unknown} model - The authored value.
621
594
  * @param {number} index - The entry's position, for the message.
@@ -653,6 +626,36 @@ export class SystemActorCompiler extends BasePackCompiler {
653
626
  return { type: read.type, shortcode: read.shortcode, package: read.package ?? null };
654
627
  }
655
628
 
629
+ /**
630
+ * Resolve one embedded item from a `(type, shortcode?, overlay)`
631
+ * descriptor. If `shortcode` is given, the predefined item is fetched
632
+ * from `itemsMap` and the overlay deep-merged on top. If absent, the
633
+ * descriptor must carry enough fields to stand alone. The embedded
634
+ * item's `_id` is regenerated deterministically from
635
+ * `(actorId, subType, shortcode, indexKey)` so re-exports are stable —
636
+ * from the **document subtype**, so that renaming a note type leaves
637
+ * every embedded id exactly where it was.
638
+ * Returns null if the descriptor cannot be resolved.
639
+ *
640
+ * @param {Map<string, object>} itemsMap - The predefined items, by address.
641
+ * @param {string} actorId - The owning actor's id, seeding embedded ids.
642
+ * @param {string} type - The **note** type the reference names.
643
+ * @param {string|null} shortcode - The referenced item's shortcode, or
644
+ * `null` for a stand-alone entry.
645
+ * @param {object} overlay - The entry's remaining properties.
646
+ * @param {string} indexKey - Where the reference sits, for a diagnostic.
647
+ * It names the entry in a message and does not reach the id.
648
+ * @param {string} ctx - Diagnostic context (the actor's label).
649
+ * @param {object} [at] - Where to locate a finding.
650
+ * @param {string} [at.fmKey] - The frontmatter key the reference sits
651
+ * under, so an unresolved one is reported at the reference rather than
652
+ * at the note.
653
+ * @param {string|null} [at.modelPackage] - The package a `model:` names,
654
+ * where the template comes from another package's catalogue. Given one,
655
+ * the packaged address is what resolves and nothing local shadows it.
656
+ * @returns {object|null} The embedded item, or null when it resolved to
657
+ * nothing — always with a finding emitted.
658
+ */
656
659
  resolveEmbedded(
657
660
  itemsMap,
658
661
  actorId,
@@ -675,7 +678,7 @@ export class SystemActorCompiler extends BasePackCompiler {
675
678
  this.errorCount++;
676
679
  return null;
677
680
  }
678
- // A `model:` may name the package its template comes from (#334). Where
681
+ // A `model:` may name the package its template comes from. Where
679
682
  // it does, the packaged address is used and nothing local can shadow
680
683
  // it; where it does not, the unqualified one is, and a local definition
681
684
  // still wins over a dependency's as it always has.
@@ -687,7 +690,7 @@ export class SystemActorCompiler extends BasePackCompiler {
687
690
 
688
691
  // The entry's `system:` overlay is merged verbatim, so it reaches the
689
692
  // document without passing a single field declaration — which left it
690
- // the one position a runtime-only field stayed authorable at once #330
693
+ // the one position a runtime-only field would stay authorable at once
691
694
  // closed the item note's own. Asked of the **overlay** rather than of
692
695
  // the merged result: the template it merges onto is a compiled
693
696
  // document, which by then carries none, and a finding has to name what
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  /**
15
- * The one charset every segment of a canonical address is held to (#59).
15
+ * The one charset every segment of a canonical address is held to.
16
16
  *
17
17
  * An address is a hyphen-joined tuple — `package-system-type-shortcode`, so
18
18
  * `sohl-none-doc-gear` — and it is read back by **counting segments**, with a
@@ -43,7 +43,7 @@
43
43
  * digits only.
44
44
  *
45
45
  * Case *was* deliberately unconstrained, on the reasoning that case has no
46
- * bearing on the separator — which is true, and beside the point (#340).
46
+ * bearing on the separator — which is true, and beside the point.
47
47
  *
48
48
  * **Two names that differ only in case are two names nobody can tell apart.** A
49
49
  * shortcode is how a person names a thing when writing a reference —
@@ -55,10 +55,10 @@
55
55
  * `sohl-sohl-weapongear-clb` and its `_id` derived from that. The authored name
56
56
  * and its address disagreed, and everything downstream keys on the address —
57
57
  * which left two notes differing only in case sharing one address, one `_id` and
58
- * one URL, with nothing to report it. It also forced two exceptions elsewhere:
59
- * #336 had to exempt the shortcode from the lowercase rule it pinned on every
60
- * other segment, and #346 had to fold the shortcode's case in the item catalogue
61
- * because an address is lowercased when read.
58
+ * one URL, with nothing to report it. It also forces two exceptions elsewhere:
59
+ * the shortcode has to be exempted from the lowercase rule pinned on every
60
+ * other segment, and its case has to be folded in the item catalogue because
61
+ * an address is lowercased when read.
62
62
  *
63
63
  * One case, one spelling, no exceptions. Every tree already complies but two,
64
64
  * and nothing in any of them collides when folded.
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  /**
15
- * Diffing a package's published item addresses against a released one (#66).
15
+ * Diffing a package's published item addresses against a released one.
16
16
  *
17
17
  * A package's `(type, shortcode)` addresses are a **published interface**.
18
18
  * Every satellite that declares `itemCatalog: true` assembles its beings out of
@@ -48,10 +48,10 @@
48
48
  * mean" from string similarity would be worse than saying nothing, because a
49
49
  * wrong one sends the reader to the wrong fix.
50
50
  *
51
- * **#270 narrowed that match, and a declaration is what makes up the
51
+ * **That match is narrow, and a declaration is what makes up the
52
52
  * difference.** The join rested on the id being independent of the shortcode: a
53
53
  * note authored its `_id`, so the `Tabri` → `Taburi` commit changed the
54
- * shortcode alone and left the id to join the two sides. Since #270 an id is
54
+ * shortcode alone and left the id to join the two sides. An id is
55
55
  * *derived from the canonical address*, which carries the shortcode — so
56
56
  * renaming a shortcode moves the id too, both sides of the join move together,
57
57
  * and the match finds nothing. It stays exact for a note that **pins** an `id`,
@@ -61,7 +61,7 @@
61
61
  * one.
62
62
  *
63
63
  * So an author who has just renamed a shortcode **says so**, with
64
- * `renamedFrom:` on the note that made the change (#278, and see
64
+ * `renamedFrom:` on the note that made the change (and see
65
65
  * `engine/note-renames.mjs`). That is neither a guess nor an identity match but
66
66
  * testimony from the only party that knows, and the diagnostic reports which of
67
67
  * the two it had rather than blending them — a reader can verify a matched id
@@ -74,7 +74,7 @@
74
74
  *
75
75
  * **Severity is decided per case.** A withdrawal is legitimate — content is
76
76
  * allowed to be retired — so it is reported and does not fail a build. A rename
77
- * is equally legitimate as a decision (#1397's charset rule forces some), which
77
+ * is equally legitimate as a decision (the charset rule forces some), which
78
78
  * is why it does not fail one either; what it must not do is happen in silence.
79
79
  * A caller that wants a gate passes `error` and treats any finding as one.
80
80
  *
@@ -91,7 +91,7 @@ import path from "node:path";
91
91
  import { formatDiagnostic, positionInFrontmatter } from "./diagnostics.mjs";
92
92
  import { positionOfLiteral } from "./diagnostics.mjs";
93
93
  import { assertStatedScope } from "./helpers.mjs";
94
- // The corpus, read from the one pass that derives it (#243). Nothing in the
94
+ // The corpus, read from the one pass that derives it. Nothing in the
95
95
  // index's import graph reaches this module — only `bin/` imports it — so this
96
96
  // is a plain static import, as in the link checker.
97
97
  import { indexRecordsFor, isNoteRecord, noteFile } from "./content-index.mjs";
@@ -173,59 +173,19 @@ export function readItemAddresses(dirs) {
173
173
  return space;
174
174
  }
175
175
 
176
- /**
177
- * Every rename the tree's notes **declare**, as old address → where it went.
178
- *
179
- * Read from the content tree rather than from compiled output, because a
180
- * declaration is authored and the compiled document does not carry it: nothing
181
- * downstream consumes `renamedFrom:`, so emitting it into every pack to let one
182
- * diagnostic read it back would put a build-time note in shipped data forever.
183
- * The tree is already read by this module for the same reason
184
- * ({@link noteFilesById}) — to place a finding where its author can fix it.
185
- *
186
- * **A declaration is keyed by document subtype, not by note type.** The address
187
- * space is the one consumers resolve against, and it is spelled in compiled
188
- * documents: `hm3` compiles a `projectile` note into a `missilegear` item,
189
- * so that is the address a rename of it moves. {@link referencedSubtype} is the
190
- * function that already answers this for a being's embedded `(type, shortcode)`
191
- * references, so both sides read the same rule rather than a second copy of it.
192
- *
193
- * **An entry is emitted for every system that maps the type**, whether or not
194
- * the note declares that system's block. Over-emitting is inert — the diff uses
195
- * an entry only when the baseline published the old address *and* this build
196
- * publishes the new one, and a system the note does not compile for satisfies
197
- * neither — while asking which blocks a note declares would put a second,
198
- * subtly different answer to that question in a third place.
199
- *
200
- * **First claim wins on a collision.** Two notes naming one predecessor is a
201
- * contradiction — an address has one successor — and it is reported as an error
202
- * by `engine/content-lint.mjs`, where both notes are in hand and can both be
203
- * named. Picking one here keeps this a map; it is not a resolution, and nothing
204
- * rests on which one it picked.
205
- *
206
- * @param {string} contentBase - Root of the content tree.
207
- * @param {object} opts
208
- * @param {readonly string[]} [opts.skipDirectories] - The corpus scope. Stated
209
- * by the caller, never defaulted — see {@link addressCorpus}.
210
- * @param {readonly object[]} [opts.maps] - The document-subtype maps.
211
- * @param {object} [opts.config] - The resolved build configuration.
212
- * @param {readonly object[]} [opts.records] - Index records the caller already
213
- * derived, shared with {@link noteFilesById} so one command reads one corpus.
214
- * @returns {Map<string, {to: string, file: string, shortcode: string}>} Old
215
- * address → the address the declaring note publishes at now, and that note.
216
- */
217
176
  /**
218
177
  * The corpus both reads below share, as content-index records.
219
178
  *
220
179
  * **One walk, not two.** `addresses diff` reads the tree twice — once for the
221
- * declarations and once to place its findings — and until #243 those were two
180
+ * declarations and once to place its findings — and those would otherwise be
181
+ * two
222
182
  * independent walks that each parsed every note. They are now one derivation,
223
183
  * shared: the caller derives the records and hands them to both, so the two
224
184
  * halves of a single command cannot disagree about which files the corpus is,
225
185
  * or about the ids in it.
226
186
  *
227
187
  * **The id is why it matters, and not only tidiness.** `noteFilesById` joins
228
- * tree-side ids against ids read out of the *compiled packs*. Since #270 an id
188
+ * tree-side ids against ids read out of the *compiled packs*. An id
229
189
  * is derived from the canonical address, whose first segment is the content
230
190
  * package — and the tree side used to derive it through `resolveNoteId(fm)`
231
191
  * with no package, which falls back to `contentPackage()` and so to whichever
@@ -257,6 +217,49 @@ function addressCorpus(contentBase, { skipDirectories, config, records, problems
257
217
  return indexRecordsFor({ contentBase, config, skipDirectories, problems });
258
218
  }
259
219
 
220
+ /**
221
+ * Every rename the tree's notes **declare**, as old address → where it went.
222
+ *
223
+ * Read from the content tree rather than from compiled output, because a
224
+ * declaration is authored and the compiled document does not carry it: nothing
225
+ * downstream consumes `renamedFrom:`, so emitting it into every pack to let one
226
+ * diagnostic read it back would put a build-time note in shipped data forever.
227
+ * The tree is already read by this module for the same reason
228
+ * ({@link noteFilesById}) — to place a finding where its author can fix it.
229
+ *
230
+ * **A declaration is keyed by document subtype, not by note type.** The address
231
+ * space is the one consumers resolve against, and it is spelled in compiled
232
+ * documents: `hm3` compiles a `projectile` note into a `missilegear` item,
233
+ * so that is the address a rename of it moves. {@link referencedSubtype} is the
234
+ * function that already answers this for a being's embedded `(type, shortcode)`
235
+ * references, so both sides read the same rule rather than a second copy of it.
236
+ *
237
+ * **An entry is emitted for every system that maps the type**, whether or not
238
+ * the note declares that system's block. Over-emitting is inert — the diff uses
239
+ * an entry only when the baseline published the old address *and* this build
240
+ * publishes the new one, and a system the note does not compile for satisfies
241
+ * neither — while asking which blocks a note declares would put a second,
242
+ * subtly different answer to that question in a third place.
243
+ *
244
+ * **First claim wins on a collision.** Two notes naming one predecessor is a
245
+ * contradiction — an address has one successor — and it is reported as an error
246
+ * by `engine/content-lint.mjs`, where both notes are in hand and can both be
247
+ * named. Picking one here keeps this a map; it is not a resolution, and nothing
248
+ * rests on which one it picked.
249
+ *
250
+ * @param {string} contentBase - Root of the content tree.
251
+ * @param {object} opts
252
+ * @param {readonly string[]} [opts.skipDirectories] - The corpus scope. Stated
253
+ * by the caller, never defaulted — see {@link addressCorpus}.
254
+ * @param {readonly object[]} [opts.maps] - The document-subtype maps.
255
+ * @param {object} [opts.config] - The resolved build configuration.
256
+ * @param {readonly object[]} [opts.records] - Index records the caller already
257
+ * derived, shared with {@link noteFilesById} so one command reads one corpus.
258
+ * @param {object[]} [opts.problems] - Collects the notes the index cannot
259
+ * record, so one of them does not abort the diff before it reports.
260
+ * @returns {Map<string, {to: string, file: string, shortcode: string}>} Old
261
+ * address → the address the declaring note publishes at now, and that note.
262
+ */
260
263
  export function declaredPredecessors(
261
264
  contentBase,
262
265
  { skipDirectories, maps = KNOWN_DOCUMENT_SUBTYPE_MAPS, config, records, problems } = {},
@@ -315,7 +318,7 @@ export function declaredPredecessors(
315
318
  * @param {Map<string, {to: string, file: string}>} [opts.predecessors] - The
316
319
  * declared renames, from {@link declaredPredecessors}. Omitted, the diff
317
320
  * falls back to the id join alone and reports an unpinned rename as a
318
- * withdrawal, which is what it did before #278.
321
+ * withdrawal, which is what it did before.
319
322
  * @returns {Array<object>} One finding per departed address, in address order
320
323
  * so two runs read the same. `kind` is `"renamed"` (with `to`, and `declared`
321
324
  * when it was the note's word rather than an id match) or `"withdrawn"`.
@@ -393,6 +396,8 @@ export function diffItemAddresses(baseline, current, { baseline: label, predeces
393
396
  * id is derived against. See {@link addressCorpus} for why that matters.
394
397
  * @param {readonly object[]} [opts.records] - Index records the caller already
395
398
  * derived, shared with {@link declaredPredecessors}.
399
+ * @param {object[]} [opts.problems] - Collects the notes the index cannot
400
+ * record, so one of them does not abort the diff before it reports.
396
401
  * @returns {Map<string, string>} Document id → the note's absolute path.
397
402
  */
398
403
  export function noteFilesById(contentBase, { skipDirectories, config, records, problems } = {}) {
@@ -431,7 +436,7 @@ export function noteFilesById(contentBase, { skipDirectories, config, records, p
431
436
  * A **declared** rename knows its note without any lookup — the declaration is
432
437
  * how it was found — and is reported at the `renamedFrom:` line rather than the
433
438
  * `shortcode:` line, because that is the line the finding is about and the one
434
- * the author deletes once the declaration has done its work (#278).
439
+ * the author deletes once the declaration has done its work.
435
440
  *
436
441
  * @param {object} finding - One finding from {@link diffItemAddresses}.
437
442
  * @param {Map<string, string>} noteFiles - From {@link noteFilesById}.
@@ -480,7 +485,7 @@ export function locateAddressFinding(finding, noteFiles) {
480
485
  * known — and says so, rather than leaving the reader to wonder whether one was
481
486
  * looked for.
482
487
  *
483
- * **A declared rename says it is declared** (#278). The two claims are not
488
+ * **A declared rename says it is declared**. The two claims are not
484
489
  * equally checkable: an id match is a fact in the artefacts, while a
485
490
  * declaration is an author's word, and a reader deciding whether to trust the
486
491
  * successor needs to know which one they have. Saying "the same document" of a
@@ -19,7 +19,7 @@
19
19
  * `{#dossier}` and `{#spoilers}`. *Which field* each lands in is a system's
20
20
  * business — SoHL writes the first to an actor's `appearance`, HM3 to an
21
21
  * actor's and an item's `description` — but *finding* it is not, so the
22
- * extraction lives here where every compiler reaches it (#139).
22
+ * extraction lives here where every compiler reaches it.
23
23
  *
24
24
  * It was a pair of private functions inside the SoHL actors pass, which is
25
25
  * where the convention was first needed and not where it belongs: the anchors
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  /**
15
- * The anchors a note declares on its headings — read in one place (#243).
15
+ * The anchors a note declares on its headings — read in one place.
16
16
  *
17
17
  * **A leaf, deliberately.** This is asked by the link checker, by the content
18
18
  * index, and by the builds that emit a link, and they cannot all import one