@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
@@ -19,7 +19,7 @@
19
19
  * failed are the same in every pass. They were written out
20
20
  * once per pass — three times when this was filed, five by the time it landed —
21
21
  * so a fix to any of them had to be made everywhere, and the passes drifted
22
- * apart in exactly the places nobody was comparing (#1509).
22
+ * apart in exactly the places nobody was comparing.
23
23
  *
24
24
  * A pass now states only what makes it that pass:
25
25
  *
@@ -43,7 +43,7 @@
43
43
  * `selects` answers *which document type* a pass claims, and it is the same
44
44
  * answer for every pack of that type. Which **pack of that type** a claimed
45
45
  * note lands in is a second question, answered by the pack router from the
46
- * note's own `pack:` declaration (#1566) — so a subclass never has to know that
46
+ * note's own `pack:` declaration — so a subclass never has to know that
47
47
  * its type ships in more than one pack.
48
48
  *
49
49
  * **This is the extension point.** The pack list is data
@@ -74,7 +74,7 @@ import {
74
74
  statsForPack,
75
75
  } from "./helpers.mjs";
76
76
  // The record accessors only — see `engine/index-records.mjs` for why they live
77
- // apart from the index that builds them (#243).
77
+ // apart from the index that builds them.
78
78
  import { isNoteRecord, noteFile } from "./index-records.mjs";
79
79
  import { emitDiagnostic } from "./diagnostics.mjs";
80
80
  import { assertNoDeclaredPackage } from "./note-package.mjs";
@@ -98,7 +98,7 @@ import { locateFrontmatterKey } from "./retired-fields.mjs";
98
98
  * note is one this build **refused** — it declares a retired frontmatter field
99
99
  * — and it is an error; a skipped one legitimately belongs to another pass, and
100
100
  * there are thousands of those. Folding the first into the second is what let a
101
- * whole tree be filtered out in silence (#56).
101
+ * whole tree be filtered out in silence.
102
102
  *
103
103
  * @typedef {object} PassStats
104
104
  * @property {number} compiled - Notes that became a document.
@@ -106,7 +106,7 @@ import { locateFrontmatterKey } from "./retired-fields.mjs";
106
106
  * @property {number} skippedOther - Notes this pass does not claim.
107
107
  * @property {number} declined - Notes this pack **refused** — one declaring a
108
108
  * retired frontmatter field, or one routed to a system pack whose system it
109
- * says nothing about (#58). Counted as errors, never as skips.
109
+ * says nothing about. Counted as errors, never as skips.
110
110
  */
111
111
 
112
112
  /**
@@ -148,7 +148,7 @@ export class BasePackCompiler {
148
148
  * its generated tables expanded and its wikilinks converted.
149
149
  *
150
150
  * False for a pass whose output must be exactly what the author typed —
151
- * the macros pass, whose `command` is executable source (#1514). A pass
151
+ * the macros pass, whose `command` is executable source. A pass
152
152
  * that says so also skips building the content-wide link index it would
153
153
  * never read.
154
154
  *
@@ -165,7 +165,7 @@ export class BasePackCompiler {
165
165
  * passes wrote — so an Actor pass must run after every Item pass, and it
166
166
  * says so here.
167
167
  *
168
- * The generator derives the compile order from this (#73), so the order
168
+ * The generator derives the compile order from this, so the order
169
169
  * `packs:` declares is presentation only — it is the manifest's `packs`
170
170
  * array as well, and a consumer orders that for a reader. A pass that
171
171
  * reads another's output states the dependency once, in the class that
@@ -181,7 +181,7 @@ export class BasePackCompiler {
181
181
 
182
182
  /**
183
183
  * Whether this pass's document **is** a system's data, and therefore takes
184
- * only notes that carry that system's block (#58).
184
+ * only notes that carry that system's block.
185
185
  *
186
186
  * A pack may declare a `system:` — `harn-ensemble` ships an `actors-hm3`
187
187
  * and an `actors-sohl` from one tree — and the note-side half of that is
@@ -212,7 +212,7 @@ export class BasePackCompiler {
212
212
  * legal on every note whatever its type, because
213
213
  * `BLOCK_DOCUMENT_PROPERTIES` maps it onto `document.img` — so a note whose
214
214
  * document has no such property authors it, validates, compiles, and loses
215
- * the value with nothing said. That is #349: `Parrot` in `sohl-thalorna`
215
+ * the value with nothing said: `Parrot` in `sohl-thalorna`
216
216
  * had declared `img:` since long before the art rule existed and compiled
217
217
  * `img: null` exactly as a note declaring nothing does. Naming the fields
218
218
  * here is what lets the lint tell an inert key from a live one.
@@ -237,7 +237,7 @@ export class BasePackCompiler {
237
237
  errorCount = 0;
238
238
 
239
239
  /**
240
- * Emitted-`system` findings, one per `documentType|subtype|field` (#155).
240
+ * Emitted-`system` findings, one per `documentType|subtype|field`.
241
241
  *
242
242
  * A key the compiler writes is on **every** document of a subtype, so
243
243
  * reporting it where it is found would print the same sentence 3,126 times
@@ -301,13 +301,19 @@ export class BasePackCompiler {
301
301
  * never descends into. Required: see {@link assertStatedScope}.
302
302
  * @param {(address: string|null) => string|null} [options.folderResolver] -
303
303
  * Resolves a `packFolder` — a folder note's address — to the Foundry
304
- * folder id it materialises as in this pack (#255, #257).
304
+ * folder id it materialises as in this pack.
305
305
  * @param {string} [options.packName] - The pack this pass writes.
306
+ * @param {string|null} [options.packSystem] - The system that pack declares,
307
+ * which decides the `_stats` it stamps and the system block a note must
308
+ * carry to reach it. `null` for a pack that declares none.
306
309
  * @param {string} [options.docType] - The Foundry document type it holds.
307
310
  * @param {{resolve: Function}} [options.router] - The pack router. Omit it
308
311
  * — as the unit suite does — and every claimed note is compiled here.
309
312
  * @param {boolean} [options.routingReporter] - Whether this pass reports a
310
313
  * note of its type that routes nowhere.
314
+ * @param {object} [options.corpus] - The corpus this compile is running
315
+ * over, derived once and shared by every pass. A pass handed none derives
316
+ * its own in `prepare`, and reports that corpus's problems itself.
311
317
  */
312
318
  constructor({
313
319
  contentBase,
@@ -347,7 +353,7 @@ export class BasePackCompiler {
347
353
  writable: false,
348
354
  });
349
355
  // The walk's scope, stated by whoever built this pass rather than
350
- // resolved from the working directory (#243). Every walk this compiler
356
+ // resolved from the working directory. Every walk this compiler
351
357
  // makes — its own, the table corpus, the link index, the SQL tables —
352
358
  // uses this one answer.
353
359
  Object.defineProperty(this, "skipDirectories", {
@@ -360,14 +366,14 @@ export class BasePackCompiler {
360
366
  this.router = router;
361
367
  this.routingReporter = routingReporter;
362
368
  // The corpus this compile is running over, derived once by
363
- // `generatePacksJson` and shared by every pass (#243). A pass that is
369
+ // `generatePacksJson` and shared by every pass. A pass that is
364
370
  // handed none derives its own in `prepare`, which is what a consumer
365
371
  // constructing one compiler directly does.
366
372
  this.corpus = corpus;
367
373
  }
368
374
 
369
375
  /**
370
- * The `_stats` block every entry this pass emits is stamped with (#48).
376
+ * The `_stats` block every entry this pass emits is stamped with.
371
377
  *
372
378
  * Per pack rather than per package, because a module may ship the same
373
379
  * content for two systems — `harn-ensemble` has an `actors-hm3` pack and an
@@ -410,8 +416,7 @@ export class BasePackCompiler {
410
416
  * routed *here* and carries nothing for this pack's system is an authoring
411
417
  * mistake with a hollow document at the end of it, not a note that belongs
412
418
  * to another pass. Skipping it quietly is how a whole tree compiles to
413
- * documents nobody can use — the failure mode #1502 and #56 are both
414
- * instances of.
419
+ * documents nobody can use.
415
420
  *
416
421
  * @param {object} fm - The note's frontmatter.
417
422
  * @returns {boolean} True when the note may be compiled here; `false` when
@@ -424,7 +429,7 @@ export class BasePackCompiler {
424
429
  eligibleFor(fm) {
425
430
  if (!this.constructor.requiresSystemBlock || !this.packSystem) return true;
426
431
  if (carriesSystemBlock(fm, this.packSystem)) return true;
427
- // Another system's pack of this document type will claim it (#139). A
432
+ // Another system's pack of this document type will claim it. A
428
433
  // note carrying only `hm3:` routes here because this pack is the
429
434
  // *default* of its document type, and defaults are declared per type
430
435
  // rather than per system — but it is not an incomplete note, it is
@@ -446,7 +451,7 @@ export class BasePackCompiler {
446
451
 
447
452
  /**
448
453
  * A refusal only this pass can make, because its subject is the note's
449
- * **type** (#330).
454
+ * **type**.
450
455
  *
451
456
  * The `assertNo*Field` family above it in the walk is type-agnostic by
452
457
  * construction: it runs before `selects`, so that a note declaring a
@@ -473,7 +478,7 @@ export class BasePackCompiler {
473
478
  * Whether this pass claims a note. **Required.**
474
479
  *
475
480
  * Called only for a note this build compiles — every note in the tree
476
- * belongs to the configured content package (#56) — so a subclass decides
481
+ * belongs to the configured content package — so a subclass decides
477
482
  * on `type` alone.
478
483
  *
479
484
  * @param {object} fm - The note's frontmatter.
@@ -524,7 +529,7 @@ export class BasePackCompiler {
524
529
  // The corpus, and the three whole-tree derivations built over it. Every
525
530
  // one of them is a pure function of (tree, scope, router), which do not
526
531
  // vary between the passes of a single compile — so `generatePacksJson`
527
- // derives them once and hands them to each pass (#243).
532
+ // derives them once and hands them to each pass.
528
533
  //
529
534
  // The measurement that motivated it: compiling `sohl` read every note
530
535
  // **20 times**, four per pass — this link index, the table corpus, the
@@ -580,14 +585,14 @@ export class BasePackCompiler {
580
585
  type: fm.type,
581
586
  id: fm.id,
582
587
  // Where this note is, so a link that resolves nowhere is reported
583
- // at a position an author can open rather than by note name (#17).
588
+ // at a position an author can open rather than by note name.
584
589
  file: absPath,
585
590
  bodyLine,
586
591
  bodyColumn,
587
592
  lineMap,
588
593
  // A `[[#slug]]` self-link addresses the source note, which has no
589
594
  // entry in the index — so where its own documents landed has to
590
- // travel with it (#1566).
595
+ // travel with it.
591
596
  pack: this.router?.resolveOrNull(fm, packForType(fm.type).docType),
592
597
  docPack: this.router?.resolveOrNull(fm, "JournalEntry"),
593
598
  index: this.linkIndex,
@@ -636,8 +641,7 @@ export class BasePackCompiler {
636
641
  }
637
642
 
638
643
  /**
639
- * Report every `<system>.system` key the receiving subtype does not declare
640
- * (#58).
644
+ * Report every `<system>.system` key the receiving subtype does not declare.
641
645
  *
642
646
  * An **error**, not a warning: Foundry drops an unknown `system` key at
643
647
  * construction without a word, so the alternative is a document shipped
@@ -657,8 +661,8 @@ export class BasePackCompiler {
657
661
  */
658
662
  reportUndeclaredSystemData(fm, block, documentType, subType) {
659
663
  const absPath = this.currentNote?.absPath;
660
- // Whose schema, where a build has more than one system: this pack's
661
- // (#139). `undefined` — a pack that declares no system — keeps the
664
+ // Whose schema, where a build has more than one system: this pack's.
665
+ // `undefined` — a pack that declares no system — keeps the
662
666
  // package-wide answer this always used.
663
667
  const findings = checkAuthoredSystemData(fm, {
664
668
  block,
@@ -676,7 +680,7 @@ export class BasePackCompiler {
676
680
 
677
681
  /**
678
682
  * Record every `system` key the *compiled document* carries that the
679
- * receiving subtype does not declare (#155).
683
+ * receiving subtype does not declare.
680
684
  *
681
685
  * The sibling of {@link BasePackCompiler#reportUndeclaredSystemData}, and
682
686
  * the half that sees what no declaration states. A compiler writes keys of
@@ -734,7 +738,7 @@ export class BasePackCompiler {
734
738
  /**
735
739
  * Emit the collected emitted-`system` findings, once each.
736
740
  *
737
- * An **error**, for the reason #60 made its sibling one: the value is gone
741
+ * An **error**, for the reason its sibling is one: the value is gone
738
742
  * at load and the build says nothing, and severity that varied by *which
739
743
  * part of the build wrote the key* would make the less fixable half the
740
744
  * quieter one. What varies is the message, which says whose fix it is —
@@ -859,7 +863,7 @@ export class BasePackCompiler {
859
863
  }
860
864
  if (stats.declined) {
861
865
  // Its own line, at error level: these are not skips, and burying
862
- // them in the skipped tally is the defect (#56). Each one has
866
+ // them in the skipped tally is the defect. Each one has
863
867
  // already been named individually as a diagnostic.
864
868
  log.error(
865
869
  `Declined ${stats.declined} note(s) — each named above, with ` +
@@ -892,7 +896,7 @@ export class BasePackCompiler {
892
896
  // refusal, because it *is* the same refusal: the index defers to
893
897
  // `assertNoDeclaredPackage` for a retired `package:`, which is the
894
898
  // check this loop makes a few lines further down. All that changed is
895
- // which pass sees the note first (#243).
899
+ // which pass sees the note first.
896
900
  if (this.reportsCorpusProblems) {
897
901
  for (const problem of this.corpus.problems ?? []) {
898
902
  stats.declined++;
@@ -911,7 +915,7 @@ export class BasePackCompiler {
911
915
  }
912
916
 
913
917
  // The corpus, from the index this compile derived once — not a walk of
914
- // this pass's own (#243). Each note is then read for its **prose**: the
918
+ // this pass's own. Each note is then read for its **prose**: the
915
919
  // index carries what is *about* a note and deliberately not its text,
916
920
  // nor the `bodyLine`/`bodyColumn` a diagnostic needs, and this pass has
917
921
  // to have the body anyway. So the read is one this pass was already
@@ -922,7 +926,7 @@ export class BasePackCompiler {
922
926
  const absPath = noteFile(this.contentBase, record);
923
927
  const { frontmatter: fm, body, bodyLine, bodyColumn } = parseMarkdownFile(absPath);
924
928
  // Which note this pass is on, so anything it calls can report a
925
- // position without every method having to be handed one (#17).
929
+ // position without every method having to be handed one.
926
930
  this.currentNote = { absPath, bodyLine, bodyColumn };
927
931
  // A file carrying no frontmatter at all is not a note.
928
932
  if (!fm) {
@@ -933,12 +937,12 @@ export class BasePackCompiler {
933
937
  // note is answered whichever pass would have claimed it — and
934
938
  // whatever the declared value says.
935
939
  //
936
- // - `package:` (#56): a note's package is the repository's
940
+ // - `package:`: a note's package is the repository's
937
941
  // configured one, so declaring it restates a constant.
938
- // - `draft:` (#69): it excluded the note from the packs, the
942
+ // - `draft:`: it excluded the note from the packs, the
939
943
  // manifest and the site, and no checker reported the links that
940
944
  // left dangling.
941
- // - `aliases:` (#180): it fed the alias index, which the bare
945
+ // - `aliases:`: it fed the alias index, which the bare
942
946
  // `[[Alias]]` form was looked up in; the form is retired, so the
943
947
  // list has no reader left. The nested `name.aliases` is a
944
948
  // different field and is **not** refused — it is reserved, and
@@ -973,7 +977,7 @@ export class BasePackCompiler {
973
977
  }
974
978
  // The id this note's document is filed under: its authored `id`
975
979
  // if it pins one, otherwise the id derived from its canonical
976
- // address (#270). Resolved for every note this pass claims, and
980
+ // address. Resolved for every note this pass claims, and
977
981
  // through the one function every other corpus reader calls — the
978
982
  // wikilink index, the content index and the Foundry-address pass
979
983
  // must all compute the id this pass compiles under, and none of
@@ -1021,7 +1025,7 @@ export class BasePackCompiler {
1021
1025
  stats.skippedOther++;
1022
1026
  continue;
1023
1027
  }
1024
- // The type-specific half of the retired-field family (#330):
1028
+ // The type-specific half of the retired-field family:
1025
1029
  // what a note of *this* type may not write, which needs the
1026
1030
  // type's own field declaration and so cannot be asked before
1027
1031
  // `selects`. Counted as a declined note for the same reason
@@ -1048,7 +1052,7 @@ export class BasePackCompiler {
1048
1052
  this.errorCount++;
1049
1053
  // `position` is set by whatever failed if it knew where — an
1050
1054
  // unresolved address, a bad table directive — so the report
1051
- // points at the line rather than at the note (#17).
1055
+ // points at the line rather than at the note.
1052
1056
  this.noteError(
1053
1057
  `${this.noteLabel(fm)} failed to compile: ${err.message}`,
1054
1058
  err.position,
@@ -21,9 +21,9 @@
21
21
  * world's collection already holds that `_id`, then creates or updates each.
22
22
  * Afterwards the documents live independently and the Adventure has no further
23
23
  * role. **It is an installer** — a set of document *copies* packaged for
24
- * one-shot import (#259).
24
+ * one-shot import.
25
25
  *
26
- * That is the whole difference from a folder (#256), which is a live grouping
26
+ * That is the whole difference from a folder, which is a live grouping
27
27
  * **by reference** that persists in the pack. A bundle carries copies and
28
28
  * exists to be imported once, so the two are not variations on one idea.
29
29
  *
@@ -140,7 +140,7 @@ export function bareAddress(value) {
140
140
  * `### type: bundle` table puts it — the closed container, so a misspelled
141
141
  * `content` is a finding rather than a silently empty Adventure. It is accepted
142
142
  * at the top level too, exactly as a folder note's `parent` is: an author
143
- * following #259's own example rather than the specification should get a
143
+ * following the shipped example rather than the specification should get a
144
144
  * bundle, not a silent default.
145
145
  *
146
146
  * **Order is the author's**, and it is kept: an Adventure's `SetField` attaches
@@ -269,7 +269,7 @@ export function buildAdventure({
269
269
  _id: id,
270
270
  _stats: stats,
271
271
  // The Foundry *collection* name, not the pack's: a pack directory may
272
- // be called anything (#1566), and the key names the collection the
272
+ // be called anything, and the key names the collection the
273
273
  // record belongs to inside it.
274
274
  _key: `!adventures!${id}`,
275
275
  };
@@ -13,11 +13,11 @@
13
13
 
14
14
  /**
15
15
  * Adventure pack compiler — `type: bundle` notes → Foundry `Adventure`
16
- * documents (#259).
16
+ * documents.
17
17
  *
18
- * The specification and the vocabulary landed in #263, which left the type
18
+ * The specification and the vocabulary leave the type
19
19
  * declared and uncompiled: authoring one said so, in as many words. This is the
20
- * pass, and the two decisions #263 recorded are settled here.
20
+ * pass, and the two decisions it records are settled here.
21
21
  *
22
22
  * **Which pack.** Not the `adventures` **companion**. The scenes pass already
23
23
  * writes one Adventure per pinned place into that pack, and a companion is
@@ -32,7 +32,7 @@
32
32
  * the router's rule for `pack:`, so there is one answer and not two. One note
33
33
  * can compile into two documents — an item and the JournalEntry its prose
34
34
  * became — and the second is bundled only when the note names it by its own
35
- * `doc…` address, which is the address that already exists for it (#1362).
35
+ * `doc…` address, which is the address that already exists for it.
36
36
  * Nothing is inferred: naming `miscgear-bowlcer` puts the *item* in the bundle
37
37
  * and not its description page.
38
38
  *
@@ -41,7 +41,7 @@
41
41
  * a reader of every other one, which it states in
42
42
  * {@link Bundles.readsPackOutputOf} rather than leaving to the order a
43
43
  * consumer happened to write its pack list in — the generator derives the
44
- * compile order from that declaration (#73).
44
+ * compile order from that declaration.
45
45
  *
46
46
  * **A pack's `system:` constrains what its Adventures may hold.** An
47
47
  * `Adventure` has no `system` field, so a bundle spanning two systems cannot be
@@ -83,7 +83,7 @@ import { readQualifier } from "./wikilinks.mjs";
83
83
  * the first.
84
84
  *
85
85
  * A **folder** document is skipped. It is emitted into every pack that holds
86
- * something filed in it (#257), so it is not one pack's document and has no
86
+ * something filed in it, so it is not one pack's document and has no
87
87
  * single note behind it; a bundle that wants folders is a question this pass
88
88
  * refuses rather than guesses at — see {@link Bundles#resolveAddress}.
89
89
  *
@@ -119,6 +119,15 @@ export function loadBundleSources(sourceDirs) {
119
119
  return documents;
120
120
  }
121
121
 
122
+ /**
123
+ * Adventure pack compiler.
124
+ *
125
+ * Walks the content tree and compiles every `type: bundle` note into one
126
+ * Adventure per system, holding **copies** of the compiled documents its
127
+ * `contents` addresses name. Reading other passes' output is what
128
+ * {@link Bundles.readsPackOutputOf} declares, and what the generator orders the
129
+ * compile by.
130
+ */
122
131
  export class Bundles extends BasePackCompiler {
123
132
  static id = "bundles";
124
133
  static label = "bundle";
@@ -126,7 +135,7 @@ export class Bundles extends BasePackCompiler {
126
135
  /**
127
136
  * Every document type a bundle can hold a copy of.
128
137
  *
129
- * The declaration the generator orders passes by (#73): an Adventure holds
138
+ * The declaration the generator orders passes by: an Adventure holds
130
139
  * *compiled* documents, so every pass that produces one runs first. Stated
131
140
  * here, in the class that does the reading, rather than in each consuming
132
141
  * repository's pack list.
@@ -225,7 +234,7 @@ export class Bundles extends BasePackCompiler {
225
234
  throw new Error(
226
235
  `bundle "${bundleName}" lists the folder "${address}". A ` +
227
236
  `folder is not a member: it materialises in every pack ` +
228
- `holding something filed in it (#257), so it belongs to no ` +
237
+ `holding something filed in it, so it belongs to no ` +
229
238
  `one pack and there is no single copy to bundle. List the ` +
230
239
  `documents instead`,
231
240
  );
@@ -18,7 +18,7 @@
18
18
  * written. Every build-time rewriter that pattern-matches a body therefore has
19
19
  * to know where code is — otherwise a source listing that happens to contain
20
20
  * the rewriter's syntax is silently corrupted. Wikilink conversion met this as
21
- * `[[0]]` inside a fence being turned into a link (#1505), and it depended on
21
+ * `[[0]]` inside a fence being turned into a link, and it depended on
22
22
  * the surrounding literal's shape (`[[1,2],[3,4]]` survived), so the corruption
23
23
  * looked arbitrary.
24
24
  *
@@ -378,12 +378,12 @@ export function matchAllOutsideCode(markdown, pattern, options) {
378
378
  * **Which runs count as code is {@link codeRegions}' rule, not a second copy of
379
379
  * it.** The knowledgebase build carried its own regex once, and it was weaker in
380
380
  * two ways that both corrupted the one page whose subject *is* link syntax — so
381
- * its examples were exactly the input a looser rule mangles (SoHL#1665). A
381
+ * its examples are exactly the input a looser rule mangles. A
382
382
  * single-backtick span was allowed to cross newlines, so one odd backtick paired
383
383
  * with another paragraphs away and every span after it paired wrongly: prose was
384
384
  * masked as code while real spans were left exposed. And only three-backtick
385
385
  * fences were recognised, so a four-backtick example holding a three-backtick
386
- * block — the documented "fences of any length" case (#1505) — leaked its
386
+ * block — the documented "fences of any length" case — leaked its
387
387
  * contents.
388
388
  *
389
389
  * @param {string} body - The markdown body.
@@ -25,13 +25,13 @@
25
25
  *
26
26
  * **This module has no import-time side effects.** It creates no directories,
27
27
  * reads no manifest, configures no logger, and parses no argv — every path and
28
- * pack list is a parameter, defaulted from the resolved build configuration
29
- * (#1508), which a caller may replace wholesale to compile another package's
28
+ * pack list is a parameter, defaulted from the resolved build configuration,
29
+ * which a caller may replace wholesale to compile another package's
30
30
  * tree. Those side effects belong to the command
31
31
  * line that drives it (`bin/build-compendiums.mjs`), so the library can be
32
32
  * imported by another repository's build, or by a test, without a stray
33
33
  * `build/` tree appearing or the shared `loglevel` singleton being
34
- * reconfigured (#1507). In particular, a *module* repository ships
34
+ * reconfigured. In particular, a *module* repository ships
35
35
  * `module.json` rather than `system.template.json`, so importing must not
36
36
  * depend on the latter existing.
37
37
  *
@@ -64,7 +64,7 @@ import { loadPackConfig } from "./pack-config.mjs";
64
64
  * @param {object} opts
65
65
  * @param {object} [opts.config] The resolved build configuration, which
66
66
  * the two path arguments below default from. Supplying one is how a caller
67
- * compiles a package other than this repository's (#1508).
67
+ * compiles a package other than this repository's.
68
68
  * @param {string[]} [opts.sourcePacks] Every pack compiled from the content
69
69
  * tree, in compile order. Defaults to the configured pack directories.
70
70
  * @param {string} [opts.stageDest] Directory the LevelDB packs are built
@@ -73,10 +73,10 @@ import { loadPackConfig } from "./pack-config.mjs";
73
73
  * @throws {Error} If pack JSON generation reported any error. Packs compiled
74
74
  * from incomplete or empty JSON ship blank or short compendiums, and the
75
75
  * omission is invisible until a player looks for content that is not there
76
- * (#1502) — so this is fatal, not a warning, and the caller is expected to
76
+ * — so this is fatal, not a warning, and the caller is expected to
77
77
  * turn it into a failing exit code.
78
78
  * @throws {Error} If a compiled pack ships a Scene that has lost its embedded
79
- * Level (#1538). Fatal for the same reason: Foundry reads a missing Level
79
+ * Level. Fatal for the same reason: Foundry reads a missing Level
80
80
  * record as "no levels" and persists that on the next world launch, so the
81
81
  * map image is gone before anyone notices it was ever at risk. See
82
82
  * {@link verifyPackSceneLevels}.
@@ -137,7 +137,7 @@ export async function compilePacks({
137
137
  // LevelDB key. Nothing in Foundry ties the two together on read: a
138
138
  // missing Level record only warns, and the next world launch persists
139
139
  // the emptied `levels` array — so the map is lost for good and the
140
- // only symptom is a blank battlemap (#1538). Assert it on the bytes
140
+ // only symptom is a blank battlemap. Assert it on the bytes
141
141
  // just written, which is the one place the compendium CLI's write path
142
142
  // is observable.
143
143
  const problems = await verifyPackSceneLevels(stage);
@@ -164,7 +164,7 @@ export async function compilePacks({
164
164
  * @param {number} [options.ownership=0] Value to reset default ownership to.
165
165
  * @param {string} [options.lastModifiedBy] The stamped author id. Defaults to
166
166
  * the configured one — the same value `buildStats` stamps, so a compiled
167
- * entry and a re-cleaned one never disagree (#1508).
167
+ * entry and a re-cleaned one never disagree.
168
168
  */
169
169
  function cleanPackEntry(
170
170
  data,
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  /**
15
- * The corpus one compile runs over, and everything derived from it (#243).
15
+ * The corpus one compile runs over, and everything derived from it.
16
16
  *
17
17
  * **A compile read every note twenty times.** Measured over `sohl`'s 1,685
18
18
  * notes: 33,700 reads, exactly twenty per note. Four per pass — the
@@ -24,10 +24,9 @@
24
24
  * to all of them.
25
25
  *
26
26
  * So they are derived **once**, here, and every pass is handed the result. That
27
- * is #243's claim stated at the point where it costs the most: not that
27
+ * is the claim stated at the point where it costs the most: not that
28
28
  * re-deriving is wasteful, but that N passes each answering "which files are
29
- * the corpus?" is N answers that can differ — which is what #241 was, and what
30
- * the twenty reads were paying for.
29
+ * the corpus?" is N answers that can differ.
31
30
  *
32
31
  * **This module exists apart from the compilers for an import reason.**
33
32
  * Deriving the index reaches the pack router and the manifest emitter, and
@@ -50,7 +49,7 @@ import { prepareTreeSqlTables } from "./sql-tables.mjs";
50
49
  * @param {object} opts - Options.
51
50
  * @param {string} opts.contentBase - Root of the content tree.
52
51
  * @param {readonly string[]} opts.skipDirectories - The scope, stated by the
53
- * caller as every corpus read requires (#243).
52
+ * caller as every corpus read requires.
54
53
  * @param {object} opts.router - The pack router this compile resolved. Shared
55
54
  * by every pass, which is what makes one link index correct for all of them.
56
55
  * @param {object} [opts.config] - The resolved configuration.
@@ -19,13 +19,13 @@
19
19
  * address other packages link to. Stating it twice is how a manifest comes to
20
20
  * assert a URL that resolves at build time and 404s for the reader.
21
21
  *
22
- * **A page's URL is its address** — `<package>/<type>-<shortcode>/` (#181), and
23
- * it carries **no `<system>` segment** even though the canonical address does
24
- * (#59). That is not an omission: a note publishes one page however many
22
+ * **A page's URL is its address** — `<package>/<type>-<shortcode>/`, and
23
+ * it carries **no `<system>` segment** even though the canonical address does.
24
+ * That is not an omission: a note publishes one page however many
25
25
  * systems' documents it compiles into, so there is nothing for the segment to
26
26
  * distinguish, and adding it would split one page's URL in two. The canonical
27
27
  * address names a *document*; this names a *page*. It
28
- * used to be derived from `name.full`, which made a display string load-bearing
28
+ * is not derived from `name.full`, which would make a display string load-bearing
29
29
  * in three separate ways: a rename moved the URL and nothing redirected, two
30
30
  * notes in one section could derive the same URL so a uniqueness check had to
31
31
  * run, and a long name had to be abbreviated through a word table to keep the
@@ -62,7 +62,7 @@ export { DEFAULT_ADDRESS_SCHEME };
62
62
  */
63
63
  export { NO_SYSTEM };
64
64
 
65
- /** The knowledgebase's mount within this package's site (#1470). */
65
+ /** The knowledgebase's mount within this package's site. */
66
66
  export const KB_PREFIX = "kb/";
67
67
 
68
68
  /**
@@ -73,7 +73,7 @@ export const KB_PREFIX = "kb/";
73
73
  * key's **last two segments** — which is what makes a manifest entry's `path`
74
74
  * derivable from the key it is filed under rather than transported beside it.
75
75
  *
76
- * It was once the key's whole tail, and #59 ended that: the key gained a
76
+ * It is not the key's whole tail: the key carries a
77
77
  * `<system>` segment, so its tail is now `system-type-shortcode` and a slug is
78
78
  * the tail with that segment dropped. The behaviour here is unchanged, and
79
79
  * deliberately — a page has no system to name (see the module note above), so
@@ -81,11 +81,11 @@ export const KB_PREFIX = "kb/";
81
81
  * consumer deriving a `path` from a key drops the *package and the system*, not
82
82
  * the package alone.
83
83
  *
84
- * The hyphen is a separator and never occurs inside a segment: a shortcode is
85
- * `^[A-Za-z0-9]+$` (`ADDRESS_SEGMENT_PATTERN`, enforced by `content-lint.mjs`)
86
- * and a type is a bare word. That is the same charset guarantee positional key
87
- * parsing rests on, so the address and the key cannot disagree about where one
88
- * ends and the other begins.
84
+ * The hyphen is a separator and never occurs inside a segment: a shortcode
85
+ * matches `ADDRESS_SEGMENT_PATTERN` (`engine/address-charset.mjs`, enforced by
86
+ * `content-lint.mjs`) and a type is a bare word. That is the same charset
87
+ * guarantee positional key parsing rests on, so the address and the key
88
+ * cannot disagree about where one ends and the other begins.
89
89
  *
90
90
  * @param {object} fm - Parsed frontmatter.
91
91
  * @returns {string} The address segment, e.g. `weapongear-dagger`.
@@ -131,9 +131,9 @@ export function addressSlug(fm) {
131
131
  *
132
132
  * **It is a pure function of the frontmatter**, and takes no options. Nothing
133
133
  * about the file the note was read from reaches it: the `README.md` convention
134
- * that made one note address a whole section is retired with the section itself
135
- * (#204), so every note is addressed alike and there is one rule and no branch.
136
- * It took an address scheme until #215, to validate a `landing` rule it then
134
+ * that made one note address a whole section is retired with the section itself,
135
+ * so every note is addressed alike and there is one rule and no branch.
136
+ * It takes no address scheme; the `landing` rule it once validated is
137
137
  * discarded; with that key retired, `prefix` was the only thing left in the
138
138
  * scheme and the paragraph above is the reason it never applied.
139
139
  *
@@ -169,9 +169,9 @@ export function packageAddress(fm) {
169
169
  * merge is a real conflict rather than an artefact of two packages sharing a
170
170
  * namespace. `(type, shortcode)` alone is unique only *within* a package, and
171
171
  * two independently authored packages reaching for the same short string is a
172
- * matter of time (#1499).
172
+ * matter of time.
173
173
  *
174
- * **The system segment (#59).** A package may ship content for more than one
174
+ * **The system segment.** A package may ship content for more than one
175
175
  * system, and one note then compiles into a document per system — an actor in
176
176
  * `actors-sohl` *and* an actor in `actors-hm3`. Without a system segment both
177
177
  * land on one key, so the address cannot name either of them. `harn-ensemble`
@@ -234,7 +234,7 @@ export function blockSystem(keyPath) {
234
234
  /**
235
235
  * Expand a written address to the one canonical address it names.
236
236
  *
237
- * **An omitted segment defaults from where the link is written** (#336) — it is
237
+ * **An omitted segment defaults from where the link is written** — it is
238
238
  * not a wildcard, and resolution is not a search. Package omitted means the
239
239
  * citing note's own; system omitted means {@link blockSystem} of the key path it
240
240
  * was written under. So every short form has exactly one expansion, computed
@@ -311,12 +311,12 @@ export const CANONICAL_KEY_SEGMENTS = 4;
311
311
  *
312
312
  * Parsing is plain positional counting: split on the separator, require
313
313
  * {@link CANONICAL_KEY_SEGMENTS} of them, and assign each position its field.
314
- * **The charset rule is what makes that sound** — every segment is
315
- * `^[A-Za-z0-9]+$` (`ADDRESS_SEGMENT_PATTERN` in `engine/address-charset.mjs`),
316
- * so the hyphen is purely a separator and the count alone determines every
317
- * field. That is enforced at each of the three sources rather than assumed of
318
- * the data: shortcodes by `content-lint.mjs` (#1397), `contentPackage` by
319
- * `defineConfig` (#59), and types are bare words. Were any of them free to
314
+ * **The charset rule is what makes that sound** — every segment matches
315
+ * `ADDRESS_SEGMENT_PATTERN` (`engine/address-charset.mjs`), so the hyphen is
316
+ * purely a separator and the count alone determines every field. That is
317
+ * enforced at each of the three sources rather than assumed of
318
+ * the data: shortcodes by `content-lint.mjs`, `contentPackage` by
319
+ * `defineConfig`, and types are bare words. Were any of them free to
320
320
  * carry a hyphen, no amount of counting would recover the fields and the reader
321
321
  * would need a vocabulary to match against instead.
322
322
  *
@@ -440,7 +440,7 @@ export const DOCUMENT_ID_NAMESPACE = "document";
440
440
 
441
441
  /**
442
442
  * The Foundry `_id` of the document a note compiles into, derived from its
443
- * canonical address (#270).
443
+ * canonical address.
444
444
  *
445
445
  * A note used to author this — an opaque 16-character string, one per note,
446
446
  * that said nothing its address did not and that no check guaranteed. The
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  /**
15
- * The charset authored content is held to, so a book can choose its face (#377).
15
+ * The charset authored content is held to, so a book can choose its face.
16
16
  *
17
17
  * The packs and the website render in whatever font the reader's browser or
18
18
  * Foundry supplies, and a glyph nobody has is somebody else's problem. **A book