explico 0.8.0 → 0.11.1
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.
- package/CHANGELOG.md +1257 -1
- package/README.md +60 -15
- package/dist/compiler/astro.d.ts +6 -1
- package/dist/compiler/cli.d.ts +46 -2
- package/dist/compiler/cli.js +70 -4
- package/dist/compiler/collections.d.ts +35 -2
- package/dist/compiler/collections.js +68 -5
- package/dist/compiler/course-config.d.ts +3 -194
- package/dist/compiler/course-config.js +78 -5
- package/dist/compiler/exports/standalone-html.d.ts +6 -1
- package/dist/compiler/knowledge-version.js +1 -1
- package/dist/compiler/lesson-derivation.d.ts +27 -10
- package/dist/compiler/lesson-derivation.js +23 -8
- package/dist/compiler/logging.d.ts +4 -1
- package/dist/compiler/logging.js +4 -1
- package/dist/compiler/manifest.d.ts +204 -31
- package/dist/compiler/manifest.js +270 -55
- package/dist/compiler/resolution-report.d.ts +6 -1
- package/dist/course-config-Dsnri9Tk.d.ts +464 -0
- package/dist/reference/base-accents.d.mts +26 -0
- package/dist/reference/base-accents.mjs +31 -0
- package/dist/reference/base-roman-labels.d.mts +2 -2
- package/dist/reference/base-roman-labels.mjs +2 -2
- package/dist/reference/consistency.d.ts +14 -2
- package/dist/reference/consistency.js +108 -4
- package/dist/reference/delimiter-size.d.ts +3 -2
- package/dist/reference/diagrams-validate.d.ts +7 -2
- package/dist/reference/diagrams-validate.js +9 -1
- package/dist/reference/equations-validate.d.ts +8 -5
- package/dist/reference/equations-validate.js +1 -0
- package/dist/reference/equations.d.mts +60 -31
- package/dist/reference/equations.mjs +83 -43
- package/dist/reference/examples-validate.d.ts +31 -0
- package/dist/reference/examples-validate.js +48 -0
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/figures-validate.js +4 -1
- package/dist/reference/folds-validate.d.ts +33 -0
- package/dist/reference/folds-validate.js +32 -0
- package/dist/reference/gate-abbreviations.d.ts +3 -1
- package/dist/reference/gate-abbreviations.js +1 -0
- package/dist/reference/gate-course-rules.d.ts +32 -0
- package/dist/reference/gate-course-rules.js +85 -0
- package/dist/reference/gate-heading-length.d.ts +5 -2
- package/dist/reference/gate-heading-length.js +45 -21
- package/dist/reference/gate-heading-structure.d.ts +41 -0
- package/dist/reference/gate-heading-structure.js +113 -0
- package/dist/reference/gate-math-punctuation.d.ts +71 -0
- package/dist/reference/gate-math-punctuation.js +251 -0
- package/dist/reference/gate-math.d.ts +22 -6
- package/dist/reference/gate-math.js +42 -8
- package/dist/reference/gate-part-pages.d.ts +50 -0
- package/dist/reference/gate-part-pages.js +118 -0
- package/dist/reference/gate-prose-notation.d.ts +21 -11
- package/dist/reference/gate-prose-notation.js +25 -7
- package/dist/reference/katex-delimiters.mjs +20 -7
- package/dist/reference/keyed-float-validate.d.ts +27 -10
- package/dist/reference/keyed-float-validate.js +15 -1
- package/dist/reference/keyed-jsx-floats.d.mts +11 -5
- package/dist/reference/keyed-jsx-floats.mjs +73 -20
- package/dist/reference/knowledge-checks-validate.d.ts +1 -1
- package/dist/reference/knowledge-checks.d.mts +3 -1
- package/dist/reference/knowledge-checks.mjs +9 -4
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-slot.d.ts +34 -0
- package/dist/reference/lab-math-slot.js +6 -0
- package/dist/reference/math-glyphs.d.mts +80 -4
- package/dist/reference/math-glyphs.mjs +572 -37
- package/dist/reference/page-notation.d.ts +6 -1
- package/dist/reference/raw-floats-validate.js +1 -1
- package/dist/reference/raw-floats.mjs +1 -1
- package/dist/reference/rehype-heading-anchors.d.mts +14 -0
- package/dist/reference/rehype-heading-anchors.mjs +116 -17
- package/dist/reference/rehype-table-header-labels.d.mts +6 -1
- package/dist/reference/rehype-table-header-labels.mjs +21 -3
- package/dist/reference/remark-asides.d.mts +3 -0
- package/dist/reference/remark-asides.mjs +180 -0
- package/dist/reference/remark-memo.d.mts +3 -1
- package/dist/reference/remark-memo.mjs +42 -11
- package/dist/reference/remark-notation.d.mts +60 -10
- package/dist/reference/remark-notation.mjs +387 -36
- package/dist/reference/render-lab-math.d.ts +4 -26
- package/dist/reference/render-lab-math.js +4 -4
- package/dist/reference/sections-validate.d.ts +27 -0
- package/dist/reference/sections-validate.js +41 -0
- package/dist/reference/sections.d.mts +97 -0
- package/dist/reference/sections.mjs +306 -0
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/types.d.ts +17 -1
- package/dist/reference/unit-words.d.ts +37 -0
- package/dist/reference/unit-words.js +170 -0
- package/dist/rules/catalog.d.ts +176 -0
- package/dist/rules/catalog.js +983 -0
- package/dist/rules/course-rules.d.ts +115 -0
- package/dist/rules/course-rules.js +292 -0
- package/dist/rules/course-rules.schema.json +138 -0
- package/dist/rules/in-effect.d.ts +53 -0
- package/dist/rules/in-effect.js +142 -0
- package/dist/rules/rules.schema.json +1072 -0
- package/dist/rules/settings.d.ts +3 -0
- package/dist/rules/settings.js +290 -0
- package/dist/rules/waivers.d.ts +53 -0
- package/dist/rules/waivers.js +132 -0
- package/dist/session/PreferenceStore.d.ts +5 -0
- package/dist/session/PreferenceStore.js +9 -0
- package/docs/VERSIONING.md +5 -3
- package/docs/authoring/README.md +232 -40
- package/docs/authoring/agents.md +55 -9
- package/docs/authoring/components.md +178 -22
- package/docs/authoring/migrating.md +1152 -2
- package/docs/authoring/notation.md +576 -322
- package/docs/authoring/prose.md +551 -0
- package/docs/authoring/retired.md +35 -0
- package/docs/authoring/structure-rules.md +422 -40
- package/docs/authoring/structure.md +445 -62
- package/docs/authoring/versioning.md +20 -28
- package/docs/authoring/visualizations.md +142 -20
- package/package.json +2 -2
- package/src/compiler/cli.ts +116 -6
- package/src/compiler/collections.ts +111 -7
- package/src/compiler/course-config.ts +273 -34
- package/src/compiler/knowledge-version.ts +1 -1
- package/src/compiler/lesson-derivation.ts +54 -15
- package/src/compiler/logging.ts +8 -2
- package/src/compiler/manifest.ts +573 -104
- package/src/components/BlockHeading.astro +113 -0
- package/src/components/CourseContents.astro +221 -0
- package/src/components/CurriculumMap.astro +5 -6
- package/src/components/FloatIndex.astro +200 -0
- package/src/components/HoverPanel.astro +431 -79
- package/src/components/PartBridge.astro +27 -0
- package/src/components/PartOverview.astro +401 -0
- package/src/components/PartScope.astro +27 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +49 -51
- package/src/components/assessments/AssessmentRunner.tsx +8 -5
- package/src/components/assessments/AssessmentSet.astro +23 -26
- package/src/components/block-heading.ts +146 -0
- package/src/components/card-arrow.ts +243 -0
- package/src/components/card-drag.ts +631 -0
- package/src/components/card-jump.ts +45 -0
- package/src/components/card-manager.ts +1589 -0
- package/src/components/card-memory.ts +177 -0
- package/src/components/card-placement.ts +504 -0
- package/src/components/card-side.ts +336 -0
- package/src/components/card-travel.ts +197 -0
- package/src/components/citation/CitationLayer.astro +77 -76
- package/src/components/diagram/Diagram.astro +161 -316
- package/src/components/disclosure/course-disclosure.ts +24 -0
- package/src/components/disclosure/hover-preview.ts +122 -42
- package/src/components/disclosure/persisted-disclosure.ts +3 -3
- package/src/components/disclosure/print-as-shown.ts +252 -0
- package/src/components/examples/CompactExample.astro +3 -1
- package/src/components/examples/CompactExamples.astro +101 -94
- package/src/components/figure/Figure.astro +174 -274
- package/src/components/figure/FloatCaption.astro +87 -0
- package/src/components/figure/figure-state.ts +58 -0
- package/src/components/figure/figure-tools.ts +87 -0
- package/src/components/float-name.ts +101 -0
- package/src/components/fold/Fold.astro +125 -0
- package/src/components/fold/fold-id.ts +40 -0
- package/src/components/memo/Memo.astro +162 -231
- package/src/components/memo/ShortNote.astro +6 -6
- package/src/components/notation/NotationFormula.astro +5 -1
- package/src/components/notation/NotationGlossary.astro +3 -4
- package/src/components/notation/NotationLayer.astro +169 -162
- package/src/components/notation/NotationMath.astro +10 -1
- package/src/components/reference/FloatRefLayer.astro +485 -423
- package/src/components/starlight/AboutPanel.astro +5 -4
- package/src/components/starlight/CollapsibleSections.astro +207 -93
- package/src/components/starlight/DiagramEnhancer.astro +15 -138
- package/src/components/starlight/HeadingAnchorEnhancer.astro +193 -1
- package/src/components/starlight/KeyedFloatEnhancer.astro +100 -9
- package/src/components/starlight/LayoutHeader.astro +298 -19
- package/src/components/starlight/LayoutPageSidebar.astro +99 -12
- package/src/components/starlight/LayoutPageTitle.astro +120 -0
- package/src/components/starlight/LayoutSidebar.astro +229 -11
- package/src/components/starlight/LessonExportActions.astro +53 -72
- package/src/components/starlight/LessonFooter.astro +26 -4
- package/src/components/starlight/section-parts.ts +15 -2
- package/src/components/starlight/section-summary.ts +181 -0
- package/src/components/table/Table.astro +95 -76
- package/src/components/table/TableOverflowExpander.astro +65 -28
- package/src/content-config.ts +40 -4
- package/src/reference/base-accents.mjs +31 -0
- package/src/reference/base-roman-labels.mjs +2 -2
- package/src/reference/consistency.ts +176 -5
- package/src/reference/delimiter-size.ts +3 -2
- package/src/reference/diagrams-validate.ts +17 -1
- package/src/reference/equations-validate.ts +10 -5
- package/src/reference/equations.mjs +83 -43
- package/src/reference/examples-validate.ts +97 -0
- package/src/reference/figures-validate.ts +6 -1
- package/src/reference/folds-validate.ts +65 -0
- package/src/reference/gate-abbreviations.ts +1 -0
- package/src/reference/gate-course-rules.ts +134 -0
- package/src/reference/gate-heading-length.ts +66 -35
- package/src/reference/gate-heading-structure.ts +193 -0
- package/src/reference/gate-math-punctuation.ts +381 -0
- package/src/reference/gate-math.ts +69 -10
- package/src/reference/gate-part-pages.ts +217 -0
- package/src/reference/gate-prose-notation.ts +59 -12
- package/src/reference/katex-delimiters.mjs +20 -7
- package/src/reference/keyed-float-validate.ts +45 -11
- package/src/reference/keyed-jsx-floats.mjs +73 -20
- package/src/reference/knowledge-checks-validate.ts +1 -1
- package/src/reference/knowledge-checks.mjs +9 -4
- package/src/reference/lab-math-slot.ts +36 -0
- package/src/reference/math-glyphs.mjs +572 -37
- package/src/reference/raw-floats-validate.ts +1 -1
- package/src/reference/raw-floats.mjs +1 -1
- package/src/reference/rehype-heading-anchors.mjs +116 -17
- package/src/reference/rehype-table-header-labels.mjs +21 -3
- package/src/reference/remark-asides.mjs +180 -0
- package/src/reference/remark-memo.mjs +42 -11
- package/src/reference/remark-notation.mjs +387 -36
- package/src/reference/render-lab-math.ts +16 -31
- package/src/reference/sections-validate.ts +81 -0
- package/src/reference/sections.mjs +306 -0
- package/src/reference/tables-validate.ts +2 -1
- package/src/reference/types.ts +17 -0
- package/src/reference/unit-words.ts +233 -0
- package/src/rules/catalog.ts +1298 -0
- package/src/rules/course-rules.ts +456 -0
- package/src/rules/in-effect.ts +235 -0
- package/src/rules/settings.ts +448 -0
- package/src/rules/waivers.ts +249 -0
- package/src/session/PreferenceStore.ts +15 -0
- package/src/styles/global.css +1467 -163
- package/src/styles/surfaces.css +182 -0
- package/src/components/hover-panel.ts +0 -339
- package/src/components/popup-stack.ts +0 -345
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { CourseRuleId, Element, RuleEntry } from './catalog.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A course's own rules — `courseRules` in `course.settings.json`.
|
|
5
|
+
*
|
|
6
|
+
* The engine ships the `N` / `V` / `C` / `S` / `P` areas and a course tunes or
|
|
7
|
+
* turns them off through `rules` (`src/rules/settings.ts`). This is the other
|
|
8
|
+
* half: a classroom's house style, written and edited by the people who teach
|
|
9
|
+
* the course, with no engine change and no fork. `X` is permanently reserved
|
|
10
|
+
* for them, so an `X` rule reads like any other rule everywhere it appears —
|
|
11
|
+
* a diagnostic, a waiver, the rules-in-effect page, a review comment.
|
|
12
|
+
*
|
|
13
|
+
* ```json
|
|
14
|
+
* "courseRules": [
|
|
15
|
+
* {
|
|
16
|
+
* "id": "X1",
|
|
17
|
+
* "title": "A paragraph never ends in an exclamation.",
|
|
18
|
+
* "summary": "End a paragraph on a statement…",
|
|
19
|
+
* "severity": "error",
|
|
20
|
+
* "forbid": [{ "scope": "paragraph", "pattern": "Ni!$", "message": "…" }]
|
|
21
|
+
* },
|
|
22
|
+
* { "id": "X2", "title": "Every fifth paragraph is four sentences.", "enabled": false }
|
|
23
|
+
* ]
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* A rule with a `forbid` pattern is **enforced**: the pattern is matched
|
|
27
|
+
* against prose the same way rule N9's gate sees it (`gate-course-rules.ts`),
|
|
28
|
+
* and each match is a diagnostic at the rule's `severity`, carrying the rule's
|
|
29
|
+
* ID as its code — so `enforcementOf` calls it `script`, and it can be waived
|
|
30
|
+
* at one place with `{/* explico-allow X1: reason *\/}` like any located rule.
|
|
31
|
+
* A rule with no pattern is a **guide**: it appears on the rules-in-effect page
|
|
32
|
+
* for an author or an agent to follow, and nothing runs. `enabled: false`
|
|
33
|
+
* keeps a rule in the file and moves it to the page's "Off" list — the way to
|
|
34
|
+
* park a house rule that is drafted but not yet in force.
|
|
35
|
+
*
|
|
36
|
+
* Everything here is validated strictly and throws: the settings file is the
|
|
37
|
+
* course's own, a mistake in it is a mistake the author wants to hear about
|
|
38
|
+
* before a build runs with a rule that silently does nothing.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/** Where a `forbid` pattern is matched. */
|
|
42
|
+
type CourseRuleScope = 'paragraph' | 'prose' | 'heading';
|
|
43
|
+
declare const COURSE_RULE_SCOPES: readonly CourseRuleScope[];
|
|
44
|
+
/**
|
|
45
|
+
* One forbidden pattern, as the settings file gives it. `scope` is a plain
|
|
46
|
+
* `string` on purpose: this is the shape of untrusted JSON, and a
|
|
47
|
+
* `course.settings.json` imported by `course.config.ts` widens every literal to
|
|
48
|
+
* `string`, so a union here would make the course's own file fail to typecheck.
|
|
49
|
+
* The value is checked at resolve time, with the setting's path in the message.
|
|
50
|
+
*/
|
|
51
|
+
interface CourseRulePatternInput {
|
|
52
|
+
readonly pattern: string;
|
|
53
|
+
readonly flags?: string;
|
|
54
|
+
readonly scope?: string;
|
|
55
|
+
readonly message?: string;
|
|
56
|
+
}
|
|
57
|
+
/** One course rule, as the settings file gives it. See {@link CourseRulePatternInput}. */
|
|
58
|
+
interface CourseRuleInput {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly title: string;
|
|
61
|
+
readonly summary?: string;
|
|
62
|
+
readonly elements?: readonly string[];
|
|
63
|
+
readonly enabled?: boolean;
|
|
64
|
+
readonly severity?: string;
|
|
65
|
+
readonly forbid?: readonly CourseRulePatternInput[];
|
|
66
|
+
readonly unchecked?: string;
|
|
67
|
+
readonly doc?: string;
|
|
68
|
+
}
|
|
69
|
+
/** `CourseConfig.courseRules` — an array, so the settings form can add and remove one. */
|
|
70
|
+
type CourseRuleDefinitions = readonly CourseRuleInput[];
|
|
71
|
+
interface CourseRulePattern {
|
|
72
|
+
readonly pattern: string;
|
|
73
|
+
readonly flags: string;
|
|
74
|
+
readonly scope: CourseRuleScope;
|
|
75
|
+
readonly message: string;
|
|
76
|
+
}
|
|
77
|
+
/** A course rule with every default applied and every value checked. */
|
|
78
|
+
interface CourseRule {
|
|
79
|
+
readonly id: CourseRuleId;
|
|
80
|
+
readonly title: string;
|
|
81
|
+
readonly summary: string;
|
|
82
|
+
readonly elements: readonly [Element, ...Element[]];
|
|
83
|
+
readonly enabled: boolean;
|
|
84
|
+
readonly severity: 'error' | 'warning';
|
|
85
|
+
readonly forbid: readonly CourseRulePattern[];
|
|
86
|
+
readonly unchecked?: string;
|
|
87
|
+
readonly doc?: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Validate `config.courseRules` and apply the defaults. Throws on anything that
|
|
91
|
+
* would leave a rule doing something the author did not write: an ID outside
|
|
92
|
+
* the `X` namespace, a duplicate ID, an unknown key, a pattern that does not
|
|
93
|
+
* compile.
|
|
94
|
+
*/
|
|
95
|
+
declare function resolveCourseRules(config: {
|
|
96
|
+
readonly courseRules?: CourseRuleDefinitions;
|
|
97
|
+
}): readonly CourseRule[];
|
|
98
|
+
/**
|
|
99
|
+
* A course rule as a catalog entry, so the rules-in-effect page, the waiver
|
|
100
|
+
* validator and `enforcementOf` treat it like any other rule. Its one check
|
|
101
|
+
* carries the rule's ID as its code, and is `located` — every finding names a
|
|
102
|
+
* lesson file and line, which is what makes an inline waiver possible.
|
|
103
|
+
*/
|
|
104
|
+
declare function courseRuleEntry(rule: CourseRule): RuleEntry;
|
|
105
|
+
type JsonSchema = Record<string, unknown>;
|
|
106
|
+
/**
|
|
107
|
+
* The `courseRules` property for a course's `course.settings.schema.json`,
|
|
108
|
+
* written to `dist/rules/course-rules.schema.json` by
|
|
109
|
+
* `scripts/gen-rules-schema.mjs`. Self-contained (no `$ref`), so the settings
|
|
110
|
+
* form editor can inline it: an array of objects, which is what gives the form
|
|
111
|
+
* its add / remove / reorder controls.
|
|
112
|
+
*/
|
|
113
|
+
declare function courseRulesJsonSchema(): JsonSchema;
|
|
114
|
+
|
|
115
|
+
export { COURSE_RULE_SCOPES, type CourseRule, type CourseRuleDefinitions, type CourseRuleInput, type CourseRulePattern, type CourseRulePatternInput, type CourseRuleScope, courseRuleEntry, courseRulesJsonSchema, resolveCourseRules };
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ELEMENTS,
|
|
3
|
+
isCourseRuleId
|
|
4
|
+
} from "./catalog.js";
|
|
5
|
+
const COURSE_RULE_SCOPES = ["paragraph", "prose", "heading"];
|
|
6
|
+
const ALLOWED_FLAGS = "imsu";
|
|
7
|
+
const SETTINGS_DOC = "../content/course.settings.json";
|
|
8
|
+
function describe(value) {
|
|
9
|
+
return JSON.stringify(value) ?? String(value);
|
|
10
|
+
}
|
|
11
|
+
function at(index, id) {
|
|
12
|
+
return `CourseConfig courseRules[${index}]${id === void 0 ? "" : ` (${id})`}`;
|
|
13
|
+
}
|
|
14
|
+
function requireString(value, where) {
|
|
15
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
16
|
+
throw new Error(`${where} must be a non-empty string; received ${describe(value)}.`);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function resolveElements(value, where) {
|
|
21
|
+
if (value === void 0) return ["prose"];
|
|
22
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`${where}.elements must be a non-empty array of elements; received ${describe(value)}.`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
for (const element of value) {
|
|
28
|
+
if (!ELEMENTS.includes(element)) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`${where}.elements: ${describe(element)} is not an element. One of: ${ELEMENTS.join(", ")}.`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function resolvePattern(input, where, index, fallbackMessage) {
|
|
37
|
+
const spot = `${where}.forbid[${index}]`;
|
|
38
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`${spot} must be an object such as { "pattern": "\u2026" }; received ${describe(input)}.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const raw = input;
|
|
44
|
+
for (const key of Object.keys(raw)) {
|
|
45
|
+
if (!["pattern", "flags", "scope", "message"].includes(key)) {
|
|
46
|
+
throw new Error(`${spot}: unknown key \`${key}\`. Takes: pattern, flags, scope, message.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const pattern = requireString(raw.pattern, `${spot}.pattern`);
|
|
50
|
+
const flags = raw.flags === void 0 ? "" : requireString(raw.flags, `${spot}.flags`);
|
|
51
|
+
for (const flag of flags) {
|
|
52
|
+
if (!ALLOWED_FLAGS.includes(flag)) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`${spot}.flags: \`${flag}\` is not allowed. Use any of \`${ALLOWED_FLAGS}\` \u2014 \`g\` is always applied.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (new Set(flags).size !== flags.length) {
|
|
59
|
+
throw new Error(`${spot}.flags: ${describe(flags)} repeats a flag.`);
|
|
60
|
+
}
|
|
61
|
+
const scope = raw.scope === void 0 ? "paragraph" : raw.scope;
|
|
62
|
+
if (!COURSE_RULE_SCOPES.includes(scope)) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`${spot}.scope: ${describe(scope)} is not a scope. One of: ${COURSE_RULE_SCOPES.join(", ")}.`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
let compiled;
|
|
68
|
+
try {
|
|
69
|
+
compiled = new RegExp(pattern, `${flags}g`);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`${spot}.pattern: ${pattern} is not a valid regular expression \u2014 ${error instanceof Error ? error.message : String(error)}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (compiled.test("")) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`${spot}.pattern: ${pattern} matches the empty string, so it would flag every ${scope}. Make it match the text you mean to forbid.`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const message = raw.message === void 0 ? fallbackMessage : requireString(raw.message, `${spot}.message`);
|
|
81
|
+
return { pattern, flags, scope, message };
|
|
82
|
+
}
|
|
83
|
+
function resolveCourseRules(config) {
|
|
84
|
+
const given = config.courseRules;
|
|
85
|
+
if (given === void 0) return [];
|
|
86
|
+
if (!Array.isArray(given)) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`CourseConfig courseRules must be an array of rules; received ${describe(given)}. It is a list, not a map, so the settings form can add and remove one.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const seen = /* @__PURE__ */ new Map();
|
|
92
|
+
const out = [];
|
|
93
|
+
given.forEach((input, index) => {
|
|
94
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`${at(index)} must be an object such as { "id": "X1", "title": "\u2026" }; received ${describe(input)}.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const raw = input;
|
|
100
|
+
const known = ["id", "title", "summary", "elements", "enabled", "severity", "forbid", "unchecked", "doc"];
|
|
101
|
+
for (const key of Object.keys(raw)) {
|
|
102
|
+
if (!known.includes(key)) {
|
|
103
|
+
throw new Error(`${at(index)}: unknown key \`${key}\`. Takes: ${known.join(", ")}.`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const id = requireString(raw.id, `${at(index)}.id`);
|
|
107
|
+
if (!isCourseRuleId(id)) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`${at(index)}.id: ${describe(id)} is not a course-rule ID. A course's own rules are \`X1\`\u2026\`X99\` \u2014 the \`X\` area is reserved for them, and the engine never ships one, so no release can collide with yours. To change an engine rule, use \`rules\` instead.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const first = seen.get(id);
|
|
113
|
+
if (first !== void 0) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`${at(index, id)}.id: ${id} is already used by courseRules[${first}]. An ID names one rule.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
seen.set(id, index);
|
|
119
|
+
const where = at(index, id);
|
|
120
|
+
const title = requireString(raw.title, `${where}.title`);
|
|
121
|
+
const summary = raw.summary === void 0 ? title : requireString(raw.summary, `${where}.summary`);
|
|
122
|
+
const elements = resolveElements(raw.elements, where);
|
|
123
|
+
if (raw.enabled !== void 0 && typeof raw.enabled !== "boolean") {
|
|
124
|
+
throw new Error(`${where}.enabled must be true or false; received ${describe(raw.enabled)}.`);
|
|
125
|
+
}
|
|
126
|
+
if (raw.severity !== void 0 && raw.severity !== "error" && raw.severity !== "warning") {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`${where}.severity must be "error" or "warning"; received ${describe(raw.severity)}.`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const forbidInput = raw.forbid;
|
|
132
|
+
if (forbidInput !== void 0 && !Array.isArray(forbidInput)) {
|
|
133
|
+
throw new Error(`${where}.forbid must be an array of patterns; received ${describe(forbidInput)}.`);
|
|
134
|
+
}
|
|
135
|
+
const forbid = (forbidInput ?? []).map(
|
|
136
|
+
(pattern, i) => resolvePattern(pattern, where, i, title)
|
|
137
|
+
);
|
|
138
|
+
if (raw.severity !== void 0 && forbid.length === 0) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`${where}.severity is set but the rule has no \`forbid\` pattern, so nothing is reported. Add a pattern, or drop \`severity\` and keep it a guide rule.`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
out.push({
|
|
144
|
+
id,
|
|
145
|
+
title,
|
|
146
|
+
summary,
|
|
147
|
+
elements,
|
|
148
|
+
enabled: raw.enabled !== false,
|
|
149
|
+
severity: raw.severity ?? "error",
|
|
150
|
+
forbid,
|
|
151
|
+
...raw.unchecked === void 0 ? {} : { unchecked: requireString(raw.unchecked, `${where}.unchecked`) },
|
|
152
|
+
...raw.doc === void 0 ? {} : { doc: requireString(raw.doc, `${where}.doc`) }
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
function courseRuleEntry(rule) {
|
|
158
|
+
const checks = rule.forbid.length === 0 ? [] : [
|
|
159
|
+
{
|
|
160
|
+
code: rule.id,
|
|
161
|
+
severity: rule.severity,
|
|
162
|
+
located: true,
|
|
163
|
+
emitters: [
|
|
164
|
+
{ module: "src/reference/gate-course-rules.ts", fn: "gateCourseRules" }
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
];
|
|
168
|
+
return {
|
|
169
|
+
kind: "rule",
|
|
170
|
+
id: rule.id,
|
|
171
|
+
title: rule.title,
|
|
172
|
+
elements: rule.elements,
|
|
173
|
+
doc: rule.doc ?? SETTINGS_DOC,
|
|
174
|
+
summary: rule.summary,
|
|
175
|
+
checks,
|
|
176
|
+
...rule.unchecked === void 0 ? {} : { unchecked: rule.unchecked },
|
|
177
|
+
source: "course"
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function courseRulesJsonSchema() {
|
|
181
|
+
return {
|
|
182
|
+
title: "Course rules",
|
|
183
|
+
description: "This course's own authoring rules, on top of the engine's. IDs are `X1`\u2026`X99` \u2014 the `X` area is reserved for a course, so no engine release collides with one. A rule with a `forbid` pattern is enforced by the build; a rule without one is a guide an author or an agent reads. Every rule here appears in `build/rules-in-effect.md` beside the engine's, and can be waived at one place with `{/* explico-allow X1: reason */}`.",
|
|
184
|
+
type: "array",
|
|
185
|
+
default: [],
|
|
186
|
+
items: {
|
|
187
|
+
title: "Course rule",
|
|
188
|
+
type: "object",
|
|
189
|
+
additionalProperties: false,
|
|
190
|
+
required: ["id", "title"],
|
|
191
|
+
properties: {
|
|
192
|
+
id: {
|
|
193
|
+
title: "ID",
|
|
194
|
+
description: "Permanent name of this rule, `X1`\u2026`X99`. It is what a diagnostic, a waiver and a review comment say. Never renumber a rule that is in use.",
|
|
195
|
+
type: "string",
|
|
196
|
+
pattern: "^X[1-9][0-9]?$",
|
|
197
|
+
"x-patternMessage": "An `X` and a number, 1\u201399 \u2014 for example `X1`."
|
|
198
|
+
},
|
|
199
|
+
title: {
|
|
200
|
+
title: "Rule",
|
|
201
|
+
description: 'One imperative sentence, as a rule page would open: "A paragraph never ends in an exclamation."',
|
|
202
|
+
type: "string",
|
|
203
|
+
minLength: 1,
|
|
204
|
+
"x-widget": "textarea"
|
|
205
|
+
},
|
|
206
|
+
summary: {
|
|
207
|
+
title: "What to do",
|
|
208
|
+
description: "One to three sentences telling an author or an agent how to comply \u2014 no rationale. This is what `build/rules-in-effect.md` prints. Defaults to the rule sentence.",
|
|
209
|
+
type: "string",
|
|
210
|
+
"x-widget": "textarea"
|
|
211
|
+
},
|
|
212
|
+
elements: {
|
|
213
|
+
title: "Elements",
|
|
214
|
+
description: "The parts of a page this rule is about; the first is primary and decides which section of the rules-in-effect page lists it.",
|
|
215
|
+
type: "array",
|
|
216
|
+
default: ["prose"],
|
|
217
|
+
items: { title: "Element", type: "string", enum: [...ELEMENTS] }
|
|
218
|
+
},
|
|
219
|
+
enabled: {
|
|
220
|
+
title: "Enabled",
|
|
221
|
+
description: 'Off: the rule stays in this file and moves to the "Off" list on the rules-in-effect page \u2014 nothing runs, and an agent neither applies it nor flags it in review. Use it to park a rule that is drafted but not in force.',
|
|
222
|
+
type: "boolean",
|
|
223
|
+
default: true
|
|
224
|
+
},
|
|
225
|
+
severity: {
|
|
226
|
+
title: "Severity",
|
|
227
|
+
description: "What a match of a `forbid` pattern is: an error fails `pnpm validate:content`, a warning is reported and does not. Only for a rule that has a pattern.",
|
|
228
|
+
type: "string",
|
|
229
|
+
enum: ["error", "warning"],
|
|
230
|
+
default: "error"
|
|
231
|
+
},
|
|
232
|
+
forbid: {
|
|
233
|
+
title: "Forbidden patterns",
|
|
234
|
+
description: "What makes this rule enforced. Each entry is a regular expression matched against the lesson text \u2014 with code, math, MDX comments and JSX tags blanked out first, exactly as rule N9 sees prose. Leave empty for a guide rule.",
|
|
235
|
+
type: "array",
|
|
236
|
+
default: [],
|
|
237
|
+
items: {
|
|
238
|
+
title: "Pattern",
|
|
239
|
+
type: "object",
|
|
240
|
+
additionalProperties: false,
|
|
241
|
+
required: ["pattern"],
|
|
242
|
+
properties: {
|
|
243
|
+
pattern: {
|
|
244
|
+
title: "Regular expression",
|
|
245
|
+
description: "JavaScript regular-expression source, e.g. `Ni!\\s*$`. It must not match the empty string.",
|
|
246
|
+
type: "string",
|
|
247
|
+
minLength: 1
|
|
248
|
+
},
|
|
249
|
+
scope: {
|
|
250
|
+
title: "Matched against",
|
|
251
|
+
description: "paragraph \u2014 each block of prose separated by a blank line, so `^` and `$` are its start and end. prose \u2014 the lesson's whole prose, so a pattern can span paragraphs. heading \u2014 the text of each Markdown heading.",
|
|
252
|
+
type: "string",
|
|
253
|
+
enum: [...COURSE_RULE_SCOPES],
|
|
254
|
+
default: "paragraph"
|
|
255
|
+
},
|
|
256
|
+
flags: {
|
|
257
|
+
title: "Flags",
|
|
258
|
+
description: "Any of `i` (ignore case), `m` (`^`/`$` match each line), `s` (`.` matches a newline), `u` (unicode). `g` is always applied.",
|
|
259
|
+
type: "string",
|
|
260
|
+
pattern: "^[imsu]*$",
|
|
261
|
+
"x-patternMessage": "Only the letters i, m, s and u."
|
|
262
|
+
},
|
|
263
|
+
message: {
|
|
264
|
+
title: "Message",
|
|
265
|
+
description: "What the build says at each match. Defaults to the rule sentence. Say what to do, not only what is wrong.",
|
|
266
|
+
type: "string",
|
|
267
|
+
"x-widget": "textarea"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
unchecked: {
|
|
273
|
+
title: "Not checked",
|
|
274
|
+
description: 'What the patterns cannot see, in one clause ("that the four sentences are in OSV order"). On a rule that has patterns it marks the rule `partial`, so a reader knows a green build is not the whole rule; on a guide rule it says why no pattern could do the job.',
|
|
275
|
+
type: "string",
|
|
276
|
+
"x-widget": "textarea"
|
|
277
|
+
},
|
|
278
|
+
doc: {
|
|
279
|
+
title: "Full text",
|
|
280
|
+
description: "Link to the course page that states this rule in full, as it should appear on the rules-in-effect page (a path relative to `build/`, or a URL). Defaults to the settings file itself.",
|
|
281
|
+
type: "string"
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
export {
|
|
288
|
+
COURSE_RULE_SCOPES,
|
|
289
|
+
courseRuleEntry,
|
|
290
|
+
courseRulesJsonSchema,
|
|
291
|
+
resolveCourseRules
|
|
292
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$comment": "Generated by scripts/gen-rules-schema.mjs from src/rules/course-rules.ts — do not edit.",
|
|
4
|
+
"title": "Course rules",
|
|
5
|
+
"description": "This course's own authoring rules, on top of the engine's. IDs are `X1`…`X99` — the `X` area is reserved for a course, so no engine release collides with one. A rule with a `forbid` pattern is enforced by the build; a rule without one is a guide an author or an agent reads. Every rule here appears in `build/rules-in-effect.md` beside the engine's, and can be waived at one place with `{/* explico-allow X1: reason */}`.",
|
|
6
|
+
"type": "array",
|
|
7
|
+
"default": [],
|
|
8
|
+
"items": {
|
|
9
|
+
"title": "Course rule",
|
|
10
|
+
"type": "object",
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"required": [
|
|
13
|
+
"id",
|
|
14
|
+
"title"
|
|
15
|
+
],
|
|
16
|
+
"properties": {
|
|
17
|
+
"id": {
|
|
18
|
+
"title": "ID",
|
|
19
|
+
"description": "Permanent name of this rule, `X1`…`X99`. It is what a diagnostic, a waiver and a review comment say. Never renumber a rule that is in use.",
|
|
20
|
+
"type": "string",
|
|
21
|
+
"pattern": "^X[1-9][0-9]?$",
|
|
22
|
+
"x-patternMessage": "An `X` and a number, 1–99 — for example `X1`."
|
|
23
|
+
},
|
|
24
|
+
"title": {
|
|
25
|
+
"title": "Rule",
|
|
26
|
+
"description": "One imperative sentence, as a rule page would open: \"A paragraph never ends in an exclamation.\"",
|
|
27
|
+
"type": "string",
|
|
28
|
+
"minLength": 1,
|
|
29
|
+
"x-widget": "textarea"
|
|
30
|
+
},
|
|
31
|
+
"summary": {
|
|
32
|
+
"title": "What to do",
|
|
33
|
+
"description": "One to three sentences telling an author or an agent how to comply — no rationale. This is what `build/rules-in-effect.md` prints. Defaults to the rule sentence.",
|
|
34
|
+
"type": "string",
|
|
35
|
+
"x-widget": "textarea"
|
|
36
|
+
},
|
|
37
|
+
"elements": {
|
|
38
|
+
"title": "Elements",
|
|
39
|
+
"description": "The parts of a page this rule is about; the first is primary and decides which section of the rules-in-effect page lists it.",
|
|
40
|
+
"type": "array",
|
|
41
|
+
"default": [
|
|
42
|
+
"prose"
|
|
43
|
+
],
|
|
44
|
+
"items": {
|
|
45
|
+
"title": "Element",
|
|
46
|
+
"type": "string",
|
|
47
|
+
"enum": [
|
|
48
|
+
"prose",
|
|
49
|
+
"math",
|
|
50
|
+
"equation",
|
|
51
|
+
"notation",
|
|
52
|
+
"heading",
|
|
53
|
+
"part-page",
|
|
54
|
+
"plot",
|
|
55
|
+
"diagram",
|
|
56
|
+
"table",
|
|
57
|
+
"assessment",
|
|
58
|
+
"source",
|
|
59
|
+
"changelog"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"enabled": {
|
|
64
|
+
"title": "Enabled",
|
|
65
|
+
"description": "Off: the rule stays in this file and moves to the \"Off\" list on the rules-in-effect page — nothing runs, and an agent neither applies it nor flags it in review. Use it to park a rule that is drafted but not in force.",
|
|
66
|
+
"type": "boolean",
|
|
67
|
+
"default": true
|
|
68
|
+
},
|
|
69
|
+
"severity": {
|
|
70
|
+
"title": "Severity",
|
|
71
|
+
"description": "What a match of a `forbid` pattern is: an error fails `pnpm validate:content`, a warning is reported and does not. Only for a rule that has a pattern.",
|
|
72
|
+
"type": "string",
|
|
73
|
+
"enum": [
|
|
74
|
+
"error",
|
|
75
|
+
"warning"
|
|
76
|
+
],
|
|
77
|
+
"default": "error"
|
|
78
|
+
},
|
|
79
|
+
"forbid": {
|
|
80
|
+
"title": "Forbidden patterns",
|
|
81
|
+
"description": "What makes this rule enforced. Each entry is a regular expression matched against the lesson text — with code, math, MDX comments and JSX tags blanked out first, exactly as rule N9 sees prose. Leave empty for a guide rule.",
|
|
82
|
+
"type": "array",
|
|
83
|
+
"default": [],
|
|
84
|
+
"items": {
|
|
85
|
+
"title": "Pattern",
|
|
86
|
+
"type": "object",
|
|
87
|
+
"additionalProperties": false,
|
|
88
|
+
"required": [
|
|
89
|
+
"pattern"
|
|
90
|
+
],
|
|
91
|
+
"properties": {
|
|
92
|
+
"pattern": {
|
|
93
|
+
"title": "Regular expression",
|
|
94
|
+
"description": "JavaScript regular-expression source, e.g. `Ni!\\s*$`. It must not match the empty string.",
|
|
95
|
+
"type": "string",
|
|
96
|
+
"minLength": 1
|
|
97
|
+
},
|
|
98
|
+
"scope": {
|
|
99
|
+
"title": "Matched against",
|
|
100
|
+
"description": "paragraph — each block of prose separated by a blank line, so `^` and `$` are its start and end. prose — the lesson's whole prose, so a pattern can span paragraphs. heading — the text of each Markdown heading.",
|
|
101
|
+
"type": "string",
|
|
102
|
+
"enum": [
|
|
103
|
+
"paragraph",
|
|
104
|
+
"prose",
|
|
105
|
+
"heading"
|
|
106
|
+
],
|
|
107
|
+
"default": "paragraph"
|
|
108
|
+
},
|
|
109
|
+
"flags": {
|
|
110
|
+
"title": "Flags",
|
|
111
|
+
"description": "Any of `i` (ignore case), `m` (`^`/`$` match each line), `s` (`.` matches a newline), `u` (unicode). `g` is always applied.",
|
|
112
|
+
"type": "string",
|
|
113
|
+
"pattern": "^[imsu]*$",
|
|
114
|
+
"x-patternMessage": "Only the letters i, m, s and u."
|
|
115
|
+
},
|
|
116
|
+
"message": {
|
|
117
|
+
"title": "Message",
|
|
118
|
+
"description": "What the build says at each match. Defaults to the rule sentence. Say what to do, not only what is wrong.",
|
|
119
|
+
"type": "string",
|
|
120
|
+
"x-widget": "textarea"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"unchecked": {
|
|
126
|
+
"title": "Not checked",
|
|
127
|
+
"description": "What the patterns cannot see, in one clause (\"that the four sentences are in OSV order\"). On a rule that has patterns it marks the rule `partial`, so a reader knows a green build is not the whole rule; on a guide rule it says why no pattern could do the job.",
|
|
128
|
+
"type": "string",
|
|
129
|
+
"x-widget": "textarea"
|
|
130
|
+
},
|
|
131
|
+
"doc": {
|
|
132
|
+
"title": "Full text",
|
|
133
|
+
"description": "Link to the course page that states this rule in full, as it should appear on the rules-in-effect page (a path relative to `build/`, or a URL). Defaults to the settings file itself.",
|
|
134
|
+
"type": "string"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Element, Enforcement } from './catalog.js';
|
|
2
|
+
import { M as ManifestRules } from '../course-config-Dsnri9Tk.js';
|
|
3
|
+
import './course-rules.js';
|
|
4
|
+
|
|
5
|
+
/** The slice of a manifest the page needs. */
|
|
6
|
+
interface RulesInEffectInput {
|
|
7
|
+
readonly rules: ManifestRules;
|
|
8
|
+
readonly about: {
|
|
9
|
+
readonly engine: {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly version: string;
|
|
12
|
+
};
|
|
13
|
+
readonly material: {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
};
|
|
16
|
+
readonly knowledge: {
|
|
17
|
+
readonly version: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
interface RulesFilter {
|
|
22
|
+
/** Keep a rule whose elements (any, not just primary) include one of these. */
|
|
23
|
+
readonly elements?: readonly Element[];
|
|
24
|
+
readonly enforcement?: Enforcement | 'locked';
|
|
25
|
+
}
|
|
26
|
+
/** One rule as `content rules --json` reports it. */
|
|
27
|
+
interface RuleInEffect {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly kind: 'rule' | 'invariant';
|
|
30
|
+
/** `course` for one of this course's own `X` rules; `engine` for the rest. */
|
|
31
|
+
readonly source: 'engine' | 'course';
|
|
32
|
+
readonly title: string;
|
|
33
|
+
readonly elements: readonly Element[];
|
|
34
|
+
readonly enforcement: Enforcement | 'locked';
|
|
35
|
+
readonly enabled: boolean;
|
|
36
|
+
/** May be waived at one place with `{/* explico-allow <ID>: reason *\/}`. */
|
|
37
|
+
readonly waivable: boolean;
|
|
38
|
+
readonly codes: readonly string[];
|
|
39
|
+
readonly settings: Readonly<Record<string, unknown>>;
|
|
40
|
+
readonly summary: string;
|
|
41
|
+
readonly doc: string;
|
|
42
|
+
}
|
|
43
|
+
declare function rulesInEffect(input: RulesInEffectInput, filter?: RulesFilter): RuleInEffect[];
|
|
44
|
+
/**
|
|
45
|
+
* The page. With a filter, only the matching rules are listed and the
|
|
46
|
+
* header says so; the Off / Locked / Always-on sections still list IDs so
|
|
47
|
+
* the reader knows what was left out on purpose.
|
|
48
|
+
*/
|
|
49
|
+
declare function renderRulesInEffect(input: RulesInEffectInput, filter?: RulesFilter): string;
|
|
50
|
+
/** Write `build/rules-in-effect.md` under `root` (the course package). Returns the path. */
|
|
51
|
+
declare function writeRulesInEffect(input: RulesInEffectInput, root?: string): string;
|
|
52
|
+
|
|
53
|
+
export { type RuleInEffect, type RulesFilter, type RulesInEffectInput, renderRulesInEffect, rulesInEffect, writeRulesInEffect };
|