@heroiclands/package-build 23.7.0 → 23.8.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 (71) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +32 -0
  3. package/bin/content-commands.mjs +20 -6
  4. package/ci/check-banners.mjs +37 -19
  5. package/container.mjs +48 -10
  6. package/docs/api.md +87 -43
  7. package/docs/authoring/frontmatter.md +30 -1
  8. package/docs/authoring/links-and-markup.md +229 -36
  9. package/docs/commands.md +12 -2
  10. package/docs/content-format.md +24 -1
  11. package/docs/design.md +31 -0
  12. package/docs/guides/e2e.md +37 -1
  13. package/docs/project-setup.md +30 -15
  14. package/docs/reference/format-details.md +418 -220
  15. package/docs/reference/note-types.md +90 -31
  16. package/e2e.mjs +89 -2
  17. package/engine/base-compiler.mjs +11 -18
  18. package/engine/compile-corpus.mjs +2 -1
  19. package/engine/content-alerts.mjs +4 -2
  20. package/engine/content-blocks.mjs +18 -36
  21. package/engine/content-format.yaml +3 -0
  22. package/engine/content-index.mjs +35 -22
  23. package/engine/content-poetry.mjs +9 -0
  24. package/engine/diagnostics.mjs +24 -11
  25. package/engine/frontmatter-lint.mjs +1 -1
  26. package/engine/generate.mjs +12 -22
  27. package/engine/helpers.mjs +8 -6
  28. package/engine/infobox.mjs +3 -0
  29. package/engine/journals.mjs +11 -1
  30. package/engine/language-data.mjs +236 -1
  31. package/engine/language-rows.mjs +235 -3
  32. package/engine/language-terms.mjs +524 -9
  33. package/engine/language-words.mjs +75 -0
  34. package/engine/markdownlint-run.mjs +12 -2
  35. package/engine/markup-validation.mjs +34 -0
  36. package/engine/note-addresses.mjs +1 -0
  37. package/engine/note-claims.mjs +19 -10
  38. package/engine/note-package.mjs +3 -5
  39. package/engine/note-vocabulary.mjs +29 -1
  40. package/engine/pdf-build.mjs +8 -1
  41. package/engine/prose-lint.mjs +12 -6
  42. package/engine/related-pages.mjs +22 -3
  43. package/engine/see-also.mjs +150 -0
  44. package/engine/site-build.mjs +5 -1
  45. package/engine/site-preview.mjs +4 -2
  46. package/engine/sql-tables.mjs +37 -12
  47. package/engine/standings.mjs +14 -12
  48. package/engine/word-rows.mjs +8 -3
  49. package/engine/word-terms.mjs +60 -2
  50. package/hugo-theme/layouts/partials/related.html +25 -8
  51. package/package.json +2 -1
  52. package/types/container.d.mts +4 -3
  53. package/types/e2e.d.mts +28 -0
  54. package/types/engine/base-compiler.d.mts +2 -2
  55. package/types/engine/compile-corpus.d.mts +2 -1
  56. package/types/engine/content-index.d.mts +20 -7
  57. package/types/engine/diagnostics.d.mts +7 -6
  58. package/types/engine/helpers.d.mts +7 -4
  59. package/types/engine/language-rows.d.mts +89 -3
  60. package/types/engine/language-terms.d.mts +106 -4
  61. package/types/engine/markdownlint-run.d.mts +3 -1
  62. package/types/engine/markup-validation.d.mts +14 -0
  63. package/types/engine/note-addresses.d.mts +10 -0
  64. package/types/engine/note-claims.d.mts +7 -1
  65. package/types/engine/note-package.d.mts +3 -1
  66. package/types/engine/note-vocabulary.d.mts +1 -0
  67. package/types/engine/related-pages.d.mts +10 -1
  68. package/types/engine/see-also.d.mts +55 -0
  69. package/types/engine/sql-tables.d.mts +7 -3
  70. package/types/engine/word-rows.d.mts +2 -1
  71. package/types/engine/word-terms.d.mts +11 -0
@@ -150,12 +150,14 @@ export function positionInFrontmatter(raw: string, key: string, value?: string,
150
150
  * {@link positionInFrontmatter}.
151
151
  * @param {boolean} [options.topLevel=false] - Require the key at column 1, so
152
152
  * an identically named nested key cannot answer for it.
153
+ * @param {string} [options.raw] - Source text already read by the caller.
153
154
  * @returns {{line?: number, column?: number}|undefined} Spreadable position
154
155
  * fields, dropped rather than guessed when the file cannot be read or the key
155
156
  * cannot be found — as `formatDiagnostic` requires.
156
157
  */
157
- export function locateFrontmatterKey(absPath: string | undefined, key: string, value?: string, { topLevel }?: {
158
+ export function locateFrontmatterKey(absPath: string | undefined, key: string, value?: string, { topLevel, raw }?: {
158
159
  topLevel?: boolean | undefined;
160
+ raw?: string | undefined;
159
161
  }): {
160
162
  line?: number;
161
163
  column?: number;
@@ -195,11 +197,10 @@ export function positionOfLiteral(text: string, needle: string, occurrence?: num
195
197
  * first occurrence is routinely not the one the finding is about — which is the
196
198
  * one thing the located form exists to prevent.
197
199
  *
198
- * A path resolves the exact node instead. The document is re-parsed here rather
199
- * than threaded from the loader because the loader returns plain data: `yaml`
200
- * discards ranges once a document is materialised, and carrying a parallel
201
- * position tree through configuration resolution would be a second
202
- * representation of the same file to keep in step.
200
+ * A path resolves the exact node. The most recently located text retains its
201
+ * document and line counter, so several findings in one note share a parse.
202
+ * A different text replaces that entry, including an edited copy of the same
203
+ * file. The loader returns plain data without source ranges.
203
204
  *
204
205
  * Every failure — unparseable text, an `.mjs` configuration, a path that
205
206
  * resolves to nothing — yields `{}`, so a caller spreads the result and the
@@ -19,17 +19,18 @@ export function renderFoundryMarkdown(body: string, figures?: Array<{
19
19
  /**
20
20
  * Parses a markdown file with YAML frontmatter.
21
21
  *
22
- * Returns `{ frontmatter, body, description, bodyLine, bodyColumn }` where
22
+ * Returns `{ frontmatter, body, description, bodyLine, bodyColumn, raw }` where
23
23
  * `body` is the trimmed raw markdown after the frontmatter block, and
24
- * `description` is `body` rendered to HTML. `bodyLine` / `bodyColumn` are the
24
+ * `description` is `body` rendered to HTML. The full source is retained as
25
+ * `raw` for diagnostic locations. `bodyLine` / `bodyColumn` are the
25
26
  * 1-based **file** position of the body's first character, which is what turns
26
27
  * an offset within `body` into a position a diagnostic can name — see
27
28
  * {@link positionInBody}. If the file has no frontmatter block, returns
28
- * `{ frontmatter: null, body: "", description: "" }` with a warn log, and no
29
+ * `{ frontmatter: null, body: "", description: "", raw }` and no
29
30
  * position: there is no body to have one.
30
31
  * @param {string} filePath - Markdown file to read.
31
32
  * @param {{addressContext?: object}} [opts] - Address defaults for decoded fields.
32
- * @returns {{frontmatter: object|null, body: string, description: string,
33
+ * @returns {{frontmatter: object|null, body: string, description: string, raw: string,
33
34
  * bodyLine?: number, bodyColumn?: number, parseError?: {message: string,
34
35
  * line?: number, column?: number}}} Parsed note and body position.
35
36
  */
@@ -39,6 +40,7 @@ export function parseMarkdownFile(filePath: string, { addressContext }?: {
39
40
  frontmatter: object | null;
40
41
  body: string;
41
42
  description: string;
43
+ raw: string;
42
44
  bodyLine?: number;
43
45
  bodyColumn?: number;
44
46
  parseError?: {
@@ -118,6 +120,7 @@ export function walkMarkdownTree(rootDir: string, { skipDirectories, addressCont
118
120
  frontmatter: object | null;
119
121
  body: string;
120
122
  description: string;
123
+ raw: string;
121
124
  bodyLine?: number;
122
125
  bodyColumn?: number;
123
126
  parseError?: {
@@ -63,6 +63,73 @@ export function edgeRows(records: Iterable<Record<string, any>>): object[];
63
63
  * @returns {object[]}
64
64
  */
65
65
  export function clusterRows(records: Iterable<Record<string, any>>): object[];
66
+ /**
67
+ * The `cluster_groups` rows: one per `data.sounds.clusters.groups` entry,
68
+ * `clusters` a list.
69
+ *
70
+ * @param {Iterable<Record<string, any>>} records - Index records.
71
+ * @returns {object[]}
72
+ */
73
+ export function clusterGroupRows(records: Iterable<Record<string, any>>): object[];
74
+ /**
75
+ * The `mutations` rows: one per `data.sounds.mutations` entry.
76
+ *
77
+ * @param {Iterable<Record<string, any>>} records - Index records.
78
+ * @returns {object[]}
79
+ */
80
+ export function mutationRows(records: Iterable<Record<string, any>>): object[];
81
+ /**
82
+ * The `older` rows: one per `data.older` entry.
83
+ *
84
+ * @param {Iterable<Record<string, any>>} records - Index records.
85
+ * @returns {object[]}
86
+ */
87
+ export function olderRows(records: Iterable<Record<string, any>>): object[];
88
+ /**
89
+ * The `negation` rows: one per language stating `data.negation`.
90
+ *
91
+ * @param {Iterable<Record<string, any>>} records - Index records.
92
+ * @returns {object[]}
93
+ */
94
+ export function negationRows(records: Iterable<Record<string, any>>): object[];
95
+ /**
96
+ * The `name_shapes` rows: one per `data.naming.shapes` entry, each range as
97
+ * its two bounds.
98
+ *
99
+ * @param {Iterable<Record<string, any>>} records - Index records.
100
+ * @returns {object[]}
101
+ */
102
+ export function nameShapeRows(records: Iterable<Record<string, any>>): object[];
103
+ /**
104
+ * The `name_edges` rows: one per `data.naming.edges` entry, `sounds` a list,
105
+ * `vowel` `false` and `of` `name` where unstated.
106
+ *
107
+ * @param {Iterable<Record<string, any>>} records - Index records.
108
+ * @returns {object[]}
109
+ */
110
+ export function nameEdgeRows(records: Iterable<Record<string, any>>): object[];
111
+ /**
112
+ * The `name_draws` rows: one per `data.naming.draws` entry.
113
+ *
114
+ * @param {Iterable<Record<string, any>>} records - Index records.
115
+ * @returns {object[]}
116
+ */
117
+ export function nameDrawRows(records: Iterable<Record<string, any>>): object[];
118
+ /**
119
+ * The `held_from` rows: one per `data.naming.heldFrom` entry.
120
+ *
121
+ * @param {Iterable<Record<string, any>>} records - Index records.
122
+ * @returns {object[]}
123
+ */
124
+ export function heldFromRows(records: Iterable<Record<string, any>>): object[];
125
+ /**
126
+ * The `near_names` rows: one per worked example of `data.naming.nearName`,
127
+ * each with the language's `min_syllables`.
128
+ *
129
+ * @param {Iterable<Record<string, any>>} records - Index records.
130
+ * @returns {object[]}
131
+ */
132
+ export function nearNameRows(records: Iterable<Record<string, any>>): object[];
66
133
  /**
67
134
  * The `read_past` rows: one per `data.sounds.readPast` entry.
68
135
  *
@@ -72,8 +139,8 @@ export function clusterRows(records: Iterable<Record<string, any>>): object[];
72
139
  export function readPastRows(records: Iterable<Record<string, any>>): object[];
73
140
  /**
74
141
  * The `sound_changes` rows: one per step of each `data.soundChanges` set, with
75
- * the set's `position`, `name`, `from_language` and `to_language`, and the
76
- * step's `step_position` within it.
142
+ * the set's `position`, `name`, `from_language`, `to_language` and `takes`,
143
+ * and the step's `step_position` within it.
77
144
  *
78
145
  * @param {Iterable<Record<string, any>>} records - Index records.
79
146
  * @returns {object[]}
@@ -118,7 +185,8 @@ export function finalRows(records: Iterable<Record<string, any>>): object[];
118
185
  /**
119
186
  * The `syllables` rows: one per language stating `data.sounds.syllables`, a
120
187
  * list rule always a list (`any` a list of one), the harmony's sets each a
121
- * column of its own.
188
+ * column of its own, its partners a list of `{ vowel, partner }` in the order
189
+ * stated.
122
190
  *
123
191
  * @param {Iterable<Record<string, any>>} records - Index records.
124
192
  * @returns {object[]}
@@ -182,6 +250,24 @@ export const LETTER_COLUMNS: readonly string[];
182
250
  export const EDGE_COLUMNS: readonly string[];
183
251
  /** Every column of the `clusters` relation, in order. */
184
252
  export const CLUSTER_COLUMNS: readonly string[];
253
+ /** Every column of the `cluster_groups` relation, in order. */
254
+ export const CLUSTER_GROUP_COLUMNS: readonly string[];
255
+ /** Every column of the `mutations` relation, in order. */
256
+ export const MUTATION_COLUMNS: readonly string[];
257
+ /** Every column of the `older` relation, in order. */
258
+ export const OLDER_COLUMNS: readonly string[];
259
+ /** Every column of the `name_shapes` relation, in order. */
260
+ export const NAME_SHAPE_COLUMNS: readonly string[];
261
+ /** Every column of the `name_edges` relation, in order. */
262
+ export const NAME_EDGE_COLUMNS: readonly string[];
263
+ /** Every column of the `name_draws` relation, in order. */
264
+ export const NAME_DRAW_COLUMNS: readonly string[];
265
+ /** Every column of the `held_from` relation, in order. */
266
+ export const HELD_FROM_COLUMNS: readonly string[];
267
+ /** Every column of the `near_names` relation, in order. */
268
+ export const NEAR_NAME_COLUMNS: readonly string[];
269
+ /** Every column of the `negation` relation, in order: one row per language. */
270
+ export const NEGATION_COLUMNS: readonly string[];
185
271
  /** Every column of the `stress` relation, in order. */
186
272
  export const STRESS_COLUMNS: readonly string[];
187
273
  /** Every column of the `finals` relation, in order. */
@@ -2,13 +2,15 @@
2
2
  * The keys a language note states beside its words: the register of the names
3
3
  * the setting gives in the language's reach, the tags the register uses, the
4
4
  * forms it does not write, the notes the register covers, the letters and sound
5
- * rules, the sound changes, the name elements, the roots and the cognates.
5
+ * rules, the sound changes, the name elements, the roots, the cognates, the
6
+ * forms older than the rules and the negative particle.
6
7
  *
7
8
  * A leaf module, importing nothing: the note vocabulary reads these
8
9
  * declarations as it loads, and `language-data.mjs` checks what the values
9
10
  * mean. Every closed list here — the edges, element positions, register
10
- * kinds, stress rules, joins, marks, onsets, harmony sets and calling-name
11
- * uses — holds the labels the setting's own tables use, one word for each.
11
+ * kinds, stress rules, joins, marks, onsets, harmony sets, cluster uses and
12
+ * calling-name uses — holds the labels the setting's own tables use, one word
13
+ * for each.
12
14
  *
13
15
  * @module
14
16
  */
@@ -48,8 +50,30 @@ export const ANY_SOUND: "any";
48
50
  * never begins or ends a name.
49
51
  */
50
52
  export const ELEMENT_POSITIONS: readonly string[];
51
- /** What a register name is, where a language sorts its names by kind. */
53
+ /**
54
+ * What a register name is, where a language sorts its names by kind, and the
55
+ * kind of name an element builds.
56
+ */
52
57
  export const REGISTER_KINDS: readonly string[];
58
+ /**
59
+ * The class of name a naming shape or edge holds for: every name, a given or a
60
+ * clan name, or a name by how it is built — `bound` a stem and an ending,
61
+ * `compound` elements alone.
62
+ */
63
+ export const NAME_CLASSES: readonly string[];
64
+ /** The class of name a naming edge holds for where it states none. */
65
+ export const ANY_NAME: "name";
66
+ /**
67
+ * What a place name takes first from what it is held from: a name-stem, a
68
+ * god's name, a founder's given name, or one of the listed elements.
69
+ */
70
+ export const HELD_TAKES: readonly string[];
71
+ /** The `takes` values that are a name a place name shapes. */
72
+ export const HELD_NAMES: readonly string[];
73
+ /** How a name is clipped where a place name takes it. */
74
+ export const HELD_CLIPS: readonly string[];
75
+ /** How often a group of clusters stands inside a word. */
76
+ export const CLUSTER_USES: readonly string[];
53
77
  /** Where a language's stress falls. */
54
78
  export const STRESS_RULES: readonly string[];
55
79
  /** Which sounds meet where two parts of a word join. */
@@ -106,6 +130,13 @@ export const LETTER_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
106
130
  * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
107
131
  */
108
132
  export const EDGE_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
133
+ /**
134
+ * The keys of one `data.sounds.clusters.groups` entry: clusters of one kind,
135
+ * and how often they stand.
136
+ *
137
+ * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
138
+ */
139
+ export const CLUSTER_GROUP_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
109
140
  /**
110
141
  * The keys of a language's `data.sounds.clusters`.
111
142
  *
@@ -172,6 +203,13 @@ export const SKELETON_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
172
203
  * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
173
204
  */
174
205
  export const FRAME_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
206
+ /**
207
+ * The keys of one `data.sounds.mutations` entry: a sound and what it becomes
208
+ * where a named mutation holds.
209
+ *
210
+ * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
211
+ */
212
+ export const MUTATION_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
175
213
  /**
176
214
  * The keys of a language's `data.sounds`.
177
215
  *
@@ -223,6 +261,64 @@ export const CALLING_CLOSING_FIELDS: readonly import("./data-keys.mjs").InnerKey
223
261
  * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
224
262
  */
225
263
  export const CALLING_NAME_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
264
+ /**
265
+ * The keys of one `data.older` entry.
266
+ *
267
+ * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
268
+ */
269
+ export const OLDER_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
270
+ /**
271
+ * The keys of a language's `data.negation`.
272
+ *
273
+ * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
274
+ */
275
+ export const NEGATION_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
276
+ /**
277
+ * The keys of one `data.naming.shapes` entry: how long a class of name runs.
278
+ *
279
+ * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
280
+ */
281
+ export const NAME_SHAPE_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
282
+ /**
283
+ * The keys of one `data.naming.edges` entry: the sounds a name may open or
284
+ * close on, where a name keeps edges ordinary words do not.
285
+ *
286
+ * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
287
+ */
288
+ export const NAME_EDGE_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
289
+ /**
290
+ * The keys of one `data.naming.draws` entry: the element kinds a kind of name
291
+ * is built from.
292
+ *
293
+ * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
294
+ */
295
+ export const NAME_DRAW_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
296
+ /**
297
+ * The keys of one `data.naming.heldFrom` entry: what a place name takes first
298
+ * from what the place is held from.
299
+ *
300
+ * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
301
+ */
302
+ export const HELD_FROM_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
303
+ /**
304
+ * The keys of one `data.naming.nearName.examples` entry.
305
+ *
306
+ * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
307
+ */
308
+ export const NEAR_NAME_EXAMPLE_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
309
+ /**
310
+ * The keys of a language's `data.naming.nearName`.
311
+ *
312
+ * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
313
+ */
314
+ export const NEAR_NAME_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
315
+ /**
316
+ * The keys of a language's `data.naming`: the parameters its names keep
317
+ * beyond the rules its words keep.
318
+ *
319
+ * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
320
+ */
321
+ export const NAMING_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
226
322
  /**
227
323
  * The `data` fields this module declares, in the order the vocabulary lists
228
324
  * them. {@link module:engine/language-data.checkLanguageData} checks what the
@@ -261,5 +357,11 @@ export const CALLING_CLOSING_KEYS: readonly string[];
261
357
  export const ELEMENT_KEYS: readonly string[];
262
358
  /** The keys of {@link ROOT_FIELDS}, in declared order. */
263
359
  export const ROOT_KEYS: readonly string[];
360
+ /** The keys of {@link MUTATION_FIELDS}, in declared order. */
361
+ export const MUTATION_KEYS: readonly string[];
362
+ /** The keys of {@link CLUSTER_GROUP_FIELDS}, in declared order. */
363
+ export const CLUSTER_GROUP_KEYS: readonly string[];
364
+ /** The keys of {@link OLDER_FIELDS}, in declared order. */
365
+ export const OLDER_KEYS: readonly string[];
264
366
  /** The keys of {@link SCOPE_FIELDS}, in declared order. */
265
367
  export const SCOPE_KINDS: readonly string[];
@@ -9,8 +9,9 @@
9
9
  * `config`, `ignores`, `gitignore` and `globs`.
10
10
  * @param {boolean} [params.fix=false] - Repair what markdownlint can, then
11
11
  * report what remains.
12
- * @returns {Promise<{results: object[]}>} markdownlint results, each with its
12
+ * @returns {Promise<{results: object[], markup: object[]}>} markdownlint results, each with its
13
13
  * `fileName` relative to `directory`, ordered by file, line and rule.
14
+ * `markup` carries block syntax diagnostics with absolute filenames.
14
15
  * @throws {MarkdownConfigError} A configuration file the contract refuses.
15
16
  */
16
17
  export function runMarkdownlint(params: {
@@ -20,6 +21,7 @@ export function runMarkdownlint(params: {
20
21
  fix?: boolean | undefined;
21
22
  }): Promise<{
22
23
  results: object[];
24
+ markup: object[];
23
25
  }>;
24
26
  /** Keys an options file may declare at the repository root. */
25
27
  export const ROOT_OPTION_KEYS: readonly string[];
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Validate authored block syntax with positions in the original file.
3
+ * Frontmatter is masked so literal YAML values cannot become body markup.
4
+ * @param {string} source - Complete Markdown source, optionally with frontmatter.
5
+ * @param {string} file - Diagnostic filename.
6
+ * @returns {Array<{file: string, line: number, column: number, severity: string, message: string}>} Syntax errors.
7
+ */
8
+ export function markupFindings(source: string, file: string): Array<{
9
+ file: string;
10
+ line: number;
11
+ column: number;
12
+ severity: string;
13
+ message: string;
14
+ }>;
@@ -71,12 +71,21 @@ export const STRUCTURED_ADDRESSES: readonly ({
71
71
  noteType: string;
72
72
  path: (string | string[] | undefined)[];
73
73
  shape: string;
74
+ } | {
75
+ path: string[];
76
+ shape: string;
77
+ lenient: boolean;
78
+ type?: undefined;
79
+ accepts?: undefined;
80
+ system?: undefined;
81
+ noteType?: undefined;
74
82
  } | {
75
83
  path: string[];
76
84
  shape: string;
77
85
  type: string;
78
86
  accepts: string[];
79
87
  system: string;
88
+ lenient?: undefined;
80
89
  noteType?: undefined;
81
90
  } | {
82
91
  noteType: string;
@@ -84,6 +93,7 @@ export const STRUCTURED_ADDRESSES: readonly ({
84
93
  shape: string;
85
94
  type: string;
86
95
  accepts: string[];
96
+ lenient?: undefined;
87
97
  system?: undefined;
88
98
  })[];
89
99
  export { AddressEntries, encodeAddresses, cloneAddressState, publishAddresses, readPublishedAddresses, flattenPublishedAddresses } from "./address-values.mjs";
@@ -120,11 +120,17 @@ export function noteTypeVocabulary(sources?: ClaimSources): ReadonlySet<string>;
120
120
  * @param {object} [opts] - Options.
121
121
  * @param {readonly object[]} [opts.records] - The corpus, derived once by the
122
122
  * compile and handed in — required, for the reason above.
123
+ * @param {Map<string, {body: string, raw: string}>} [opts.parsed] - Already parsed
124
+ * notes, keyed by file, for body checks and finding positions.
123
125
  * @returns {Array<{file: string, line?: number, column?: number,
124
126
  * severity: "error", message: string, type: string}>} One finding per note.
125
127
  */
126
- export function unclaimedNoteFindings(config?: object, sources?: ClaimSources, { records }?: {
128
+ export function unclaimedNoteFindings(config?: object, sources?: ClaimSources, { records, parsed }?: {
127
129
  records?: readonly object[] | undefined;
130
+ parsed?: Map<string, {
131
+ body: string;
132
+ raw: string;
133
+ }> | undefined;
128
134
  }): Array<{
129
135
  file: string;
130
136
  line?: number;
@@ -21,13 +21,15 @@ export function searchableFrontmatter(fm: object | null | undefined, configured?
21
21
  * @param {string} [options.absPath] - The note's file on disk, read only on the
22
22
  * failing path to locate the offending line and column. The position rides on
23
23
  * the thrown error as `position`, for a caller that emits a diagnostic.
24
+ * @param {string} [options.raw] - Source text already read by the caller.
24
25
  * @param {string} [options.configured] - The package this build compiles.
25
26
  * Defaults to the configured `contentPackage`.
26
27
  * @returns {void}
27
28
  * @throws {Error} When the note declares the field.
28
29
  */
29
- export function assertNoDeclaredPackage(fm: object | null | undefined, { file, absPath, configured }?: {
30
+ export function assertNoDeclaredPackage(fm: object | null | undefined, { file, absPath, configured, raw }?: {
30
31
  file?: string | undefined;
31
32
  absPath?: string | undefined;
33
+ raw?: string | undefined;
32
34
  configured?: string | undefined;
33
35
  }): void;
@@ -476,6 +476,7 @@ export type DataFieldSpec = {
476
476
  */
477
477
  check?: ((note: object, opts: {
478
478
  index?: object;
479
+ addressContext?: object;
479
480
  }) => object[]) | undefined;
480
481
  /**
481
482
  * - The
@@ -14,10 +14,11 @@
14
14
  * the source page's URL and the target page's.
15
15
  * @param {ReadonlyMap<string, RelatedEntry>} entries - Every page of this
16
16
  * site, keyed by URL.
17
+ * @param {ReadonlyMap<string, RelatedEntry[]>} [deliberate] Authored references, including foreign destinations.
17
18
  * @returns {Map<string, Related>} URL → the page's `related` block, both lists
18
19
  * sorted, for every page with at least one entry in either.
19
20
  */
20
- export function relatedPages(edges: Iterable<readonly [string, string]>, entries: ReadonlyMap<string, RelatedEntry>): Map<string, Related>;
21
+ export function relatedPages(edges: Iterable<readonly [string, string]>, entries: ReadonlyMap<string, RelatedEntry>, deliberate?: ReadonlyMap<string, RelatedEntry[]>): Map<string, Related>;
21
22
  /**
22
23
  * The pages a homepage's body links to, as URLs.
23
24
  *
@@ -50,6 +51,10 @@ export type RelatedEntry = {
50
51
  * site-root-relative form the page states for itself.
51
52
  */
52
53
  url: string;
54
+ /**
55
+ * Why the author recommends this page.
56
+ */
57
+ gloss?: string | undefined;
53
58
  /**
54
59
  * The note's `type`, which the theme groups by.
55
60
  */
@@ -59,6 +64,10 @@ export type RelatedEntry = {
59
64
  * The `related` block one page publishes.
60
65
  */
61
66
  export type Related = {
67
+ /**
68
+ * Deliberate references in authored order.
69
+ */
70
+ seeAlso?: RelatedEntry[] | undefined;
62
71
  /**
63
72
  * Pages that link to this one.
64
73
  */
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Validate note references, refusing unresolved, repeated and self destinations.
3
+ * @param {object} note - The authored note, including its raw source.
4
+ * @param {object} [options] - The local and declared dependency context.
5
+ * @param {object} [options.index] - The local and declared dependency index.
6
+ * @param {string} [options.index.contentPackage] - The package owning the note.
7
+ * @param {Set<string>} [options.index.types] - The indexed note types.
8
+ * @param {Set<string>} [options.index.packages] - The indexed packages.
9
+ * @param {(key: string) => {url?: string, uuid?: string, file?: string}|undefined}
10
+ * [options.index.addressHit] - Resolve a canonical Address to its destination.
11
+ * @param {import("./address.mjs").AddressDefaults} [options.addressContext]
12
+ * - The Address defaults and vocabularies at the authored reference.
13
+ * @returns {object[]} Findings located at the offending address.
14
+ */
15
+ export function checkSeeAlso(note: object, { index, addressContext }?: {
16
+ index?: {
17
+ contentPackage?: string | undefined;
18
+ types?: Set<string> | undefined;
19
+ packages?: Set<string> | undefined;
20
+ addressHit?: ((key: string) => {
21
+ url?: string;
22
+ uuid?: string;
23
+ file?: string;
24
+ } | undefined) | undefined;
25
+ } | undefined;
26
+ addressContext?: import("./address.mjs").AddressDefaults | undefined;
27
+ }): object[];
28
+ /**
29
+ * Resolve deliberate references in authored order using the surface's index.
30
+ * @param {object} fm - The note frontmatter.
31
+ * @param {(address: unknown) => object|undefined} resolve - The surface's reference resolver.
32
+ * @returns {object[]} Display names, destinations and optional glosses.
33
+ */
34
+ export function seeAlsoEntries(fm: object, resolve: (address: unknown) => object | undefined): object[];
35
+ /**
36
+ * A generated section; an empty list emits nothing.
37
+ * @param {object[]} entries - Resolved references in authored order.
38
+ * @param {(entry: object) => string} link - The medium's link markup.
39
+ * @param {number} [level=2] - Heading level; a journal uses one for a separate page.
40
+ * @returns {string} Markdown for the section.
41
+ */
42
+ export function seeAlsoMarkdown(entries: object[], link: (entry: object) => string, level?: number): string;
43
+ /**
44
+ * A reference rendered as an ordinary Markdown link for the book pipeline.
45
+ * @param {object} entry - A resolved name and URL.
46
+ * @returns {string} Markdown.
47
+ */
48
+ export function seeAlsoWebLink(entry: object): string;
49
+ /**
50
+ * Deliberate site connections for a set of pages, including dependency pages.
51
+ * @param {object[]} pages - Pages carrying frontmatter and their published URL.
52
+ * @param {(address: unknown) => object|undefined} resolve - The site's reference resolver.
53
+ * @returns {Map<string, import("./related-pages.mjs").RelatedEntry[]>} Entries keyed by source URL.
54
+ */
55
+ export function seeAlsoPages(pages: object[], resolve: (address: unknown) => object | undefined): Map<string, import("./related-pages.mjs").RelatedEntry[]>;
@@ -139,8 +139,9 @@ export function renderSqlTable(result: {
139
139
  * its own table and not the whole build's report.
140
140
  *
141
141
  * @param {object} db - From {@link openNotesDatabase}.
142
- * @param {Array<{source: string, markdown: string}>} sources - The bodies to
143
- * scan.
142
+ * @param {Array<{source: string, markdown: string, frontmatter?: object, authoredSqlBlocks?: number}>} sources -
143
+ * The bodies to scan. `joinGeneratedSections` records the number of authored
144
+ * fences; following generated event fences need no stub comparison.
144
145
  * @param {object} [opts]
145
146
  * @param {(ref: string) => boolean} [opts.linkable] - Passed to
146
147
  * {@link renderSqlTable}.
@@ -163,6 +164,8 @@ export function renderSqlTable(result: {
163
164
  export function prepareSqlTables(db: object, sources: Array<{
164
165
  source: string;
165
166
  markdown: string;
167
+ frontmatter?: object;
168
+ authoredSqlBlocks?: number;
166
169
  }>, { linkable, records, pageLanguage }?: {
167
170
  linkable?: ((ref: string) => boolean) | undefined;
168
171
  pageLanguage?: ((source: string, frontmatter?: object) => string | undefined) | undefined;
@@ -216,7 +219,7 @@ export function generatedSectionsFor(db: object | undefined, records: object[],
216
219
  *
217
220
  * @param {{sections: Map<string, string>, views: Set<string>}} generated -
218
221
  * From {@link generatedSectionsFor}.
219
- * @param {Array<{source: string, markdown: string, frontmatter?: object}>} sources -
222
+ * @param {Array<{source: string, markdown: string, frontmatter?: object, authoredSqlBlocks?: number}>} sources -
220
223
  * The bodies to answer, extended in place.
221
224
  * @param {Map<string, {body: string, frontmatter: object|null}>} [parsed] -
222
225
  * Notes already parsed, keyed by file; a note absent from it is read.
@@ -229,6 +232,7 @@ export function joinGeneratedSections(generated: {
229
232
  source: string;
230
233
  markdown: string;
231
234
  frontmatter?: object;
235
+ authoredSqlBlocks?: number;
232
236
  }>, parsed?: Map<string, {
233
237
  body: string;
234
238
  frontmatter: object | null;
@@ -78,7 +78,8 @@ export function citationLookup(records: Iterable<Record<string, any>>): (languag
78
78
  export function wordRows(records: Iterable<Record<string, any>>, all?: Iterable<Record<string, any>>): object[];
79
79
  /**
80
80
  * The `affixes` rows of a set of index records: one per `data.affixes` entry
81
- * of a language note, which is the row's `language`.
81
+ * of a language note, which is the row's `language`, with `written`, the form
82
+ * as a table prints it: `∅` for a zero affix, whose `form` is `NULL`.
82
83
  *
83
84
  * @param {Iterable<Record<string, any>>} records - Index records, every
84
85
  * Address a string, already narrowed to the audience being published to.
@@ -45,6 +45,17 @@ export const WORD_DATA_KEYS: readonly string[];
45
45
  * one; `coined`, `substrate` and `island` name no form.
46
46
  */
47
47
  export const WORD_SOURCE_KINDS: readonly string[];
48
+ /**
49
+ * What an affix makes: a case, a word derived from another, a verb's tense,
50
+ * aspect, mood or voice, a given name's ending, or a lineage name's ending.
51
+ */
52
+ export const AFFIX_KINDS: readonly string[];
53
+ /** The affix kind whose ending says who the name is given to. */
54
+ export const NAME_AFFIX_KIND: "name";
55
+ /** How a zero affix is written where a table prints it. */
56
+ export const ZERO_AFFIX: "\u2205";
57
+ /** Who a given name's ending is given to. */
58
+ export const AFFIX_GIVEN: readonly string[];
48
59
  /** The source kind that requires `language`. */
49
60
  export const BORROWED_SOURCE_KIND: "borrowed";
50
61
  /**