@heroiclands/package-build 8.0.0 → 9.0.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 (75) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/bin/content-build.mjs +44 -118
  3. package/bin/package-build.mjs +27 -69
  4. package/bin/report.mjs +1 -2
  5. package/bundle.mjs +2 -10
  6. package/config.mjs +31 -106
  7. package/container.mjs +13 -57
  8. package/content-config.mjs +45 -164
  9. package/coverage.mjs +14 -55
  10. package/deploy.mjs +4 -13
  11. package/e2e.mjs +16 -55
  12. package/engine/address-diff.mjs +1 -4
  13. package/engine/base-compiler.mjs +10 -28
  14. package/engine/code-fences.mjs +4 -13
  15. package/engine/compendiums.mjs +13 -37
  16. package/engine/content-address.mjs +2 -6
  17. package/engine/content-links.mjs +13 -44
  18. package/engine/content-lint.mjs +4 -15
  19. package/engine/content-slug.mjs +2 -6
  20. package/engine/content-tables.mjs +26 -79
  21. package/engine/diagnostics.mjs +4 -15
  22. package/engine/field-reference.mjs +6 -20
  23. package/engine/field-spec.mjs +1 -3
  24. package/engine/foreign-catalog.mjs +7 -22
  25. package/engine/foreign-manifests.mjs +1 -4
  26. package/engine/frontmatter-lint.mjs +6 -18
  27. package/engine/frontmatter.mjs +3 -8
  28. package/engine/generate.mjs +4 -16
  29. package/engine/helpers.mjs +13 -52
  30. package/engine/homepage.mjs +4 -15
  31. package/engine/ids.mjs +3 -12
  32. package/engine/item-registry.mjs +2 -6
  33. package/engine/journals.mjs +4 -14
  34. package/engine/kb-manifest.mjs +5 -17
  35. package/engine/macros.mjs +2 -10
  36. package/engine/manifest-emit.mjs +6 -17
  37. package/engine/map-notes.mjs +19 -69
  38. package/engine/note-package.mjs +1 -4
  39. package/engine/pack-config.mjs +17 -38
  40. package/engine/pack-router.mjs +1 -2
  41. package/engine/prose-config.mjs +20 -4
  42. package/engine/prose-lint.mjs +6 -14
  43. package/engine/region-events.mjs +1 -3
  44. package/engine/scene-levels.mjs +8 -22
  45. package/engine/scenes.mjs +13 -47
  46. package/engine/schema-check.mjs +1 -4
  47. package/engine/schema-extract.mjs +70 -45
  48. package/engine/site-build.mjs +12 -37
  49. package/engine/site-index.mjs +3 -15
  50. package/engine/web-wikilinks.mjs +4 -13
  51. package/engine/wikilinks.mjs +115 -167
  52. package/index.mjs +1 -5
  53. package/lang.mjs +1 -3
  54. package/manifest.mjs +10 -37
  55. package/markdownlint-config.mjs +1 -5
  56. package/package.json +1 -1
  57. package/sohl/actors.mjs +10 -40
  58. package/sohl/being-info.mjs +3 -6
  59. package/sohl/index.mjs +1 -6
  60. package/sohl/item-builders.mjs +1 -3
  61. package/sohl/item-fields.mjs +16 -34
  62. package/sohl/items.mjs +1 -3
  63. package/sohl/kb-passes.mjs +29 -39
  64. package/sohl/skill-base.mjs +7 -23
  65. package/stage.mjs +3 -13
  66. package/templates.mjs +4 -15
  67. package/types/bundle.d.mts +1 -1
  68. package/types/container.d.mts +2 -2
  69. package/types/coverage.d.mts +1 -1
  70. package/types/e2e.d.mts +4 -4
  71. package/types/engine/generate.d.mts +1 -1
  72. package/types/engine/helpers.d.mts +1 -1
  73. package/types/engine/schema-extract.d.mts +1 -1
  74. package/types/engine/site-index.d.mts +1 -1
  75. package/types/manifest.d.mts +1 -1
@@ -277,10 +277,7 @@ export function resolveSchemaArtifact(config) {
277
277
  });
278
278
 
279
279
  // The system checking itself, against the schema its own build published.
280
- if (
281
- config.packageKind === "systems" &&
282
- config.foundryPackage === systemId
283
- ) {
280
+ if (config.packageKind === "systems" && config.foundryPackage === systemId) {
284
281
  const own = path.join(config.rootDir, SCHEMA_ARTIFACT_FILE);
285
282
  return fs.existsSync(own) ? read(own) : null;
286
283
  }
@@ -95,6 +95,37 @@ function propName(name) {
95
95
  return name.getText();
96
96
  }
97
97
 
98
+ /**
99
+ * A *field* name, which unlike a property name elsewhere must be knowable.
100
+ *
101
+ * A computed key — `[`${name}Date`]: worldTimeDateField()` — depends on an
102
+ * argument this reader does not evaluate, so its real name is not in the file.
103
+ * {@link propName} would hand back the source text, and a field called
104
+ * ``[`${name}Date`]`` matches nothing a builder could ever emit: it is absent
105
+ * from the schema for checking purposes while *looking* present, and it shows
106
+ * up as permanently unemitted noise.
107
+ *
108
+ * So this refuses rather than guessing. The schema is a contract other
109
+ * repositories read, and a contract it cannot state is worth stopping for —
110
+ * the same reason `compareFields` refuses an artifact of the wrong version
111
+ * instead of resolving it anyway. Writing the keys out fixes it at the source,
112
+ * where the names are actually decided.
113
+ *
114
+ * @param {ts.PropertyName} name - The property name node.
115
+ * @param {string} file - For the message.
116
+ * @returns {string} The literal field name.
117
+ * @throws {Error} When the name is computed.
118
+ */
119
+ function fieldName(name, file) {
120
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
121
+ throw new Error(
122
+ `${path.basename(file)} declares a schema field with a computed name, ` +
123
+ `\`${name.getText()}\`, whose value depends on an argument this ` +
124
+ `reader does not evaluate. Write the keys out so the published ` +
125
+ `schema can name them.`,
126
+ );
127
+ }
128
+
98
129
  /**
99
130
  * The `subtype: ClassName` entries of a registry object literal.
100
131
  *
@@ -119,10 +150,7 @@ export function registryOf(src, name) {
119
150
  }
120
151
  if (ts.isObjectLiteralExpression(init)) {
121
152
  for (const p of init.properties) {
122
- if (
123
- ts.isPropertyAssignment(p) &&
124
- ts.isIdentifier(p.initializer)
125
- ) {
153
+ if (ts.isPropertyAssignment(p) && ts.isIdentifier(p.initializer)) {
126
154
  out.set(propName(p.name), p.initializer.text);
127
155
  }
128
156
  }
@@ -185,10 +213,7 @@ function resolveSpecifier(fromDir, spec, aliases) {
185
213
  }
186
214
  if (!base) return null;
187
215
  for (const ext of EXTENSIONS) {
188
- for (const candidate of [
189
- `${base}${ext}`,
190
- path.join(base, `index${ext}`),
191
- ]) {
216
+ for (const candidate of [`${base}${ext}`, path.join(base, `index${ext}`)]) {
192
217
  if (fs.existsSync(candidate)) return candidate;
193
218
  }
194
219
  }
@@ -212,11 +237,7 @@ function importSourceOf(src, name, aliases) {
212
237
  if (!bindings.elements.some((e) => e.name.text === name)) return;
213
238
  const spec = node.moduleSpecifier;
214
239
  if (!ts.isStringLiteral(spec)) return;
215
- found = resolveSpecifier(
216
- path.dirname(src.fileName),
217
- spec.text,
218
- aliases,
219
- );
240
+ found = resolveSpecifier(path.dirname(src.fileName), spec.text, aliases);
220
241
  });
221
242
  return found;
222
243
  }
@@ -255,9 +276,7 @@ function locateClass(fromFile, className, aliases, cache) {
255
276
  if (!imported) return null;
256
277
  const importedSrc = parse(imported, cache);
257
278
  const importedDecl = classDeclIn(importedSrc, className);
258
- return importedDecl ?
259
- { file: imported, src: importedSrc, decl: importedDecl }
260
- : null;
279
+ return importedDecl ? { file: imported, src: importedSrc, decl: importedDecl } : null;
261
280
  }
262
281
 
263
282
  /**
@@ -272,8 +291,7 @@ function superClassOf(decl) {
272
291
  for (const clause of decl.heritageClauses ?? []) {
273
292
  if (clause.token !== ts.SyntaxKind.ExtendsKeyword) continue;
274
293
  const [type] = clause.types;
275
- if (type && ts.isIdentifier(type.expression))
276
- return type.expression.text;
294
+ if (type && ts.isIdentifier(type.expression)) return type.expression.text;
277
295
  }
278
296
  return null;
279
297
  }
@@ -319,9 +337,8 @@ function literalReturnedBy(src, fnName) {
319
337
  if (isTarget) {
320
338
  const body =
321
339
  ts.isFunctionDeclaration(node) ? node.body
322
- : node.initializer && ts.isArrowFunction(node.initializer) ?
323
- node.initializer.body
324
- : null;
340
+ : node.initializer && ts.isArrowFunction(node.initializer) ? node.initializer.body
341
+ : null;
325
342
  if (body) literal = returnedLiteral(body);
326
343
  }
327
344
  ts.forEachChild(node, visit);
@@ -373,14 +390,10 @@ function isObjectAssign(expr) {
373
390
  */
374
391
  function schemaContributions(src, decl) {
375
392
  const method = decl.members.find(
376
- (m) =>
377
- ts.isMethodDeclaration(m) &&
378
- propName(m.name) === "defineSchema" &&
379
- m.body,
393
+ (m) => ts.isMethodDeclaration(m) && propName(m.name) === "defineSchema" && m.body,
380
394
  );
381
395
  // No `defineSchema()` — the whole schema is the parent's.
382
- if (!method)
383
- return { literals: [], edges: [{ kind: "super", name: null }] };
396
+ if (!method) return { literals: [], edges: [{ kind: "super", name: null }] };
384
397
 
385
398
  const direct = returnedLiteral(method.body);
386
399
  if (direct) return { literals: [direct], edges: [] };
@@ -424,16 +437,16 @@ function isSchemaField(expr) {
424
437
  }
425
438
 
426
439
  /** The keys of a `new SchemaField({ … })`, recursively dotted. */
427
- function nestedKeysOf(expr) {
440
+ function nestedKeysOf(expr, file) {
428
441
  if (!isSchemaField(expr)) return [];
429
442
  const arg = expr.arguments?.[0];
430
443
  if (!arg || !ts.isObjectLiteralExpression(arg)) return [];
431
444
  const out = [];
432
445
  for (const p of arg.properties) {
433
446
  if (!ts.isPropertyAssignment(p)) continue;
434
- const key = propName(p.name);
447
+ const key = fieldName(p.name, file);
435
448
  out.push(key);
436
- for (const child of nestedKeysOf(p.initializer)) {
449
+ for (const child of nestedKeysOf(p.initializer, file)) {
437
450
  out.push(`${key}.${child}`);
438
451
  }
439
452
  }
@@ -445,7 +458,7 @@ function nestedKeysOf(expr) {
445
458
  *
446
459
  * @returns {{own: string[], edges: object[]}}
447
460
  */
448
- function readLiteral(literal) {
461
+ function readLiteral(literal, file) {
449
462
  const own = [];
450
463
  const edges = [];
451
464
  for (const p of literal.properties) {
@@ -455,9 +468,9 @@ function readLiteral(literal) {
455
468
  continue;
456
469
  }
457
470
  if (!ts.isPropertyAssignment(p)) continue;
458
- const key = propName(p.name);
471
+ const key = fieldName(p.name, file);
459
472
  own.push(key);
460
- for (const child of nestedKeysOf(p.initializer)) {
473
+ for (const child of nestedKeysOf(p.initializer, file)) {
461
474
  own.push(`${key}.${child}`);
462
475
  }
463
476
  }
@@ -505,7 +518,7 @@ export function fieldsForClass({ file, className, aliases, cache, rootDir }) {
505
518
 
506
519
  /** Walk a schema literal, following the spreads inside it. */
507
520
  const walkLiteral = (located, literal, into, superName) => {
508
- const { own: fields, edges } = readLiteral(literal);
521
+ const { own: fields, edges } = readLiteral(literal, located.file);
509
522
  into.push(...fields);
510
523
  for (const edge of edges) followEdge(located, edge, superName);
511
524
  };
@@ -516,12 +529,29 @@ export function fieldsForClass({ file, className, aliases, cache, rootDir }) {
516
529
  const key = `local:${located.file}:${edge.name}`;
517
530
  if (seen.has(key)) return;
518
531
  seen.add(key);
519
- // A local spread inside a subtype's own definition is still the
520
- // parent's contribution — a shared `defineXDataSchema()` is where
521
- // the common fields come from — so it lands in `inherited`
522
- // whichever file it is written in.
532
+ // A spread of a schema-building function is the parent's
533
+ // contribution — a shared `defineXDataSchema()` is where the common
534
+ // fields come from — so it lands in `inherited` whichever file it
535
+ // is written in.
523
536
  const lit = literalReturnedBy(located.src, edge.name);
524
- if (lit) walkLiteral(located, lit, inherited, superName);
537
+ if (lit) {
538
+ walkLiteral(located, lit, inherited, superName);
539
+ return;
540
+ }
541
+ // Not declared here, so it was imported. Following it matters more
542
+ // than it looks: the shared base schema is spread by name from the
543
+ // file that exports it, and resolving only same-file functions
544
+ // dropped it **entirely and in silence** — every SoHL subtype lost
545
+ // `shortcode` and `actionDefs`, so content correctly authoring
546
+ // `system.shortcode` was reported as undeclared. A spread that
547
+ // resolves to nothing must not read as a spread of nothing.
548
+ const from = importSourceOf(located.src, edge.name, aliases);
549
+ if (!from) return;
550
+ const importedSrc = parse(from, cache);
551
+ const importedLit = literalReturnedBy(importedSrc, edge.name);
552
+ if (importedLit) {
553
+ walkLiteral({ file: from, src: importedSrc }, importedLit, inherited, superName);
554
+ }
525
555
  return;
526
556
  }
527
557
  const name = edge.kind === "super" ? superName : edge.name;
@@ -563,12 +593,7 @@ export function fieldsForClass({ file, className, aliases, cache, rootDir }) {
563
593
  * @param {string} opts.version - The package version.
564
594
  * @returns {object} The artifact `schema-check.mjs` reads.
565
595
  */
566
- export function buildSchemaArtifact({
567
- rootDir,
568
- registries,
569
- packageId,
570
- version,
571
- }) {
596
+ export function buildSchemaArtifact({ rootDir, registries, packageId, version }) {
572
597
  const aliases = pathAliases(rootDir);
573
598
  const cache = new Map();
574
599
  const documents = {};
@@ -56,10 +56,7 @@ import { expandContentTables } from "./content-tables.mjs";
56
56
  import { buildSiteIndex, wikiContext } from "./site-index.mjs";
57
57
  import { frontmatterWikilinks, resolveWebWikilinks } from "./web-wikilinks.mjs";
58
58
  import { loadForeignManifests, manifestsComplete } from "./kb-manifest.mjs";
59
- import {
60
- formatUnaddressableFinding,
61
- unaddressableForeignPackages,
62
- } from "./foreign-manifests.mjs";
59
+ import { formatUnaddressableFinding, unaddressableForeignPackages } from "./foreign-manifests.mjs";
63
60
  import { deriveBeingInfo, isBeing } from "../sohl/being-info.mjs";
64
61
  import { loadPackConfig } from "./pack-config.mjs";
65
62
  import { searchableFrontmatter } from "./note-package.mjs";
@@ -192,10 +189,7 @@ export function collectContentPages(contentBase, ctx) {
192
189
  // authoring folder, for grouped landings.
193
190
  folder: path.basename(path.dirname(file)),
194
191
  sec,
195
- url:
196
- isReadme ?
197
- `${ctx.mount}${sec}/`
198
- : `${ctx.mount}${sec}/${slug}/`,
192
+ url: isReadme ? `${ctx.mount}${sec}/` : `${ctx.mount}${sec}/${slug}/`,
199
193
  isReadme,
200
194
  });
201
195
  }
@@ -231,10 +225,8 @@ export function collectTreePages(tree, ctx) {
231
225
  const isReadme = base.toLowerCase() === "readme.md";
232
226
  const sec = fm.category ?? tree.section;
233
227
  const h1 = /^#\s+(.+?)\s*$/m.exec(body);
234
- const h1Title =
235
- h1 ? h1[1].replace(/\{@link\s+[^}]*\}/g, "").trim() : null;
236
- const name =
237
- fm.name?.full ?? fm.title ?? h1Title ?? path.basename(base, ".md");
228
+ const h1Title = h1 ? h1[1].replace(/\{@link\s+[^}]*\}/g, "").trim() : null;
229
+ const name = fm.name?.full ?? fm.title ?? h1Title ?? path.basename(base, ".md");
238
230
  const slug = fm.slug ?? slugify(path.basename(base, ".md"));
239
231
  const relNoExt = rel.slice(0, -3).toLowerCase();
240
232
  const dir = path.posix.dirname(relNoExt);
@@ -562,9 +554,7 @@ export function pageDestination(page) {
562
554
  : path.join(page.sec, `${page.slug}.md`);
563
555
  }
564
556
  const rel =
565
- page.isReadme ?
566
- path.posix.join(path.posix.dirname(page.rel), "_index.md")
567
- : page.rel;
557
+ page.isReadme ? path.posix.join(path.posix.dirname(page.rel), "_index.md") : page.rel;
568
558
  return path.join(page.sec, rel);
569
559
  }
570
560
 
@@ -640,10 +630,7 @@ export function renderPages(pages, options) {
640
630
  const data = pageFrontmatter(page, { readmeSections, decorate });
641
631
  const dest = path.join(outRoot, pageDestination(page));
642
632
  fs.mkdirSync(path.dirname(dest), { recursive: true });
643
- fs.writeFileSync(
644
- dest,
645
- matter.stringify(protectCode(body, resolve), data),
646
- );
633
+ fs.writeFileSync(dest, matter.stringify(protectCode(body, resolve), data));
647
634
  byKind[page.kind] = (byKind[page.kind] ?? 0) + 1;
648
635
  }
649
636
 
@@ -676,10 +663,7 @@ export function renderPages(pages, options) {
676
663
  * @param {object} options - `{ sections, landing, sectionTitle }`.
677
664
  * @returns {number} How many landings were written.
678
665
  */
679
- export function writeSectionLandings(
680
- outRoot,
681
- { sections = {}, landing, sectionTitle },
682
- ) {
666
+ export function writeSectionLandings(outRoot, { sections = {}, landing, sectionTitle }) {
683
667
  let written = 0;
684
668
 
685
669
  // The mount's own landing carries a `type` of its own. Hugo's template
@@ -689,10 +673,7 @@ export function writeSectionLandings(
689
673
  // template out of the path where it could be inherited.
690
674
  if (landing) {
691
675
  fs.mkdirSync(outRoot, { recursive: true });
692
- fs.writeFileSync(
693
- path.join(outRoot, "_index.md"),
694
- matter.stringify("", landing),
695
- );
676
+ fs.writeFileSync(path.join(outRoot, "_index.md"), matter.stringify("", landing));
696
677
  written += 1;
697
678
  }
698
679
 
@@ -713,10 +694,7 @@ export function writeSectionLandings(
713
694
  if (!entry.isDirectory()) continue;
714
695
  const index = path.join(outRoot, entry.name, "_index.md");
715
696
  if (fs.existsSync(index)) continue;
716
- fs.writeFileSync(
717
- index,
718
- matter.stringify("", { title: sectionTitle(entry.name) }),
719
- );
697
+ fs.writeFileSync(index, matter.stringify("", { title: sectionTitle(entry.name) }));
720
698
  written += 1;
721
699
  }
722
700
  return written;
@@ -859,19 +837,16 @@ export function buildSite({ config, outRoot } = {}) {
859
837
  const outBase = resolveOutputRoot(resolved.rootDir, site.out);
860
838
  const out =
861
839
  outRoot ? path.resolve(outRoot)
862
- : publishesContent ?
863
- path.join(outBase, scheme.prefix.replace(/\/$/, ""))
840
+ : publishesContent ? path.join(outBase, scheme.prefix.replace(/\/$/, ""))
864
841
  // Homepage-only has no content mount, so the package's root *is*
865
842
  // the output root and `--out` redirects the whole of it.
866
- : outBase;
843
+ : outBase;
867
844
  // The homepage publishes at `/<contentPackage>/`, which is the package's
868
845
  // own root — one level above the content mount, and the same directory in
869
846
  // homepage-only mode.
870
847
  const homeRoot = publishesContent ? outBase : out;
871
848
 
872
- const packages = new Set(
873
- site.packages.length ? site.packages : [resolved.contentPackage],
874
- );
849
+ const packages = new Set(site.packages.length ? site.packages : [resolved.contentPackage]);
875
850
 
876
851
  const ctx = {
877
852
  packages,
@@ -152,10 +152,7 @@ function mergeForeign(index, foreignIndex) {
152
152
  const parts = readCanonicalKey(key);
153
153
  if (!parts) continue;
154
154
  const shortKey = `${parts.type}/${parts.shortcode}`;
155
- if (
156
- short.has(shortKey) &&
157
- short.get(shortKey).package !== value.package
158
- ) {
155
+ if (short.has(shortKey) && short.get(shortKey).package !== value.package) {
159
156
  ambiguous.add(shortKey);
160
157
  } else {
161
158
  short.set(shortKey, value);
@@ -242,10 +239,7 @@ export function buildSiteIndex(entries, { foreignIndex = new Map() } = {}) {
242
239
  // The page's package is the configured one — the site collection
243
240
  // resolves it and records it as `pkg`. Never read out of
244
241
  // frontmatter: `package:` is retired (#56).
245
- index.set(
246
- canonicalKey(e.pkg ?? contentPackage(), type, shortcode),
247
- value,
248
- );
242
+ index.set(canonicalKey(e.pkg ?? contentPackage(), type, shortcode), value);
249
243
  // In Foundry an item and its documentation are two documents, so
250
244
  // `skill/wpnc` and `docskill/wpnc` are two UUIDs (#1362). Here the
251
245
  // item note renders as one page which *is* its documentation, so
@@ -315,13 +309,7 @@ export function buildSiteIndex(entries, { foreignIndex = new Map() } = {}) {
315
309
  */
316
310
  export function wikiContext(
317
311
  built,
318
- {
319
- src,
320
- type = null,
321
- errors,
322
- foreignIndex = new Map(),
323
- manifestsComplete = true,
324
- },
312
+ { src, type = null, errors, foreignIndex = new Map(), manifestsComplete = true },
325
313
  ) {
326
314
  return {
327
315
  index: built.index,
@@ -46,12 +46,7 @@ import { slugify } from "./content-slug.mjs";
46
46
  // Re-exported so a site build keeps one import path for the whole of link
47
47
  // resolution: the same rule that names a page also names an anchor within it.
48
48
  export { slugify };
49
- import {
50
- authoredLabel,
51
- WIKILINK,
52
- isSamePage,
53
- parseWikilink,
54
- } from "./wikilink-syntax.mjs";
49
+ import { authoredLabel, WIKILINK, isSamePage, parseWikilink } from "./wikilink-syntax.mjs";
55
50
 
56
51
  /** KB heading/anchor slug: lowercase, non-alphanumerics to single hyphens. */
57
52
 
@@ -280,9 +275,7 @@ export function resolveWebWikilinks(body, ctx) {
280
275
  // Both separators qualify: `type-shortcode` is the canonical form
281
276
  // (#1398), and a hyphen inside a note *name* ("Grukar-ahk") is not
282
277
  // one, which is why the rule is the packs' own (#1409).
283
- const text =
284
- label ??
285
- (isAddress(target, ctx.contentTypes) ? hit.name : target);
278
+ const text = label ?? (isAddress(target, ctx.contentTypes) ? hit.name : target);
286
279
  // A pack-only package publishes Foundry addresses and no pages
287
280
  // (#1516), so its entries carry no `path` and resolve to no URL.
288
281
  // The address is real — this is not a typo and must not fail the
@@ -295,8 +288,7 @@ export function resolveWebWikilinks(body, ctx) {
295
288
  }
296
289
 
297
290
  const slash = target.indexOf("/");
298
- const prefix =
299
- slash === -1 ? null : target.slice(0, slash).toLowerCase();
291
+ const prefix = slash === -1 ? null : target.slice(0, slash).toLowerCase();
300
292
  // Deliberately *not* extended to the hyphen form, which is also how a
301
293
  // note addresses content in a package this build does not publish
302
294
  // (`Rules/Bestiary.md` → `being-grkrahk`, a real note in the `thalorna`
@@ -307,8 +299,7 @@ export function resolveWebWikilinks(body, ctx) {
307
299
  // which holds the reviewed list of cross-package exceptions — and which,
308
300
  // unlike this build, runs as part of `npm run lint` on every change.
309
301
  const badQualified =
310
- prefix !== null &&
311
- (ctx.sections.has(prefix) || ctx.contentTypes.has(prefix));
302
+ prefix !== null && (ctx.sections.has(prefix) || ctx.contentTypes.has(prefix));
312
303
  // The hyphen form is the canonical address (#1398) and is what the
313
304
  // authored content writes. It could not be guarded while some packages
314
305
  // were invisible here: `Rules/Bestiary.md` addresses `being-grkrahk`,