@heroiclands/package-build 22.17.0 → 22.17.2

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 (37) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/bin/content-commands.mjs +114 -37
  3. package/config.mjs +54 -3
  4. package/docs/api.md +4 -14
  5. package/docs/commands.md +89 -52
  6. package/docs/configuration.md +49 -22
  7. package/docs/reference/format-details.md +27 -45
  8. package/docs/reference/note-types.md +1 -1
  9. package/engine/actor-compiler.mjs +5 -5
  10. package/engine/address-diff.mjs +32 -279
  11. package/engine/art-fields.mjs +8 -5
  12. package/engine/content-format.yaml +4 -4
  13. package/engine/content-index.mjs +28 -3
  14. package/engine/content-lint.mjs +3 -182
  15. package/engine/frontmatter-lint.mjs +112 -59
  16. package/engine/helpers.mjs +15 -3
  17. package/engine/index.mjs +0 -3
  18. package/engine/map-graphviz.mjs +4 -1
  19. package/engine/note-frontmatter.mjs +1 -0
  20. package/engine/note-vocabulary.mjs +1 -13
  21. package/engine/readability-lint.mjs +196 -18
  22. package/engine/subtype-registry.mjs +2 -6
  23. package/engine/systems.mjs +11 -13
  24. package/package.json +5 -1
  25. package/types/config.d.mts +14 -2
  26. package/types/engine/actor-compiler.d.mts +5 -5
  27. package/types/engine/address-diff.d.mts +18 -107
  28. package/types/engine/art-fields.d.mts +4 -5
  29. package/types/engine/frontmatter-lint.d.mts +14 -0
  30. package/types/engine/helpers.d.mts +12 -1
  31. package/types/engine/index.d.mts +0 -1
  32. package/types/engine/note-vocabulary.d.mts +0 -9
  33. package/types/engine/readability-lint.d.mts +11 -5
  34. package/types/engine/subtype-registry.d.mts +2 -6
  35. package/types/engine/systems.d.mts +6 -6
  36. package/engine/note-renames.mjs +0 -134
  37. package/types/engine/note-renames.d.mts +0 -102
@@ -14,7 +14,8 @@ export function renderFoundryMarkdown(body: any, captions: any, footnoteNumbers:
14
14
  * @param {string} filePath - Markdown file to read.
15
15
  * @param {{addressContext?: object}} [opts] - Address defaults for decoded fields.
16
16
  * @returns {{frontmatter: object|null, body: string, description: string,
17
- * bodyLine?: number, bodyColumn?: number}} Parsed note and body position.
17
+ * bodyLine?: number, bodyColumn?: number, parseError?: {message: string,
18
+ * line?: number, column?: number}}} Parsed note and body position.
18
19
  */
19
20
  export function parseMarkdownFile(filePath: string, { addressContext }?: {
20
21
  addressContext?: object;
@@ -24,6 +25,11 @@ export function parseMarkdownFile(filePath: string, { addressContext }?: {
24
25
  description: string;
25
26
  bodyLine?: number;
26
27
  bodyColumn?: number;
28
+ parseError?: {
29
+ message: string;
30
+ line?: number;
31
+ column?: number;
32
+ };
27
33
  };
28
34
  /**
29
35
  * Refuse a corpus read whose scope its caller did not state.
@@ -98,6 +104,11 @@ export function walkMarkdownTree(rootDir: string, { skipDirectories, addressCont
98
104
  description: string;
99
105
  bodyLine?: number;
100
106
  bodyColumn?: number;
107
+ parseError?: {
108
+ message: string;
109
+ line?: number;
110
+ column?: number;
111
+ };
101
112
  }, void, unknown>;
102
113
  /**
103
114
  * The template priority a note states, for a system that treats an unstated one
@@ -47,7 +47,6 @@ export * as infobox from "./infobox.mjs";
47
47
  export * as infoboxRegistry from "./infobox-registry.mjs";
48
48
  export * as infoboxRender from "./infobox-render.mjs";
49
49
  export * as noteIds from "./note-ids.mjs";
50
- export * as noteRenames from "./note-renames.mjs";
51
50
  export * as metadataIndex from "./metadata-index.mjs";
52
51
  export * as foundryEntries from "./foundry-entries.mjs";
53
52
  export * as contentIndex from "./content-index.mjs";
@@ -233,15 +233,6 @@ export const DECLARED_TAGS: Readonly<{
233
233
  types: string[];
234
234
  tags: readonly string[];
235
235
  }>;
236
- /**
237
- * What kind of being this is — a person, or one of the beasts and made
238
- * things. A being is one or the other, so the group is a slot.
239
- */
240
- beingKind: Readonly<{
241
- types: string[];
242
- exclusive: "kind";
243
- tags: readonly string[];
244
- }>;
245
236
  /** A note's working state, which any note may carry. */
246
237
  state: Readonly<{
247
238
  types: null;
@@ -1,11 +1,7 @@
1
1
  /** Files in a content tree, excluding configured directory names. */
2
2
  export function proseFiles(root: any, skipDirectories?: any[]): any[];
3
3
  /** Source-aligned findings for one Markdown note. */
4
- export function analyzeProse(file: any, source: any, { age, threshold, minWords }?: {
5
- age?: number | undefined;
6
- threshold?: number | undefined;
7
- minWords?: number | undefined;
8
- }): Promise<{
4
+ export function analyzeProse(file: any, source: any, options?: {}): Promise<{
9
5
  file: any;
10
6
  line: any;
11
7
  column: any;
@@ -20,3 +16,13 @@ export function lintProse(root: any, options: any, skipDirectories?: any[]): Pro
20
16
  severity: string;
21
17
  message: string;
22
18
  }[]>;
19
+ /** Score the complete prose paragraphs in one note. */
20
+ export function scoreProse(file: any, source: any, { minWords, bands }?: {
21
+ minWords?: number | undefined;
22
+ bands?: {} | undefined;
23
+ }): Promise<any>;
24
+ /** Score a note or content tree and pool counts for its aggregate. */
25
+ export function scoreProseTree(root: any, options: any, skipDirectories?: any[]): Promise<{
26
+ notes: any[];
27
+ summary: any;
28
+ }>;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The map one system ships, by its id.
3
3
  *
4
- * @param {string|undefined} system - The system id (`"sohl"`, `"hm3"`).
4
+ * @param {string|undefined} system - The system id.
5
5
  * @returns {import("./document-subtypes.mjs").DocumentSubtypeMap|undefined} Its
6
6
  * map, or `undefined` where this toolchain ships none for it.
7
7
  */
@@ -29,10 +29,6 @@ export function schemaSubtypeOf(system: string | undefined, type: string): strin
29
29
  /**
30
30
  * The note-type → document-subtype maps this toolchain ships.
31
31
  *
32
- * Two, since `hm3/` landed — and it joined this list rather than the
33
- * claim table growing a second copy of the same fact, which is what the list
34
- * was for.
35
- *
36
32
  * The union is what makes the vocabulary wider than any one repository's
37
33
  * configuration: `armorlocation` is a real content type because HM3 maps it,
38
34
  * however a given repository is configured, so a tree full of them is a
@@ -71,7 +67,7 @@ export const KNOWN_DOCUMENT_SUBTYPE_MAPS: readonly import("./document-subtypes.m
71
67
  */
72
68
  export const DEFAULT_DOCUMENT_SUBTYPES: import("./document-subtypes.mjs").DocumentSubtypeMap;
73
69
  /**
74
- * Every system this toolchain ships a map for, by id.
70
+ * Every system identifier with declared system-specific document types.
75
71
  *
76
72
  * Derived from {@link KNOWN_DOCUMENT_SUBTYPE_MAPS}, so registering a map is the
77
73
  * whole of adding a system to every reader that asks "which systems are there?"
@@ -28,8 +28,8 @@ export function isSystemSegment(value: unknown): boolean;
28
28
  *
29
29
  * The nullish case is called out by name because it is the likely mistake and
30
30
  * the least legible failure: a YAML `null`, a `~`, or a key written with no
31
- * value at all reaches here as `undefined`, and "expected one of sohl, hm3,
32
- * none" would leave a reader hunting for the value they cannot see.
31
+ * value at all reaches here as `undefined`, and a list of accepted values
32
+ * would leave a reader hunting for the value they cannot see.
33
33
  *
34
34
  * @param {unknown} value - The offending value.
35
35
  * @param {string} [where] - What carried it, for the message — a note path, a
@@ -83,11 +83,11 @@ export const NO_SYSTEM: string;
83
83
  /** The Address segment for a note's readable content. */
84
84
  export const NOTE_SYSTEM: "note";
85
85
  /**
86
- * Every game system this toolchain compiles for.
86
+ * Every game system identifier this toolchain recognizes.
87
87
  *
88
- * The registry, and the only place the list is written down. Adding a system
89
- * is an edit to this set plus the map and compilers that make it real — a data
90
- * change, not a search for every place a name was spelled.
88
+ * The registry, and the only place the list is written down. A recognized id
89
+ * can have no system-specific document types; adding one of those requires a
90
+ * subtype map and compiler as well.
91
91
  *
92
92
  * `none` is **not** a member: it marks the absence of a system, and a caller
93
93
  * asking "is this a system" and a caller asking "is this a well-formed
@@ -1,134 +0,0 @@
1
- /*
2
- * This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
3
- * Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
4
- *
5
- * This work is licensed under the GNU General Public License v3.0 (GPLv3).
6
- * You may copy, modify, and distribute it under the terms of that license.
7
- *
8
- * For full terms, see the LICENSE.md file in the project root or visit:
9
- * https://www.gnu.org/licenses/gpl-3.0.html
10
- *
11
- * SPDX-License-Identifier: GPL-3.0-or-later
12
- */
13
-
14
- /**
15
- * The shortcodes a note declares it used to be published under.
16
- *
17
- * A package's `(type, shortcode)` addresses are a published interface, and
18
- * `addresses diff` reports what a build stopped publishing — telling a
19
- * **rename** from a **withdrawal** by matching document ids across two
20
- * releases. The property that rested on is gone: an id is derived from
21
- * the canonical address, which carries the shortcode, so renaming a shortcode
22
- * moves the id too: both sides of the join move together, the match finds
23
- * nothing, and a rename is reported as a withdrawal with no successor named.
24
- *
25
- * **The remaining lever needed foresight, which is the wrong thing to ask for.**
26
- * An authored `id:` still wins, and pinning one does keep a document's identity
27
- * across a rename — but it has to be written *before* the rename, by an author
28
- * who does not yet know they will make one. An author who has just renamed a
29
- * shortcode knows exactly what the old one was, and that is the only moment
30
- * anyone does.
31
- *
32
- * So a note states it:
33
- *
34
- * ```yaml
35
- * type: weapongear
36
- * shortcode: Taburi
37
- * renamedFrom: Tabri
38
- * ```
39
- *
40
- * **A declaration is an assertion, not a match, and the diagnostic says which
41
- * it had.** That distinction is the whole ethos of `address-diff.mjs`: it
42
- * refuses to guess a successor from a similar-looking string, because a wrong
43
- * one sends the reader to the wrong fix. An author's declaration is neither a
44
- * guess nor an identity match — it is testimony from the one party that knows —
45
- * so it is used, attributed, and never silently blended with an id match.
46
- *
47
- * **It is a list, because renames chain.** The diff is release-to-release and a
48
- * shortcode may be renamed more than once between two releases; a baseline
49
- * several releases back published a name two renames ago. A single value would
50
- * report that as a withdrawal again, which is the bug this closes.
51
- *
52
- * **It is transient.** Once every baseline a build is compared against post-dates
53
- * the rename, the declaration has nothing left to say and may be deleted. That
54
- * is the difference from an `id:` pin, which is permanent, opaque, and a second
55
- * identity for a thing that already has one.
56
- *
57
- * **It is one key per note, at the top level, however many systems the note
58
- * compiles into.** A shortcode is the note's, not a system block's: a note
59
- * carrying `sohl:` and `hm3:` blocks compiles into two documents that share one
60
- * shortcode, so a rename moves both and one declaration covers both.
61
- *
62
- * This module reads the key and says nothing about whether the values are
63
- * *sound* — that a value is a well-formed shortcode, is not the note's own, and
64
- * does not name an address some live note still publishes are cross-note
65
- * questions, and `engine/content-lint.mjs` owns them. Composing a value into an
66
- * address key is likewise not here: the diff defines that space and spells the
67
- * key once, in `engine/address-diff.mjs`.
68
- *
69
- * @module
70
- */
71
-
72
- /**
73
- * The `renamedFrom:` entries a note authors, exactly as authored.
74
- *
75
- * Scalar or list, because one predecessor is the overwhelmingly common case and
76
- * requiring `- ` on it would be friction with nothing behind it; both normalize
77
- * here so no reader has to ask which form it got.
78
- *
79
- * Entries are returned **unvalidated** — a number, a nested list, a blank
80
- * string all come back as they were written. The lint needs to see them to
81
- * report them, and a reader that quietly dropped them would report a correct
82
- * tree clean while a typo silently did nothing.
83
- *
84
- * @param {object|null|undefined} fm - Parsed frontmatter.
85
- * @returns {readonly unknown[]} The authored entries, in authored order; empty
86
- * when the note declares none.
87
- */
88
- export function renamedFromEntries(fm) {
89
- if (!fm || typeof fm !== "object") return [];
90
- const raw = /** @type {{renamedFrom?: unknown}} */ (fm).renamedFrom;
91
- if (raw == null) return [];
92
- return Array.isArray(raw) ? raw : [raw];
93
- }
94
-
95
- /**
96
- * The well-formed shortcodes among a note's `renamedFrom:` entries.
97
- *
98
- * Trimmed, de-duplicated, and in authored order. Anything that is not a
99
- * non-blank string is skipped rather than coerced: it is reported by the lint,
100
- * and a diff that guessed at what a number meant would claim a rename nobody
101
- * declared.
102
- *
103
- * De-duplicating here rather than leaving it to callers is not tidiness — the
104
- * predecessor index is a map, so a repeated entry would otherwise be indexed
105
- * twice and the second write would look like a conflicting claim.
106
- *
107
- * @param {object|null|undefined} fm - Parsed frontmatter.
108
- * @returns {string[]} The declared predecessor shortcodes.
109
- */
110
- export function renamedFrom(fm) {
111
- const seen = new Set();
112
- for (const entry of renamedFromEntries(fm)) {
113
- if (typeof entry !== "string") continue;
114
- const trimmed = entry.trim();
115
- if (trimmed) seen.add(trimmed);
116
- }
117
- return [...seen];
118
- }
119
-
120
- /**
121
- * Whether a note declares the key at all, however malformed its value.
122
- *
123
- * Separate from {@link renamedFrom} returning nothing, because the two mean
124
- * opposite things to a lint: a note declaring no key is silent and correct,
125
- * while one declaring `renamedFrom: []` — or a single blank string — has said
126
- * something that does nothing, which is worth a word.
127
- *
128
- * @param {object|null|undefined} fm - Parsed frontmatter.
129
- * @returns {boolean} `true` when the key is present and not null.
130
- */
131
- export function declaresRenamedFrom(fm) {
132
- if (!fm || typeof fm !== "object") return false;
133
- return /** @type {{renamedFrom?: unknown}} */ (fm).renamedFrom != null;
134
- }
@@ -1,102 +0,0 @@
1
- /**
2
- * The shortcodes a note declares it used to be published under.
3
- *
4
- * A package's `(type, shortcode)` addresses are a published interface, and
5
- * `addresses diff` reports what a build stopped publishing — telling a
6
- * **rename** from a **withdrawal** by matching document ids across two
7
- * releases. The property that rested on is gone: an id is derived from
8
- * the canonical address, which carries the shortcode, so renaming a shortcode
9
- * moves the id too: both sides of the join move together, the match finds
10
- * nothing, and a rename is reported as a withdrawal with no successor named.
11
- *
12
- * **The remaining lever needed foresight, which is the wrong thing to ask for.**
13
- * An authored `id:` still wins, and pinning one does keep a document's identity
14
- * across a rename — but it has to be written *before* the rename, by an author
15
- * who does not yet know they will make one. An author who has just renamed a
16
- * shortcode knows exactly what the old one was, and that is the only moment
17
- * anyone does.
18
- *
19
- * So a note states it:
20
- *
21
- * ```yaml
22
- * type: weapongear
23
- * shortcode: Taburi
24
- * renamedFrom: Tabri
25
- * ```
26
- *
27
- * **A declaration is an assertion, not a match, and the diagnostic says which
28
- * it had.** That distinction is the whole ethos of `address-diff.mjs`: it
29
- * refuses to guess a successor from a similar-looking string, because a wrong
30
- * one sends the reader to the wrong fix. An author's declaration is neither a
31
- * guess nor an identity match — it is testimony from the one party that knows —
32
- * so it is used, attributed, and never silently blended with an id match.
33
- *
34
- * **It is a list, because renames chain.** The diff is release-to-release and a
35
- * shortcode may be renamed more than once between two releases; a baseline
36
- * several releases back published a name two renames ago. A single value would
37
- * report that as a withdrawal again, which is the bug this closes.
38
- *
39
- * **It is transient.** Once every baseline a build is compared against post-dates
40
- * the rename, the declaration has nothing left to say and may be deleted. That
41
- * is the difference from an `id:` pin, which is permanent, opaque, and a second
42
- * identity for a thing that already has one.
43
- *
44
- * **It is one key per note, at the top level, however many systems the note
45
- * compiles into.** A shortcode is the note's, not a system block's: a note
46
- * carrying `sohl:` and `hm3:` blocks compiles into two documents that share one
47
- * shortcode, so a rename moves both and one declaration covers both.
48
- *
49
- * This module reads the key and says nothing about whether the values are
50
- * *sound* — that a value is a well-formed shortcode, is not the note's own, and
51
- * does not name an address some live note still publishes are cross-note
52
- * questions, and `engine/content-lint.mjs` owns them. Composing a value into an
53
- * address key is likewise not here: the diff defines that space and spells the
54
- * key once, in `engine/address-diff.mjs`.
55
- *
56
- * @module
57
- */
58
- /**
59
- * The `renamedFrom:` entries a note authors, exactly as authored.
60
- *
61
- * Scalar or list, because one predecessor is the overwhelmingly common case and
62
- * requiring `- ` on it would be friction with nothing behind it; both normalize
63
- * here so no reader has to ask which form it got.
64
- *
65
- * Entries are returned **unvalidated** — a number, a nested list, a blank
66
- * string all come back as they were written. The lint needs to see them to
67
- * report them, and a reader that quietly dropped them would report a correct
68
- * tree clean while a typo silently did nothing.
69
- *
70
- * @param {object|null|undefined} fm - Parsed frontmatter.
71
- * @returns {readonly unknown[]} The authored entries, in authored order; empty
72
- * when the note declares none.
73
- */
74
- export function renamedFromEntries(fm: object | null | undefined): readonly unknown[];
75
- /**
76
- * The well-formed shortcodes among a note's `renamedFrom:` entries.
77
- *
78
- * Trimmed, de-duplicated, and in authored order. Anything that is not a
79
- * non-blank string is skipped rather than coerced: it is reported by the lint,
80
- * and a diff that guessed at what a number meant would claim a rename nobody
81
- * declared.
82
- *
83
- * De-duplicating here rather than leaving it to callers is not tidiness — the
84
- * predecessor index is a map, so a repeated entry would otherwise be indexed
85
- * twice and the second write would look like a conflicting claim.
86
- *
87
- * @param {object|null|undefined} fm - Parsed frontmatter.
88
- * @returns {string[]} The declared predecessor shortcodes.
89
- */
90
- export function renamedFrom(fm: object | null | undefined): string[];
91
- /**
92
- * Whether a note declares the key at all, however malformed its value.
93
- *
94
- * Separate from {@link renamedFrom} returning nothing, because the two mean
95
- * opposite things to a lint: a note declaring no key is silent and correct,
96
- * while one declaring `renamedFrom: []` — or a single blank string — has said
97
- * something that does nothing, which is worth a word.
98
- *
99
- * @param {object|null|undefined} fm - Parsed frontmatter.
100
- * @returns {boolean} `true` when the key is present and not null.
101
- */
102
- export function declaresRenamedFrom(fm: object | null | undefined): boolean;