explico 0.8.0 → 0.11.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 (230) hide show
  1. package/CHANGELOG.md +1239 -1
  2. package/README.md +60 -15
  3. package/dist/compiler/astro.d.ts +6 -1
  4. package/dist/compiler/cli.d.ts +46 -2
  5. package/dist/compiler/cli.js +70 -4
  6. package/dist/compiler/collections.d.ts +35 -2
  7. package/dist/compiler/collections.js +68 -5
  8. package/dist/compiler/course-config.d.ts +3 -194
  9. package/dist/compiler/course-config.js +78 -5
  10. package/dist/compiler/exports/standalone-html.d.ts +6 -1
  11. package/dist/compiler/knowledge-version.js +1 -1
  12. package/dist/compiler/lesson-derivation.d.ts +27 -10
  13. package/dist/compiler/lesson-derivation.js +23 -8
  14. package/dist/compiler/logging.d.ts +4 -1
  15. package/dist/compiler/logging.js +4 -1
  16. package/dist/compiler/manifest.d.ts +204 -31
  17. package/dist/compiler/manifest.js +270 -55
  18. package/dist/compiler/resolution-report.d.ts +6 -1
  19. package/dist/course-config-Dsnri9Tk.d.ts +464 -0
  20. package/dist/reference/base-accents.d.mts +26 -0
  21. package/dist/reference/base-accents.mjs +31 -0
  22. package/dist/reference/base-roman-labels.d.mts +2 -2
  23. package/dist/reference/base-roman-labels.mjs +2 -2
  24. package/dist/reference/consistency.d.ts +14 -2
  25. package/dist/reference/consistency.js +108 -4
  26. package/dist/reference/delimiter-size.d.ts +3 -2
  27. package/dist/reference/diagrams-validate.d.ts +7 -2
  28. package/dist/reference/diagrams-validate.js +9 -1
  29. package/dist/reference/equations-validate.d.ts +8 -5
  30. package/dist/reference/equations-validate.js +1 -0
  31. package/dist/reference/equations.d.mts +60 -31
  32. package/dist/reference/equations.mjs +83 -43
  33. package/dist/reference/examples-validate.d.ts +31 -0
  34. package/dist/reference/examples-validate.js +48 -0
  35. package/dist/reference/figures-validate.d.ts +1 -1
  36. package/dist/reference/figures-validate.js +4 -1
  37. package/dist/reference/folds-validate.d.ts +33 -0
  38. package/dist/reference/folds-validate.js +32 -0
  39. package/dist/reference/gate-abbreviations.d.ts +3 -1
  40. package/dist/reference/gate-abbreviations.js +1 -0
  41. package/dist/reference/gate-course-rules.d.ts +32 -0
  42. package/dist/reference/gate-course-rules.js +85 -0
  43. package/dist/reference/gate-heading-length.d.ts +5 -2
  44. package/dist/reference/gate-heading-length.js +45 -21
  45. package/dist/reference/gate-heading-structure.d.ts +41 -0
  46. package/dist/reference/gate-heading-structure.js +113 -0
  47. package/dist/reference/gate-math-punctuation.d.ts +71 -0
  48. package/dist/reference/gate-math-punctuation.js +251 -0
  49. package/dist/reference/gate-math.d.ts +22 -6
  50. package/dist/reference/gate-math.js +42 -8
  51. package/dist/reference/gate-part-pages.d.ts +50 -0
  52. package/dist/reference/gate-part-pages.js +118 -0
  53. package/dist/reference/gate-prose-notation.d.ts +21 -11
  54. package/dist/reference/gate-prose-notation.js +25 -7
  55. package/dist/reference/katex-delimiters.mjs +20 -7
  56. package/dist/reference/keyed-float-validate.d.ts +27 -10
  57. package/dist/reference/keyed-float-validate.js +15 -1
  58. package/dist/reference/keyed-jsx-floats.d.mts +11 -5
  59. package/dist/reference/keyed-jsx-floats.mjs +73 -20
  60. package/dist/reference/knowledge-checks-validate.d.ts +1 -1
  61. package/dist/reference/knowledge-checks.d.mts +3 -1
  62. package/dist/reference/knowledge-checks.mjs +9 -4
  63. package/dist/reference/lab-math-scope.d.ts +1 -0
  64. package/dist/reference/lab-math-slot.d.ts +34 -0
  65. package/dist/reference/lab-math-slot.js +6 -0
  66. package/dist/reference/math-glyphs.d.mts +80 -4
  67. package/dist/reference/math-glyphs.mjs +572 -37
  68. package/dist/reference/page-notation.d.ts +6 -1
  69. package/dist/reference/raw-floats-validate.js +1 -1
  70. package/dist/reference/raw-floats.mjs +1 -1
  71. package/dist/reference/rehype-heading-anchors.d.mts +14 -0
  72. package/dist/reference/rehype-heading-anchors.mjs +116 -17
  73. package/dist/reference/rehype-table-header-labels.d.mts +6 -1
  74. package/dist/reference/rehype-table-header-labels.mjs +21 -3
  75. package/dist/reference/remark-asides.d.mts +3 -0
  76. package/dist/reference/remark-asides.mjs +180 -0
  77. package/dist/reference/remark-memo.d.mts +3 -1
  78. package/dist/reference/remark-memo.mjs +42 -11
  79. package/dist/reference/remark-notation.d.mts +60 -10
  80. package/dist/reference/remark-notation.mjs +387 -36
  81. package/dist/reference/render-lab-math.d.ts +4 -26
  82. package/dist/reference/render-lab-math.js +4 -4
  83. package/dist/reference/sections-validate.d.ts +27 -0
  84. package/dist/reference/sections-validate.js +41 -0
  85. package/dist/reference/sections.d.mts +97 -0
  86. package/dist/reference/sections.mjs +306 -0
  87. package/dist/reference/tables-validate.d.ts +1 -1
  88. package/dist/reference/types.d.ts +17 -1
  89. package/dist/reference/unit-words.d.ts +37 -0
  90. package/dist/reference/unit-words.js +170 -0
  91. package/dist/rules/catalog.d.ts +176 -0
  92. package/dist/rules/catalog.js +983 -0
  93. package/dist/rules/course-rules.d.ts +115 -0
  94. package/dist/rules/course-rules.js +292 -0
  95. package/dist/rules/course-rules.schema.json +138 -0
  96. package/dist/rules/in-effect.d.ts +53 -0
  97. package/dist/rules/in-effect.js +142 -0
  98. package/dist/rules/rules.schema.json +1072 -0
  99. package/dist/rules/settings.d.ts +3 -0
  100. package/dist/rules/settings.js +290 -0
  101. package/dist/rules/waivers.d.ts +53 -0
  102. package/dist/rules/waivers.js +132 -0
  103. package/dist/session/PreferenceStore.d.ts +5 -0
  104. package/dist/session/PreferenceStore.js +9 -0
  105. package/docs/VERSIONING.md +2 -2
  106. package/docs/authoring/README.md +232 -40
  107. package/docs/authoring/agents.md +55 -9
  108. package/docs/authoring/components.md +178 -22
  109. package/docs/authoring/migrating.md +1096 -2
  110. package/docs/authoring/notation.md +576 -322
  111. package/docs/authoring/prose.md +551 -0
  112. package/docs/authoring/retired.md +35 -0
  113. package/docs/authoring/structure-rules.md +422 -40
  114. package/docs/authoring/structure.md +445 -62
  115. package/docs/authoring/versioning.md +20 -28
  116. package/docs/authoring/visualizations.md +142 -20
  117. package/package.json +2 -2
  118. package/src/compiler/cli.ts +116 -6
  119. package/src/compiler/collections.ts +111 -7
  120. package/src/compiler/course-config.ts +273 -34
  121. package/src/compiler/knowledge-version.ts +1 -1
  122. package/src/compiler/lesson-derivation.ts +54 -15
  123. package/src/compiler/logging.ts +8 -2
  124. package/src/compiler/manifest.ts +573 -104
  125. package/src/components/BlockHeading.astro +113 -0
  126. package/src/components/CourseContents.astro +221 -0
  127. package/src/components/CurriculumMap.astro +5 -6
  128. package/src/components/FloatIndex.astro +200 -0
  129. package/src/components/HoverPanel.astro +431 -79
  130. package/src/components/PartBridge.astro +27 -0
  131. package/src/components/PartOverview.astro +401 -0
  132. package/src/components/PartScope.astro +27 -0
  133. package/src/components/abbreviation/AbbreviationLayer.astro +49 -51
  134. package/src/components/assessments/AssessmentRunner.tsx +8 -5
  135. package/src/components/assessments/AssessmentSet.astro +23 -26
  136. package/src/components/block-heading.ts +146 -0
  137. package/src/components/card-arrow.ts +243 -0
  138. package/src/components/card-drag.ts +631 -0
  139. package/src/components/card-jump.ts +45 -0
  140. package/src/components/card-manager.ts +1589 -0
  141. package/src/components/card-memory.ts +177 -0
  142. package/src/components/card-placement.ts +504 -0
  143. package/src/components/card-side.ts +336 -0
  144. package/src/components/card-travel.ts +197 -0
  145. package/src/components/citation/CitationLayer.astro +77 -76
  146. package/src/components/diagram/Diagram.astro +161 -316
  147. package/src/components/disclosure/course-disclosure.ts +24 -0
  148. package/src/components/disclosure/hover-preview.ts +122 -42
  149. package/src/components/disclosure/persisted-disclosure.ts +3 -3
  150. package/src/components/disclosure/print-as-shown.ts +252 -0
  151. package/src/components/examples/CompactExample.astro +3 -1
  152. package/src/components/examples/CompactExamples.astro +101 -94
  153. package/src/components/figure/Figure.astro +174 -274
  154. package/src/components/figure/FloatCaption.astro +87 -0
  155. package/src/components/figure/figure-state.ts +58 -0
  156. package/src/components/figure/figure-tools.ts +87 -0
  157. package/src/components/float-name.ts +101 -0
  158. package/src/components/fold/Fold.astro +125 -0
  159. package/src/components/fold/fold-id.ts +40 -0
  160. package/src/components/memo/Memo.astro +162 -231
  161. package/src/components/memo/ShortNote.astro +6 -6
  162. package/src/components/notation/NotationFormula.astro +5 -1
  163. package/src/components/notation/NotationGlossary.astro +3 -4
  164. package/src/components/notation/NotationLayer.astro +169 -162
  165. package/src/components/notation/NotationMath.astro +10 -1
  166. package/src/components/reference/FloatRefLayer.astro +485 -423
  167. package/src/components/starlight/AboutPanel.astro +5 -4
  168. package/src/components/starlight/CollapsibleSections.astro +207 -93
  169. package/src/components/starlight/DiagramEnhancer.astro +15 -138
  170. package/src/components/starlight/HeadingAnchorEnhancer.astro +193 -1
  171. package/src/components/starlight/KeyedFloatEnhancer.astro +100 -9
  172. package/src/components/starlight/LayoutHeader.astro +298 -19
  173. package/src/components/starlight/LayoutPageSidebar.astro +99 -12
  174. package/src/components/starlight/LayoutPageTitle.astro +120 -0
  175. package/src/components/starlight/LayoutSidebar.astro +229 -11
  176. package/src/components/starlight/LessonExportActions.astro +53 -72
  177. package/src/components/starlight/LessonFooter.astro +26 -4
  178. package/src/components/starlight/section-parts.ts +15 -2
  179. package/src/components/starlight/section-summary.ts +181 -0
  180. package/src/components/table/Table.astro +95 -76
  181. package/src/components/table/TableOverflowExpander.astro +65 -28
  182. package/src/content-config.ts +40 -4
  183. package/src/reference/base-accents.mjs +31 -0
  184. package/src/reference/base-roman-labels.mjs +2 -2
  185. package/src/reference/consistency.ts +176 -5
  186. package/src/reference/delimiter-size.ts +3 -2
  187. package/src/reference/diagrams-validate.ts +17 -1
  188. package/src/reference/equations-validate.ts +10 -5
  189. package/src/reference/equations.mjs +83 -43
  190. package/src/reference/examples-validate.ts +97 -0
  191. package/src/reference/figures-validate.ts +6 -1
  192. package/src/reference/folds-validate.ts +65 -0
  193. package/src/reference/gate-abbreviations.ts +1 -0
  194. package/src/reference/gate-course-rules.ts +134 -0
  195. package/src/reference/gate-heading-length.ts +66 -35
  196. package/src/reference/gate-heading-structure.ts +193 -0
  197. package/src/reference/gate-math-punctuation.ts +381 -0
  198. package/src/reference/gate-math.ts +69 -10
  199. package/src/reference/gate-part-pages.ts +217 -0
  200. package/src/reference/gate-prose-notation.ts +59 -12
  201. package/src/reference/katex-delimiters.mjs +20 -7
  202. package/src/reference/keyed-float-validate.ts +45 -11
  203. package/src/reference/keyed-jsx-floats.mjs +73 -20
  204. package/src/reference/knowledge-checks-validate.ts +1 -1
  205. package/src/reference/knowledge-checks.mjs +9 -4
  206. package/src/reference/lab-math-slot.ts +36 -0
  207. package/src/reference/math-glyphs.mjs +572 -37
  208. package/src/reference/raw-floats-validate.ts +1 -1
  209. package/src/reference/raw-floats.mjs +1 -1
  210. package/src/reference/rehype-heading-anchors.mjs +116 -17
  211. package/src/reference/rehype-table-header-labels.mjs +21 -3
  212. package/src/reference/remark-asides.mjs +180 -0
  213. package/src/reference/remark-memo.mjs +42 -11
  214. package/src/reference/remark-notation.mjs +387 -36
  215. package/src/reference/render-lab-math.ts +16 -31
  216. package/src/reference/sections-validate.ts +81 -0
  217. package/src/reference/sections.mjs +306 -0
  218. package/src/reference/tables-validate.ts +2 -1
  219. package/src/reference/types.ts +17 -0
  220. package/src/reference/unit-words.ts +233 -0
  221. package/src/rules/catalog.ts +1298 -0
  222. package/src/rules/course-rules.ts +456 -0
  223. package/src/rules/in-effect.ts +235 -0
  224. package/src/rules/settings.ts +448 -0
  225. package/src/rules/waivers.ts +249 -0
  226. package/src/session/PreferenceStore.ts +15 -0
  227. package/src/styles/global.css +1471 -163
  228. package/src/styles/surfaces.css +182 -0
  229. package/src/components/hover-panel.ts +0 -339
  230. package/src/components/popup-stack.ts +0 -345
@@ -0,0 +1,170 @@
1
+ const UNIT_WORDS = [
2
+ { stem: "kilomet(?:er|re)", symbol: "km" },
3
+ { stem: "centimet(?:er|re)", symbol: "cm" },
4
+ { stem: "millimet(?:er|re)", symbol: "mm" },
5
+ { stem: "met(?:er|re)", symbol: "m" },
6
+ { stem: "millisecond", symbol: "ms" },
7
+ { stem: "second", symbol: "s" },
8
+ { stem: "minute", symbol: "min" },
9
+ { stem: "hour", symbol: "h" },
10
+ { stem: "kilogram", symbol: "kg" },
11
+ { stem: "milligram", symbol: "mg" },
12
+ { stem: "gram", symbol: "g" },
13
+ { stem: "newton", symbol: "N" },
14
+ { stem: "kilojoule", symbol: "kJ" },
15
+ { stem: "joule", symbol: "J" },
16
+ { stem: "kilowatt", symbol: "kW" },
17
+ { stem: "watt", symbol: "W" },
18
+ { stem: "hertz", symbol: "Hz" },
19
+ { stem: "kilopascal", symbol: "kPa" },
20
+ { stem: "pascal", symbol: "Pa" },
21
+ { stem: "volt", symbol: "V" },
22
+ { stem: "amp(?:ere)?", symbol: "A" },
23
+ { stem: "coulomb", symbol: "C" },
24
+ { stem: "kelvin", symbol: "K" },
25
+ { stem: "mole", symbol: "mol" },
26
+ { stem: "lit(?:er|re)", symbol: "L" },
27
+ { stem: "radian", symbol: "rad" },
28
+ { stem: "degree", symbol: "\xB0" }
29
+ ];
30
+ const NUMBER_WORDS = {
31
+ zero: 0,
32
+ one: 1,
33
+ two: 2,
34
+ three: 3,
35
+ four: 4,
36
+ five: 5,
37
+ six: 6,
38
+ seven: 7,
39
+ eight: 8,
40
+ nine: 9,
41
+ ten: 10,
42
+ eleven: 11,
43
+ twelve: 12,
44
+ twenty: 20,
45
+ thirty: 30,
46
+ forty: 40,
47
+ fifty: 50,
48
+ sixty: 60,
49
+ seventy: 70,
50
+ eighty: 80,
51
+ ninety: 90,
52
+ hundred: 100,
53
+ thousand: 1e3
54
+ };
55
+ const UNIT = `(?:${UNIT_WORDS.map((u) => `${u.stem}s?`).join("|")})`;
56
+ const UNIT_WORD = String.raw`\b${UNIT}\b(?!-)(?!\s+of\s+freedom\b)`;
57
+ const POWER_BEFORE = String.raw`(?:(?:square|cubic)\s+)`;
58
+ const POWER_AFTER = String.raw`(?:\s+(?:squared|cubed)\b)`;
59
+ const TERM = String.raw`${POWER_BEFORE}?${UNIT_WORD}${POWER_AFTER}?`;
60
+ const COMPOUND = String.raw`${TERM}(?:\s+per\s+${TERM})*`;
61
+ const NUMBER = String.raw`(?:\d+(?:\.\d+)?|${Object.keys(NUMBER_WORDS).join("|")})`;
62
+ const PATTERNS = [
63
+ // 2 meters per second squared · two-meter · 4 seconds
64
+ new RegExp(String.raw`(?<![\w.])${NUMBER}(?:\s+|-)${COMPOUND}`, "gi"),
65
+ // meters per second · rotations per second
66
+ new RegExp(String.raw`(?:${TERM}\s+)?\bper\s+${COMPOUND}`, "gi"),
67
+ // meters per unit time
68
+ new RegExp(String.raw`${TERM}\s+per\b`, "gi"),
69
+ // square meters · seconds squared
70
+ new RegExp(String.raw`${POWER_BEFORE}${UNIT_WORD}${POWER_AFTER}?|${UNIT_WORD}${POWER_AFTER}`, "gi")
71
+ ];
72
+ const INLINE_MATH = /(?<![\\$])\$(?!\s)([^$\n]+?)(?<![\\\s])\$(?!\d)/g;
73
+ const AFTER_MATH = new RegExp(String.raw`^(?:[ \t]+|-)(${COMPOUND})`, "i");
74
+ function unitOf(word) {
75
+ return UNIT_WORDS.find((u) => new RegExp(`^${u.stem}s?$`, "i").test(word));
76
+ }
77
+ function unitWordsIn(phrase) {
78
+ return [...phrase.matchAll(new RegExp(UNIT_WORD, "gi"))].map((m) => m[0].toLowerCase());
79
+ }
80
+ function unitLatex(compound) {
81
+ const groups = compound.trim().split(/\s+per\s+/i);
82
+ const segments = [];
83
+ for (const [i, group] of groups.entries()) {
84
+ const words = group.toLowerCase().split(/\s+/);
85
+ const power = words.includes("square") || words.includes("squared") ? "2" : words.includes("cubic") || words.includes("cubed") ? "3" : void 0;
86
+ const unit = words.map(unitOf).find(Boolean);
87
+ if (unit === void 0) return void 0;
88
+ if (unit.symbol === "\xB0") {
89
+ return groups.length === 1 && power === void 0 ? "^{\\circ}" : void 0;
90
+ }
91
+ segments.push({ text: `${i > 0 ? "/" : ""}${unit.symbol}`, ...power ? { power } : {} });
92
+ }
93
+ let out = "";
94
+ let pending = "";
95
+ for (const segment of segments) {
96
+ pending += segment.text;
97
+ if (segment.power !== void 0) {
98
+ out += `\\text{${pending}}^{${segment.power}}`;
99
+ pending = "";
100
+ }
101
+ }
102
+ return pending === "" ? out : `${out}\\text{${pending}}`;
103
+ }
104
+ function numberOf(word) {
105
+ return /^\d/.test(word) ? word : String(NUMBER_WORDS[word.toLowerCase()] ?? word);
106
+ }
107
+ const GENERIC = "a unit symbol in `$\u2026$` (`$2\\ \\text{m/s}^2$`, `$30^{\\circ}$`)";
108
+ const LEADING_NUMBER = new RegExp(String.raw`^(${NUMBER})(?:\s+|-)([\s\S]*)$`, "i");
109
+ const STARTS_WITH_UNIT = new RegExp(String.raw`^${TERM}`, "i");
110
+ function suggest(phrase) {
111
+ const numbered = LEADING_NUMBER.exec(phrase.trim());
112
+ const number = numbered ? numberOf(numbered[1]) : void 0;
113
+ const rest = numbered ? numbered[2] : phrase.trim();
114
+ if (!STARTS_WITH_UNIT.test(rest)) {
115
+ const after = /\bper\s+([\s\S]*)$/i.exec(rest)?.[1];
116
+ const latex2 = after === void 0 ? void 0 : unitLatex(after);
117
+ return latex2 === void 0 ? GENERIC : `the rate in \`$\u2026$\`, with \`${latex2}\` after the slash (\`$\u2026/${latex2}$\`)`;
118
+ }
119
+ const unit = rest.replace(/\s+per$/i, "");
120
+ const latex = unitLatex(unit);
121
+ if (latex === void 0) return GENERIC;
122
+ const trailingPer = unit !== rest ? "/\u2026" : "";
123
+ if (number === void 0) return `\`$${latex}${trailingPer}$\``;
124
+ return `\`$${number}${latex.startsWith("^") ? "" : "\\ "}${latex}${trailingPer}$\``;
125
+ }
126
+ function merge(found) {
127
+ const sorted = [...found].sort((a, b) => a.index - b.index || b.token.length - a.token.length);
128
+ const out = [];
129
+ let end = -1;
130
+ for (const item of sorted) {
131
+ if (item.index < end) continue;
132
+ out.push(item);
133
+ end = item.index + item.token.length;
134
+ }
135
+ return out;
136
+ }
137
+ function findSpelledUnits(prose, withMath, allow = []) {
138
+ const allowed = new Set(allow.map((word) => word.toLowerCase()));
139
+ const isAllowed = (phrase) => {
140
+ const words = unitWordsIn(phrase);
141
+ return words.length > 0 && words.every((w) => allowed.has(w) || allowed.has(w.replace(/s$/, "")));
142
+ };
143
+ const found = [];
144
+ for (const pattern of PATTERNS) {
145
+ for (const m of prose.matchAll(new RegExp(pattern.source, pattern.flags))) {
146
+ if (isAllowed(m[0])) continue;
147
+ found.push({ index: m.index, token: m[0], suggestion: suggest(m[0]) });
148
+ }
149
+ }
150
+ for (const m of withMath.matchAll(INLINE_MATH)) {
151
+ const content = m[1];
152
+ if (!/\d\s*$/.test(content.replace(/\\[,;:! ]|~|\\quad/g, ""))) continue;
153
+ const after = withMath.slice(m.index + m[0].length);
154
+ const unit = AFTER_MATH.exec(after);
155
+ if (unit === null || isAllowed(unit[1])) continue;
156
+ const at = m.index + m[0].length + unit[0].indexOf(unit[1]);
157
+ if (prose.slice(at, at + unit[1].length) !== unit[1]) continue;
158
+ const latex = unitLatex(unit[1]);
159
+ const typeset = latex === void 0 ? "the unit inside the same `$\u2026$` as its number" : `\`$${content.trim()}${latex.startsWith("^") ? "" : "\\ "}${latex}$\``;
160
+ found.push({
161
+ index: m.index,
162
+ token: `${m[0]}${unit[0]}`,
163
+ suggestion: typeset
164
+ });
165
+ }
166
+ return merge(found);
167
+ }
168
+ export {
169
+ findSpelledUnits
170
+ };
@@ -0,0 +1,176 @@
1
+ /**
2
+ * The rule catalog — every authoring rule and every always-on invariant the
3
+ * engine enforces, as data.
4
+ *
5
+ * The prose of a rule (rationale, how to apply, examples) lives in
6
+ * `docs/authoring/<area>.md`; this module holds only what a machine needs:
7
+ * the permanent ID, the element(s) a rule affects, the diagnostic codes it
8
+ * owns and where they are emitted, its settings, and whether a course may
9
+ * turn it off. `scripts/agent/gen-rules.mjs` renders the README index from
10
+ * it and checks the `.md` files against it; `tests/rules/catalog.test.ts`
11
+ * fails when a code is emitted that no entry owns, or when two entries claim
12
+ * one code. See `docs/plans/rule-catalog.md` for the design.
13
+ *
14
+ * Two kinds of entry share the catalog:
15
+ *
16
+ * - A **rule** is a choice — a house style that could have gone another way.
17
+ * It has a lettered ID and, unless `locked`, a checkbox in the course
18
+ * settings (`rules.<ID>.enabled`). A rule that is retired leaves this list
19
+ * for the compact {@link RetiredRule} one, which costs a reader four lines
20
+ * instead of a whole entry.
21
+ * - An **invariant** is what the syntax means — a `[[key]]` must resolve, a
22
+ * `\label{eq:…}` key is unique. One entry per feature, always on,
23
+ * documented next to the syntax rather than as a rule page.
24
+ *
25
+ * Enforcement is never typed by hand: it is derived from `checks` and
26
+ * `unchecked` by {@link enforcementOf}.
27
+ */
28
+ /** The part of a page a rule is about. The first tag on an entry is primary. */
29
+ type Element = 'prose' | 'math' | 'equation' | 'notation' | 'heading' | 'part-page' | 'plot' | 'diagram' | 'table' | 'assessment' | 'source' | 'changelog';
30
+ /** Every element, in the order the rules-in-effect page lists them. */
31
+ declare const ELEMENTS: readonly Element[];
32
+ /**
33
+ * The five areas the engine ships, plus `X` — reserved for a course's own rules
34
+ * and never used by an engine rule (see {@link CourseRuleId}).
35
+ */
36
+ type RuleArea = 'N' | 'V' | 'C' | 'S' | 'P' | 'X';
37
+ type RuleId = `${RuleArea}${number}`;
38
+ /**
39
+ * A course's own rule, defined in `courseRules` in the course settings rather
40
+ * than here (`src/rules/course-rules.ts`). `X` is permanently reserved for
41
+ * them: the engine never ships an `X` rule, so a course can number its own
42
+ * `X1`, `X2`, … without a future engine release colliding with them. The form
43
+ * is the same as an engine rule's, so one ID works everywhere — a commit
44
+ * message, a diagnostic, a waiver, the rules-in-effect page.
45
+ */
46
+ type CourseRuleId = `X${number}`;
47
+ /** True for a well-formed course-rule ID (`X1`…`X99`). */
48
+ declare function isCourseRuleId(id: string): id is CourseRuleId;
49
+ type Severity = 'error' | 'warning' | 'info';
50
+ /** Where a code is emitted: engine source module + the function that pushes it. */
51
+ interface Emitter {
52
+ /** Repo-relative engine source path, e.g. `src/reference/gate-heading-length.ts`. */
53
+ readonly module: string;
54
+ /** A `function`/`const` name in that module; existence-checked by the tests. */
55
+ readonly fn: string;
56
+ }
57
+ interface RuleCheck {
58
+ /** The `code` the diagnostic carries. Unique across the whole catalog. */
59
+ readonly code: string;
60
+ readonly severity: Severity;
61
+ readonly emitters: readonly [Emitter, ...Emitter[]];
62
+ /**
63
+ * True when a lesson-body emission carries `file` + `line`, so a per-place
64
+ * waiver can target it. Corpus-level and per-file checks are `false`.
65
+ */
66
+ readonly located: boolean;
67
+ }
68
+ /** A course-settable parameter of a rule — a tiny JSON-Schema subset. */
69
+ type SettingSchema = {
70
+ readonly type: 'integer';
71
+ readonly minimum?: number;
72
+ readonly default: number;
73
+ } | {
74
+ readonly type: 'boolean';
75
+ readonly default: boolean;
76
+ } | {
77
+ readonly type: 'array';
78
+ readonly items: 'string';
79
+ readonly default: readonly string[];
80
+ };
81
+ interface SettingSpec {
82
+ readonly key: string;
83
+ readonly title: string;
84
+ readonly description: string;
85
+ readonly schema: SettingSchema;
86
+ }
87
+ interface RuleEntry {
88
+ readonly kind: 'rule';
89
+ /** Permanent. A rule that is retired keeps its ID in {@link RetiredRule}. */
90
+ readonly id: RuleId;
91
+ /** The bold first sentence of the rule page, verbatim. */
92
+ readonly title: string;
93
+ /** Index-table phrasing, one line; the README row uses it over `title`. */
94
+ readonly short?: string;
95
+ readonly elements: readonly [Element, ...Element[]];
96
+ /**
97
+ * Path + anchor under `docs/authoring/`, e.g. `notation.md#n9`. On a course
98
+ * rule (`source: 'course'`) it is instead a link the course owns, used as
99
+ * given.
100
+ */
101
+ readonly doc: string;
102
+ /** What an agent needs in order to comply — at most three sentences, no rationale. */
103
+ readonly summary: string;
104
+ readonly checks: readonly RuleCheck[];
105
+ /** What no script sees. Present → enforcement is `partial`. */
106
+ readonly unchecked?: string;
107
+ /** Why the rule has no checkbox. */
108
+ readonly locked?: string;
109
+ readonly settings?: readonly SettingSpec[];
110
+ /**
111
+ * Set only on the synthetic entry built from a course's own `courseRules`
112
+ * block (`courseRuleEntry`). The static catalog never sets it, so `source`
113
+ * absent means "shipped with the engine".
114
+ */
115
+ readonly source?: 'course';
116
+ }
117
+ interface InvariantEntry {
118
+ readonly kind: 'invariant';
119
+ /** A feature slug, e.g. `notation-references`. */
120
+ readonly id: string;
121
+ readonly title: string;
122
+ readonly elements: readonly [Element, ...Element[]];
123
+ readonly doc: string;
124
+ readonly summary: string;
125
+ readonly checks: readonly [RuleCheck, ...RuleCheck[]];
126
+ }
127
+ type CatalogEntry = RuleEntry | InvariantEntry;
128
+ type Enforcement = 'script' | 'partial' | 'guide';
129
+ /**
130
+ * A rule that is no longer in force. It keeps its ID forever — an old commit
131
+ * message, review comment or waiver naming `N3` must stay unambiguous — but it
132
+ * is deliberately *not* a {@link RuleEntry}: nothing iterates it as a rule, it
133
+ * has no page in `docs/authoring/`, it is absent from the settings schema and
134
+ * from `build/rules-in-effect.md`, and an agent never reads it while drafting.
135
+ * The one place it is written out is the generated table in
136
+ * `docs/authoring/retired.md`, which is where a migration guide links.
137
+ */
138
+ interface RetiredRule {
139
+ readonly id: RuleId;
140
+ /** The sentence the rule had, so an old reference to it still resolves. */
141
+ readonly title: string;
142
+ /** `YYYY-MM-DD`. */
143
+ readonly date: string;
144
+ /** The rule it was folded into, when it was folded into one. */
145
+ readonly into?: RuleId;
146
+ /** Why, and what changed for an author — one or two sentences. */
147
+ readonly why: string;
148
+ }
149
+ declare const CATALOG: readonly CatalogEntry[];
150
+ /** The rule or invariant that owns a diagnostic code, if any. */
151
+ declare function ownerOf(code: string): CatalogEntry | undefined;
152
+ /**
153
+ * The **rule** ID a diagnostic code belongs to — a lettered engine rule, or a
154
+ * course rule, whose single check carries the rule's own ID as its code.
155
+ * `undefined` for a code owned by an invariant (there is no rule page to read)
156
+ * and for a code nothing owns.
157
+ */
158
+ declare function ruleIdOf(code: string): RuleId | undefined;
159
+ declare function entryById(id: string): CatalogEntry | undefined;
160
+ declare function ruleById(id: string): RuleEntry | undefined;
161
+ declare function rules(): readonly RuleEntry[];
162
+ /** Every retired rule, oldest ID first. Read by the generated table and nothing else. */
163
+ declare function retiredRules(): readonly RetiredRule[];
164
+ /** The retired rule with this ID, if the ID belonged to one. */
165
+ declare function retiredRule(id: string): RetiredRule | undefined;
166
+ declare function invariants(): readonly InvariantEntry[];
167
+ /**
168
+ * `script` — every violation a check can see is reported; `partial` — checks
169
+ * exist but the rule says what they miss; `guide` — nothing runs, a reviewer
170
+ * (or the agent's own reading) is the enforcement.
171
+ */
172
+ declare function enforcementOf(entry: CatalogEntry): Enforcement;
173
+ /** Every code the catalog owns, sorted. */
174
+ declare function ownedCodes(): readonly string[];
175
+
176
+ export { CATALOG, type CatalogEntry, type CourseRuleId, ELEMENTS, type Element, type Emitter, type Enforcement, type InvariantEntry, type RetiredRule, type RuleArea, type RuleCheck, type RuleEntry, type RuleId, type SettingSchema, type SettingSpec, type Severity, enforcementOf, entryById, invariants, isCourseRuleId, ownedCodes, ownerOf, retiredRule, retiredRules, ruleById, ruleIdOf, rules };