@heroiclands/package-build 25.5.5 → 25.6.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 (66) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/assets/styles/content-base.css +1 -1
  3. package/docs/api.md +24 -16
  4. package/docs/authoring/frontmatter.md +1 -1
  5. package/docs/authoring/links-and-markup.md +10 -9
  6. package/docs/content-format.md +40 -1
  7. package/docs/diagnostics.md +2 -2
  8. package/docs/guides/reference-pages.md +4 -1
  9. package/docs/guides/site.md +1 -1
  10. package/docs/reference/format-details.md +174 -129
  11. package/docs/reference/note-types.md +134 -121
  12. package/docs/reference/theme-pages.md +1 -0
  13. package/engine/content-format.yaml +9 -7
  14. package/engine/content-html.mjs +3 -2
  15. package/engine/content-index.mjs +23 -0
  16. package/engine/content-links.mjs +8 -0
  17. package/engine/content-render.mjs +2 -2
  18. package/engine/content-tables.mjs +12 -5
  19. package/engine/culture-reach.mjs +3 -0
  20. package/engine/declared-sections.mjs +100 -0
  21. package/engine/derived-sections.mjs +36 -2
  22. package/engine/dictionary-rows.mjs +2 -1
  23. package/engine/generated-sections.mjs +2 -0
  24. package/engine/holdings.mjs +107 -3
  25. package/engine/index-records.mjs +3 -1
  26. package/engine/index.mjs +1 -1
  27. package/engine/language-data.mjs +0 -1
  28. package/engine/language-rows.mjs +1 -3
  29. package/engine/language-terms.mjs +7 -10
  30. package/engine/map-dot.mjs +22 -0
  31. package/engine/markdown-blocks.mjs +23 -8
  32. package/engine/metadata-index.mjs +1 -0
  33. package/engine/note-addresses.mjs +16 -0
  34. package/engine/note-vocabulary.mjs +19 -8
  35. package/engine/office-holders.mjs +76 -0
  36. package/engine/page-breaks.mjs +36 -8
  37. package/engine/pdf-render.mjs +4 -3
  38. package/engine/place-kinds.mjs +106 -0
  39. package/engine/settlement-tables.mjs +410 -675
  40. package/engine/settlement-terms.mjs +118 -0
  41. package/engine/site-build.mjs +2 -4
  42. package/engine/sql-tables.mjs +101 -7
  43. package/engine/standing-terms.mjs +9 -5
  44. package/engine/standings.mjs +83 -31
  45. package/engine/unique-items.mjs +85 -0
  46. package/package.json +9 -4
  47. package/site-theme/data/banner-kinds.yaml +2 -0
  48. package/sohl/actors.mjs +4 -3
  49. package/types/engine/content-html.d.mts +2 -1
  50. package/types/engine/culture-reach.d.mts +3 -0
  51. package/types/engine/declared-sections.d.mts +30 -0
  52. package/types/engine/derived-sections.d.mts +5 -1
  53. package/types/engine/holdings.d.mts +34 -2
  54. package/types/engine/index-records.d.mts +2 -1
  55. package/types/engine/language-terms.d.mts +5 -1
  56. package/types/engine/markdown-blocks.d.mts +5 -1
  57. package/types/engine/office-holders.d.mts +33 -0
  58. package/types/engine/page-breaks.d.mts +8 -0
  59. package/types/engine/place-kinds.d.mts +18 -0
  60. package/types/engine/settlement-tables.d.mts +25 -158
  61. package/types/engine/settlement-terms.d.mts +53 -0
  62. package/types/engine/sql-tables.d.mts +35 -3
  63. package/types/engine/standing-terms.d.mts +3 -2
  64. package/types/engine/standings.d.mts +14 -2
  65. package/types/engine/unique-items.d.mts +12 -0
  66. package/vendor/markdownlint.mjs +855 -569
package/sohl/actors.mjs CHANGED
@@ -42,7 +42,8 @@
42
42
  * **A membership is derived, never authored here.** `data.affiliations` names
43
43
  * the bodies a being belongs to and the standing it holds in each, and each
44
44
  * entry becomes one embedded affiliation item: the entry's `rank` is that
45
- * item's `system.level`, its `office` is `system.office`, and the first of its
45
+ * item's `system.level`, its `office` is `system.office` (several posts joined
46
+ * by commas), and the first of its
46
47
  * `societies` is `system.society`, the one society the SoHL item carries. A
47
48
  * standing is a fact about the setting rather than about one system — it does
48
49
  * not become true because SoHL is installed — so the system block states none
@@ -76,7 +77,7 @@ import { blockProperty, mergeSystemData } from "../engine/system-block.mjs";
76
77
  // A being's memberships and the standing it holds in each, read from the one
77
78
  // place they are authored. The rank *is* the affiliation item's `level`, so
78
79
  // there is nothing to map and nowhere for a second authoring to disagree.
79
- import { readStandings } from "../engine/standings.mjs";
80
+ import { readStandings, standingOffices } from "../engine/standings.mjs";
80
81
 
81
82
  /**
82
83
  * The system this pass compiles for — the block its notes write.
@@ -234,7 +235,7 @@ export class Actors extends SystemActorCompiler {
234
235
  const shortcode = body?.shortcode;
235
236
  if (!shortcode) continue;
236
237
  const level = Number(standing?.rank);
237
- const office = typeof standing?.office === "string" ? standing.office.trim() : "";
238
+ const office = standingOffices(standing).join(", ");
238
239
  const [first] = Array.isArray(standing?.societies) ? standing.societies : [];
239
240
  const society = typeof first === "string" ? first.trim() : "";
240
241
  const system = {
@@ -74,7 +74,8 @@ export function lintContentHtml(contentBase: string, { skipDirectories, sources
74
74
  * markdown's own angle brackets out of it: an autolink is `<https://…>`, and
75
75
  * `https` is followed by `:` rather than whitespace or `>`, so the pattern
76
76
  * stops. A comparison written in prose — `a < b` — has no name after the
77
- * bracket at all.
77
+ * bracket at all. A doubled `<<` opens a quotation, so its second bracket
78
+ * never starts an HTML tag.
78
79
  *
79
80
  * Attributes are consumed as "anything but a bracket", deliberately loosely: a
80
81
  * finding names the tag, and a pattern that tried to parse attribute syntax
@@ -42,6 +42,9 @@ export function noteTerms(index: object, note: object, entry?: object): Map<stri
42
42
  * in a tree that describes peoples. Its ranks and offices then have no
43
43
  * culture associations for term queries.
44
44
  *
45
+ * The note's `data.culture` is the culture the content index resolved for it:
46
+ * its own, else the one its `data.parents` chain agrees on.
47
+ *
45
48
  * @param {object} note - An `affiliation` note.
46
49
  * @param {{index?: object}} [context]
47
50
  * @returns {object[]} One warning, at the note's `type`, or none.
@@ -0,0 +1,30 @@
1
+ /** Authored headings, excluding literal examples and named containers. */
2
+ export function declaredHeadings(markdown: any, syntax: any): {
3
+ level: number;
4
+ line: number;
5
+ text: string;
6
+ id: string;
7
+ classes: string[];
8
+ values: Record<string, string>;
9
+ }[];
10
+ /**
11
+ * Insert indexed content at its heading while preserving subsequent source lines.
12
+ * @param {string} markdown - Expanded body.
13
+ * @param {Array<{slug:string,heading:string,markdown:string}>} [sections] - Declared content.
14
+ * @param {{authored?:string,lineMap?:object[],generatedFrom?:number,syntax?:Function}} [context] - Captured source mappings.
15
+ * @returns {{markdown:string,lineMap:object[],generatedFrom?:number}} Expanded body and locations.
16
+ */
17
+ export function insertDeclaredSections(markdown: string, sections?: Array<{
18
+ slug: string;
19
+ heading: string;
20
+ markdown: string;
21
+ }>, { authored, lineMap, generatedFrom, syntax }?: {
22
+ authored?: string;
23
+ lineMap?: object[];
24
+ generatedFrom?: number;
25
+ syntax?: Function;
26
+ }): {
27
+ markdown: string;
28
+ lineMap: object[];
29
+ generatedFrom?: number;
30
+ };
@@ -51,15 +51,19 @@ export function drawMapsFrom(records: object[], { outDir, foreignIndex, config,
51
51
  * prepared results are keyed.
52
52
  * @param {Map<string, object>} [opts.foreignIndex] - The fetched indexes.
53
53
  * @param {object} [opts.config] - The resolved configuration.
54
+ * @param {(file:string)=>string|undefined} [opts.rawOf] - Captured source locators.
55
+ * @param {object[]} [opts.all=records] - Complete records for settlement validation.
54
56
  * @param {string} [opts.only] - One note file to give sections to; absent,
55
57
  * every note.
56
58
  * @returns {Map<string, Map<string, string>>} Note file to its sections, each
57
59
  * keyed by its slug, for every note given at least one.
58
60
  */
59
- export function derivedSections(records: object[], { fileOf, foreignIndex, config, only }: {
61
+ export function derivedSections(records: object[], { fileOf, foreignIndex, config, only, rawOf, all }: {
60
62
  fileOf: (record: object) => string;
61
63
  foreignIndex?: Map<string, object> | undefined;
62
64
  config?: object | undefined;
65
+ rawOf?: ((file: string) => string | undefined) | undefined;
66
+ all?: object[] | undefined;
63
67
  only?: string | undefined;
64
68
  }): Map<string, Map<string, string>>;
65
69
  /**
@@ -47,13 +47,45 @@ export function foreignHoldingsNodes(foreignIndex: Map<string, object> | undefin
47
47
  export function holdingsPages(nodes: Iterable<HoldingsNode | null>, { governmentNodes }?: {
48
48
  governmentNodes?: Iterable<HoldingsNode | null> | undefined;
49
49
  }): Map<string, Holdings>;
50
+ /**
51
+ * Whether a record's `data.government` came from a parent rather than its own
52
+ * note.
53
+ *
54
+ * @param {object} record - An index record.
55
+ * @returns {boolean} True for a district that inherited its government.
56
+ */
57
+ export function governmentInherited(record: object): boolean;
58
+ /**
59
+ * Give each district that states no government its parent's.
60
+ *
61
+ * A `place/district` falls under the government of the place it stands
62
+ * within. One stating no `data.government` key takes the value of its nearest
63
+ * parent that states the key, walking `data.parents` breadth first, in the
64
+ * order written, through any parent that states none, and never visiting a
65
+ * place twice. An explicit value on the district, `null` (complete anarchy)
66
+ * included, is its own and is kept; a parent's explicit `null` is inherited as
67
+ * anarchy. Every reader of the index — the Governed by and Governed places
68
+ * sections, the "missing government" advisory, a consumer of the published
69
+ * index — then reads the effective government at `data.government`.
70
+ *
71
+ * @param {object[]} records - Content-index records, edited in place.
72
+ * @param {object} [opts]
73
+ * @param {(key: string) => object|undefined} [opts.foreignRecord] - A fetched
74
+ * index's record by canonical Address, for a parent in a dependency.
75
+ * @returns {void}
76
+ */
77
+ export function inheritDistrictGovernments(records: object[], { foreignRecord }?: {
78
+ foreignRecord?: ((key: string) => object | undefined) | undefined;
79
+ }): void;
50
80
  /**
51
81
  * Advise when a place with an authored positive population omits government.
52
82
  * Missing, null or zero population establishes no inhabited population.
53
83
  * Property presence distinguishes omitted government from explicit null,
54
84
  * which means complete anarchy. Non-null values are validated by the declared
55
- * affiliation reference field. Neither geographic containment nor tenure
56
- * supplies this fact, and the rule is independent of place subtype.
85
+ * affiliation reference field. Tenure never supplies this fact, and
86
+ * containment supplies it only to a district, which the content index gives
87
+ * its nearest parent's government (see {@link inheritDistrictGovernments}), so
88
+ * a district under a governed place is never advised.
57
89
  *
58
90
  * @param {object} note - The note with parsed frontmatter and raw source.
59
91
  * @returns {object[]} Located warning findings.
@@ -122,7 +122,8 @@ export function recordPath(record: Record<string, any>): string;
122
122
  * since a note declaring its own is a hard error — and it matches what
123
123
  * the content-table expander puts on the same field, so a query reads the same
124
124
  * value from either. `file` namespaces the note's place in the tree, again
125
- * matching the expander's `file.*`.
125
+ * matching the expander's `file.*`. `officeHolders` lists, on a body, the
126
+ * living beings whose own standing names each of its offices.
126
127
  *
127
128
  * Both are checked rather than assumed: `folder` is real frontmatter on most
128
129
  * notes, so the neighbouring names are close enough to a real key that a silent
@@ -94,7 +94,11 @@ export const JOIN_MEETINGS: readonly string[];
94
94
  * letters between, `drop` loses the first, `double` doubles the consonant.
95
95
  */
96
96
  export const JOIN_RULES: readonly string[];
97
- /** What a written mark says of its letter. */
97
+ /**
98
+ * What a written mark says of its letter: `stress` falls on it, `length` holds
99
+ * it longer, `apart` says it apart from its neighbour, and `quality` changes
100
+ * which vowel is said.
101
+ */
98
102
  export const MARK_KINDS: readonly string[];
99
103
  /** How many of a mark a word may carry: `one` at most, or `any` number. */
100
104
  export const MARK_LIMITS: readonly string[];
@@ -3,6 +3,10 @@
3
3
  * and must not be modified; an `inline` token carries no `children`.
4
4
  *
5
5
  * @param {unknown} source - Markdown.
6
+ * @param {object} [options] - Optional block projection.
7
+ * @param {boolean} [options.footnotes=false] - Include footnote definition containers.
6
8
  * @returns {readonly import("markdown-it").Token[]} The block tokens, in order.
7
9
  */
8
- export function blockTokens(source: unknown): readonly import("markdown-it").Token[];
10
+ export function blockTokens(source: unknown, { footnotes }?: {
11
+ footnotes?: boolean | undefined;
12
+ }): readonly import("markdown-it").Token[];
@@ -1,3 +1,36 @@
1
+ /**
2
+ * A note's identity across its systems: `package-type-shortcode`, lowercased.
3
+ *
4
+ * A body's Item and its journal carry one page, and a being names the body by
5
+ * its `note` Address, so the system is left out.
6
+ *
7
+ * @param {{package?: string, type?: string, shortcode?: string}} tuple
8
+ * @returns {string} The key, or `""` where a part is missing.
9
+ */
10
+ export function officeBodyKey(tuple: {
11
+ package?: string;
12
+ type?: string;
13
+ shortcode?: string;
14
+ }): string;
15
+ /**
16
+ * Each body's offices with the living beings that hold them.
17
+ *
18
+ * Nothing is authored on the office for this: a being states the post it holds
19
+ * in its own `data.affiliations.<body>.office`, and this collects, for every
20
+ * office in a body's `governance.offices`, each being naming it. A being whose
21
+ * `data.died` is set holds nothing, so a former holder drops out and a vacant
22
+ * office lists no one. A post the body does not declare is the standing check's
23
+ * finding and is listed nowhere.
24
+ *
25
+ * Offices keep the body's declared order; holders are ordered by name.
26
+ *
27
+ * @param {object[]} records - Content-index records; only note records are
28
+ * read.
29
+ * @returns {Map<string, Map<string, object[]>>} Body identity
30
+ * (`package-type-shortcode`) → office → the holding beings' records, for every
31
+ * body with at least one held office.
32
+ */
33
+ export function officeHolders(records: object[]): Map<string, Map<string, object[]>>;
1
34
  /**
2
35
  * Validate one affiliation's office roster against dated beings in the corpus.
3
36
  * @param {object} note
@@ -22,6 +22,14 @@ export function scanPageBreaks(source: string): {
22
22
  * @returns {string} Markdown without valid page-break lines.
23
23
  */
24
24
  export function omitPageBreaks(source: string): string;
25
+ /**
26
+ * Preserve refused markers as literal Markdown before the book slices blocks.
27
+ * A slice must not make a marker eligible by discarding its original neighbour.
28
+ * Findings are collected from the original source before this rewrite.
29
+ * @param {string} source - Complete Markdown body, before splitting containers.
30
+ * @returns {string} Markdown with invalid markers escaped for literal rendering.
31
+ */
32
+ export function escapeInvalidPageBreaks(source: string): string;
25
33
  /**
26
34
  * Produce the book's page-break token, leaving ordinary thematic breaks alone.
27
35
  * @param {import("markdown-it")} md - The book parser.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Check a district's parent and a route's journeys.
3
+ *
4
+ * A district naming no parent is reported at its `subType`; one whose parents
5
+ * all resolve and none is a settlement or a region is reported at `parents`.
6
+ * A parent that does not resolve is the Address check's finding, so a district
7
+ * whose only parents are unresolved earns nothing more here. A route stating no
8
+ * `data.routes` entry is reported at its `subType`.
9
+ *
10
+ * @param {object} note - The note: `file`, `raw` and parsed `fm`.
11
+ * @param {{index?: object}} [options] - The lint's options.
12
+ * @returns {object[]} Located findings.
13
+ */
14
+ export function checkPlaceKind(note: object, { index }?: {
15
+ index?: object;
16
+ }): object[];
17
+ /** The place kinds a district may stand within. */
18
+ export const DISTRICT_PARENT_KINDS: readonly string[];
@@ -1,164 +1,31 @@
1
1
  /**
2
- * Every settlements table in a note, read from the raw file so each line
3
- * number is the file's own.
4
- *
5
- * @param {string} raw - The whole note, frontmatter included.
6
- * @returns {{heading: number, tables: SettlementTable[]}} The `## Settlements`
7
- * heading's line, 0 when there is none, and its tables.
8
- */
9
- export function findSettlementTables(raw: string): {
10
- heading: number;
11
- tables: SettlementTable[];
12
- };
13
- /**
14
- * Check a place note's settlements tables.
15
- *
16
- * Declared as part of the `place` vocabulary's whole-note check, so the lint
17
- * runs it with the index every reference resolves through. Without an index
18
- * nothing is reported.
19
- *
20
- * @param {object} note - The note, as the link index hands it over.
21
- * @param {object} [opts]
22
- * @param {object} [opts.index] - The link index, holding the whole corpus.
23
- * @returns {object[]} Findings.
2
+ * Check indexed rows, locating each finding in its original YAML.
3
+ * @param {object} note - Indexed note and original locator text.
4
+ * @param {object} [opts] - Captured context.
5
+ * @param {object} [opts.index] - Complete local and dependency reference index.
6
+ * @returns {object[]} Located findings.
24
7
  */
25
8
  export function checkSettlementTables(note: object, { index }?: {
26
9
  index?: object | undefined;
27
10
  }): object[];
28
- /** The heading text a settlements table stands under, at level two. */
29
- export const SETTLEMENTS_HEADING: "Settlements";
30
- /**
31
- * How a region's countryside is held, each the tag a region carries to say so.
32
- *
33
- * `holder` is what the `Held by` cell names under that model. `person` says
34
- * whether that holder may be a person: only where land is held by a person or
35
- * a house may the cell link a `being`.
36
- *
37
- * **The tags stay open.** They are not a declared tag group: a group would
38
- * report every tag within a typo's distance of one — a region tagged `hellad`
39
- * reads as a near miss of `held` — and a misspelled model on a note with a
40
- * table is already reported, because the note then names no model at all.
41
- *
42
- * @type {ReadonlyArray<Readonly<{tag: string, holder: string, person: boolean}>>}
43
- */
44
- export const HOLDING_MODELS: ReadonlyArray<Readonly<{
45
- tag: string;
46
- holder: string;
47
- person: boolean;
48
- }>>;
49
- /**
50
- * The table's columns, in the order the header states them. An optional
51
- * column may be left out; the header carries no other column, and keeps this
52
- * order.
53
- *
54
- * @type {ReadonlyArray<Readonly<{name: string, required: boolean}>>}
55
- */
56
- export const SETTLEMENT_COLUMNS: ReadonlyArray<Readonly<{
57
- name: string;
58
- required: boolean;
59
- }>>;
60
- /**
61
- * The rules this module enforces, each `name` the prefix its findings carry.
62
- *
63
- * @type {ReadonlyArray<Readonly<{name: string, describe: string}>>}
64
- */
65
- export const SETTLEMENT_TABLE_RULES: ReadonlyArray<Readonly<{
66
- name: string;
67
- describe: string;
68
- }>>;
69
11
  /**
70
- * One cell, as authored.
71
- */
72
- export type Cell = {
73
- /**
74
- * - The cell, trimmed.
75
- */
76
- text: string;
77
- /**
78
- * - The 1-based column its text starts at.
79
- */
80
- column: number;
81
- };
82
- /**
83
- * One settlements table.
84
- */
85
- export type SettlementTable = {
86
- /**
87
- * - The 1-based line of its header.
88
- */
89
- line: number;
90
- /**
91
- * - The header cells.
92
- */
93
- header: string[];
94
- /**
95
- * - Whether the header is the declared columns.
96
- */
97
- wellFormed: boolean;
98
- /**
99
- * - Each
100
- * row's cells keyed by column name; empty when the header is not well formed.
101
- */
102
- rows: Array<{
103
- line: number;
104
- cells: Record<string, Cell>;
105
- }>;
106
- /**
107
- * - The 1-based line of its last row.
108
- */
109
- last: number;
110
- /**
111
- * - Whether prose follows it before the next heading.
112
- */
113
- tailed: boolean;
114
- };
115
- /**
116
- * One place note, as the rules read it.
117
- */
118
- export type PlaceNode = {
119
- /**
120
- * - The note.
121
- */
122
- note: object;
123
- /**
124
- * - Lower case.
125
- */
126
- shortcode: string;
127
- subType: string;
128
- title: string;
129
- /**
130
- * - Its name and listed forms, lower case.
131
- */
132
- names: Set<string>;
133
- /**
134
- * - Enclosing places' shortcodes.
135
- */
136
- parents: string[];
137
- section: {
138
- heading: number;
139
- tables: SettlementTable[];
140
- };
141
- };
142
- /**
143
- * Everything the rules ask of the whole tree.
144
- */
145
- export type Corpus = {
146
- /**
147
- * - By shortcode.
148
- */
149
- places: Map<string, PlaceNode>;
150
- /**
151
- * - By note.
152
- */
153
- byNote: Map<object, PlaceNode>;
154
- /**
155
- * - Region shortcode → the
156
- * settlement notes its table covers.
157
- */
158
- covers: Map<string, PlaceNode[]>;
159
- /**
160
- * - Settlement note → the region
161
- * notes whose tables link it, in path order.
162
- */
163
- rowedIn: Map<object, object[]>;
164
- };
12
+ * Fail every publishing surface before it writes invalid settlement output.
13
+ * @param {object[]} records - Indexed records, including documentation projections.
14
+ * @param {object} opts - Captured context.
15
+ * @param {(record: object) => string} opts.fileOf - Source filename for a record.
16
+ * @param {Map<string,object>} [opts.foreignIndex] - Dependency reference targets.
17
+ * @param {(file: string) => string|undefined} [opts.rawOf] - Captured locator text.
18
+ * @returns {void}
19
+ */
20
+ export function assertSettlementRecords(records: object[], { fileOf, foreignIndex, rawOf }?: {
21
+ fileOf: (record: object) => string;
22
+ foreignIndex?: Map<string, object> | undefined;
23
+ rawOf?: ((file: string) => string | undefined) | undefined;
24
+ }): void;
25
+ /**
26
+ * Render rows once as shared Markdown; all surfaces consume the same table.
27
+ * @param {object} record - Indexed region.
28
+ * @returns {string} Generated table and its closing sentence.
29
+ */
30
+ export function settlementMarkdown(record: object): string;
31
+ export { SETTLEMENTS_HEADING, SETTLEMENT_COLUMNS, SETTLEMENT_ROW_FIELDS, SETTLEMENTS_FIELD, SETTLEMENT_TABLE_RULES } from "./settlement-terms.mjs";
@@ -0,0 +1,53 @@
1
+ /** Dependency-free settlement row declarations. @module */
2
+ /** The generated heading. */
3
+ export const SETTLEMENTS_HEADING: "Settlements";
4
+ /** Rendered columns, in their fixed order; Held by and For appear when a row states them. */
5
+ export const SETTLEMENT_COLUMNS: readonly (Readonly<{
6
+ name: "Name";
7
+ required: true;
8
+ }> | Readonly<{
9
+ name: "Market";
10
+ required: true;
11
+ }> | Readonly<{
12
+ name: "People";
13
+ required: true;
14
+ }> | Readonly<{
15
+ name: "Held by";
16
+ required: false;
17
+ }> | Readonly<{
18
+ name: "For";
19
+ required: false;
20
+ }>)[];
21
+ /** The closed row fields; existing subjects use native Addresses. */
22
+ export const SETTLEMENT_ROW_FIELDS: readonly Readonly<unknown>[];
23
+ /** The place vocabulary's single settlement declaration. */
24
+ export const SETTLEMENTS_FIELD: Readonly<{
25
+ name: "settlements";
26
+ kind: "map";
27
+ shape: "{ rows, tail }";
28
+ fields: readonly ({
29
+ name: string;
30
+ kind: string;
31
+ required: boolean;
32
+ shape: string;
33
+ entries: {
34
+ kind: string;
35
+ shape: string;
36
+ fields: readonly Readonly<unknown>[];
37
+ };
38
+ describe: string;
39
+ } | {
40
+ name: string;
41
+ kind: string;
42
+ required: boolean;
43
+ shape: string;
44
+ describe: string;
45
+ entries?: undefined;
46
+ })[];
47
+ describe: "A region's settlement rows and the sentence stating what they leave out; the table is generated from this declaration.";
48
+ }>;
49
+ /** Every semantic finding, named once for the reference and its guards. */
50
+ export const SETTLEMENT_TABLE_RULES: readonly Readonly<{
51
+ name: string;
52
+ describe: string;
53
+ }>[];
@@ -21,6 +21,23 @@ export function findSqlBlocks(markdown: string): Array<{
21
21
  layout: string;
22
22
  block: string;
23
23
  }>;
24
+ /**
25
+ * Where a query reads the `file` block of the index record: `file.path`,
26
+ * `file.folder` or `file.name`, bare or qualified by a relation or its alias.
27
+ * A note is selected by property, tag or address, so a query that reaches for
28
+ * the file it was written in is refused. A field of a note's own data that is
29
+ * named `file` is not the block and is left alone.
30
+ *
31
+ * @param {string} sql - One fence's query.
32
+ * @returns {{line: number, column: number, text: string}|undefined} The first
33
+ * reference, its 0-based line within the query and 1-based column, or
34
+ * nothing where the query makes none.
35
+ */
36
+ export function findFileReference(sql: string): {
37
+ line: number;
38
+ column: number;
39
+ text: string;
40
+ } | undefined;
24
41
  /**
25
42
  * Open a DuckDB view over the content index.
26
43
  *
@@ -205,25 +222,35 @@ export function prepareInlineSqlExpressions(db: any, sources: any): Promise<Map<
205
222
  * @param {Map<string, Map<string, string>>} [opts.derived] - The derived
206
223
  * lists, from an earlier call over the same records, which are not derived
207
224
  * again.
225
+ * @param {Map<string,{raw?:string}>} [opts.parsed] - Captured source locators.
208
226
  * @param {object[]} [opts.all=records] - Complete records for lexical lineage
209
227
  * traversal through providers outside the surface's audience.
210
228
  * @returns {Promise<{sections: Map<string, string>, views: Set<string>,
211
229
  * derived: Map<string, Map<string, string>>,
212
- * lexical: Map<string, import("./lexical-sections.mjs").LexicalSection[]>}>} Note file to its sections,
230
+ * lexical: Map<string, import("./lexical-sections.mjs").LexicalSection[]>,
231
+ * declared: Map<string, Array<{slug:string,heading:string,markdown:string}>>}>} Note file to its sections,
213
232
  * joined in order; the files whose sections hold an event view's fence; and
214
233
  * the derived lists alone.
215
234
  */
216
- export function generatedSectionsFor(db: object | undefined, records: object[], contentBase: string, { only, foreignIndex, config, derived: given, all }?: {
235
+ export function generatedSectionsFor(db: object | undefined, records: object[], contentBase: string, { only, foreignIndex, config, derived: given, all, parsed }?: {
217
236
  only?: string | undefined;
218
237
  foreignIndex?: Map<string, object> | undefined;
219
238
  config?: object | undefined;
220
239
  derived?: Map<string, Map<string, string>> | undefined;
240
+ parsed?: Map<string, {
241
+ raw?: string;
242
+ }> | undefined;
221
243
  all?: object[] | undefined;
222
244
  }): Promise<{
223
245
  sections: Map<string, string>;
224
246
  views: Set<string>;
225
247
  derived: Map<string, Map<string, string>>;
226
248
  lexical: Map<string, import("./lexical-sections.mjs").LexicalSection[]>;
249
+ declared: Map<string, Array<{
250
+ slug: string;
251
+ heading: string;
252
+ markdown: string;
253
+ }>>;
227
254
  }>;
228
255
  /**
229
256
  * Join each note's generated sections to its body among the sources a
@@ -262,13 +289,18 @@ export function joinGeneratedSections(generated: {
262
289
  * empty set carrying them.
263
290
  *
264
291
  * @param {Map<string, object[]>} prepared - From {@link prepareSqlTables}.
265
- * @param {{sections: Map<string, string>, lexical?: Map<string, import("./lexical-sections.mjs").LexicalSection[]>}} generated - From
292
+ * @param {{sections: Map<string, string>, lexical?: Map<string, import("./lexical-sections.mjs").LexicalSection[]>, declared?: Map<string, Array<{slug:string,heading:string,markdown:string}>>}} generated - From
266
293
  * {@link generatedSectionsFor}.
267
294
  * @returns {void}
268
295
  */
269
296
  export function attachGeneratedSections(prepared: Map<string, object[]>, generated: {
270
297
  sections: Map<string, string>;
271
298
  lexical?: Map<string, import("./lexical-sections.mjs").LexicalSection[]>;
299
+ declared?: Map<string, Array<{
300
+ slug: string;
301
+ heading: string;
302
+ markdown: string;
303
+ }>>;
272
304
  }): void;
273
305
  /**
274
306
  * Each declared dependency's cached index, as {@link openNotesDatabase} attaches
@@ -20,8 +20,9 @@ export const STANDING_BODY_TYPES: readonly string[];
20
20
  * The keys one standing carries, as inner-key declarations.
21
21
  *
22
22
  * `rank` is the level on the body's own `governance.ranks` ladder, and every
23
- * membership states one. `office` is a key of its `governance.offices` map, and
24
- * is stated only where the being holds a post. `societies` names the societies
23
+ * membership states one. `office` is a key of its `governance.offices` map, or
24
+ * a list of them where the being holds several posts in that body, and is
25
+ * stated only where the being holds a post. `societies` names the societies
25
26
  * the membership is held in, as a list because a membership can span several.
26
27
  *
27
28
  * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
@@ -98,12 +98,24 @@ export function standingsDigest(fm: object): {
98
98
  ranks?: Record<string, string>;
99
99
  offices?: Record<string, string>;
100
100
  } | undefined;
101
+ /**
102
+ * The posts a standing holds, trimmed, in the order written.
103
+ *
104
+ * `office` is one post or a list of them, and every reader — the line on the
105
+ * being's page, the SoHL item, the derived holders of an office — wants the
106
+ * same list from both.
107
+ *
108
+ * @param {object} [standing] - `{rank, office}`, either or both absent.
109
+ * @returns {string[]} The posts, blank and non-text entries left out.
110
+ */
111
+ export function standingOffices(standing?: object): string[];
101
112
  /**
102
113
  * What a standing says before the body that confers it — `War Chief, Hárár
103
114
  * (5), of ` — or `""` where no standing is held.
104
115
  *
105
116
  * The office leads, because it is the more particular of the two: a reader
106
- * meeting "War Chief" has been told the rung as well. The rung follows as its
117
+ * meeting "War Chief" has been told the rung as well. Several posts in one
118
+ * body lead in the order written, `Treasurer, Chancellor, …`. The rung follows as its
107
119
  * title with its level in parentheses, read from the body's own ladder; where
108
120
  * the ladder names no rung at that level, or none is available, it reads as
109
121
  * `Rank 5` and the number is not repeated. The body closes the phrase, which is
@@ -131,7 +143,7 @@ export function standingLead(standing: object, digest?: {
131
143
  * 3. the entry states a standing at all — what a stated standing holds is the
132
144
  * inner-key check's, read from {@link STANDING_FIELDS};
133
145
  * 4. a `rank` is a level the named body's `governance.ranks` declares;
134
- * 5. an `office` is a key of that body's `governance.offices`.
146
+ * 5. each post an `office` names is a key of that body's `governance.offices`.
135
147
  *
136
148
  * Both of the last two are read from the named body's own frontmatter as the
137
149
  * lint runs, so a rung or a post that does not exist fails on every being pointing
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Report a unique item whose note links no place or being.
3
+ *
4
+ * @param {object} note - The note: `file`, `raw`, `body` and parsed `fm`.
5
+ * @param {{index?: object}} [options] - The lint's options.
6
+ * @returns {object[]} The finding, located at `unique`, or none.
7
+ */
8
+ export function checkUniqueItem(note: object, { index }?: {
9
+ index?: object;
10
+ }): object[];
11
+ /** The note types a unique item's links must reach at least one of. */
12
+ export const UNIQUE_ITEM_LINK_TYPES: readonly string[];