explico 0.14.0 → 0.15.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.
- package/CHANGELOG.md +112 -1
- package/dist/compiler/cli-main.d.ts +61 -4
- package/dist/compiler/cli-main.js +77 -36
- package/dist/compiler/cli-reference.d.ts +50 -0
- package/dist/compiler/cli-reference.js +213 -0
- package/dist/compiler/cli-verbs.d.ts +45 -0
- package/dist/compiler/cli-verbs.js +224 -0
- package/dist/compiler/cli-verify.d.ts +51 -0
- package/dist/compiler/cli-verify.js +151 -0
- package/dist/compiler/cli.d.ts +19 -50
- package/dist/compiler/cli.js +45 -6
- package/dist/compiler/content-release.d.ts +49 -0
- package/dist/compiler/content-release.js +63 -0
- package/dist/compiler/content-schemas.d.ts +2 -2
- package/dist/compiler/manifest.js +29 -24
- package/dist/compiler/page-versions.d.ts +25 -1
- package/dist/compiler/page-versions.js +19 -0
- package/dist/compiler/validate-content.js +2 -2
- package/dist/reference/consistency.js +7 -1
- package/dist/reference/diagram-fit.d.ts +5 -1
- package/dist/reference/diagram-fit.js +5 -1
- package/dist/rules/catalog.js +16 -0
- package/dist/rules/in-effect.d.ts +1 -1
- package/dist/rules/in-effect.js +5 -4
- package/dist/rules/rules.schema.json +21 -0
- package/docs/authoring/README.md +2 -0
- package/docs/authoring/agents.md +18 -11
- package/docs/authoring/cli-reference.json +2172 -0
- package/docs/authoring/cli.md +887 -0
- package/docs/authoring/deck.md +24 -0
- package/docs/authoring/migrating.md +168 -18
- package/docs/authoring/versioning.md +2 -1
- package/docs/course-agent/AGENTS.md +10 -7
- package/docs/course-agent/agents/rules-reviewer.md +2 -2
- package/docs/course-agent/skills/draft-lesson/SKILL.md +7 -6
- package/docs/course-agent/skills/migrate-course/SKILL.md +5 -4
- package/docs/course-agent/skills/release-content/SKILL.md +5 -3
- package/docs/diagram-scaling.md +2 -2
- package/package.json +1 -1
- package/src/compiler/cli-main.ts +135 -47
- package/src/compiler/cli-reference.ts +285 -0
- package/src/compiler/cli-verbs.ts +270 -0
- package/src/compiler/cli-verify.ts +241 -0
- package/src/compiler/cli.ts +77 -17
- package/src/compiler/content-release.ts +91 -0
- package/src/compiler/manifest.ts +34 -24
- package/src/compiler/page-versions.ts +42 -0
- package/src/compiler/validate-content.ts +5 -2
- package/src/reference/consistency.ts +13 -2
- package/src/reference/diagram-fit.ts +18 -4
- package/src/rules/catalog.ts +17 -0
- package/src/rules/in-effect.ts +5 -4
|
@@ -181,11 +181,30 @@ function syncPageVersions(manifest, options = {}) {
|
|
|
181
181
|
writeFileSync(path, expected);
|
|
182
182
|
return { path, stale: true, written: true, changed };
|
|
183
183
|
}
|
|
184
|
+
function deckStaleDiagnostics(decks, chapterSince) {
|
|
185
|
+
const out = [];
|
|
186
|
+
for (const deck of decks) {
|
|
187
|
+
if (deck.chapter === void 0) continue;
|
|
188
|
+
const chapterVersion = chapterSince.get(deck.chapter);
|
|
189
|
+
if (chapterVersion === void 0) continue;
|
|
190
|
+
if (compareKnowledgeVersions(chapterVersion, deck.since) <= 0) continue;
|
|
191
|
+
const chapterChange = chapterVersion === null ? "has unreleased changes" : `changed in version ${chapterVersion}`;
|
|
192
|
+
out.push({
|
|
193
|
+
context: "structure",
|
|
194
|
+
severity: "info",
|
|
195
|
+
code: "deck-stale",
|
|
196
|
+
file: deck.file,
|
|
197
|
+
message: `The chapter ${chapterChange}; this deck is still from version ${deck.since}.`
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
184
202
|
export {
|
|
185
203
|
PAGE_VERSIONS_FILE,
|
|
186
204
|
PAGE_VERSIONS_FORMAT,
|
|
187
205
|
PAGE_VERSIONS_GENERATED_BY,
|
|
188
206
|
compareKnowledgeVersions,
|
|
207
|
+
deckStaleDiagnostics,
|
|
189
208
|
newestSince,
|
|
190
209
|
nextPageVersions,
|
|
191
210
|
pageSourceDigest,
|
|
@@ -86,7 +86,7 @@ Run log: ${summary2.relativeDirectory}.`);
|
|
|
86
86
|
const consistencyWarnings = consistency.filter((diagnostic) => diagnostic.severity === "warning");
|
|
87
87
|
if (consistencyWarnings.length > 0) {
|
|
88
88
|
console.warn(
|
|
89
|
-
log.
|
|
89
|
+
log.info(
|
|
90
90
|
"consistency",
|
|
91
91
|
`[consistency] ${consistencyWarnings.length} Tier-3 finding(s): ` + Object.entries(consistencyCounts(consistencyWarnings)).filter(([, count]) => count > 0).map(([code, count]) => `${code}=${count}`).join(", ")
|
|
92
92
|
)
|
|
@@ -94,7 +94,7 @@ Run log: ${summary2.relativeDirectory}.`);
|
|
|
94
94
|
}
|
|
95
95
|
log.infoDiagnostics(manifest.diagnostics);
|
|
96
96
|
for (const diagnostic of consistencyWarnings) {
|
|
97
|
-
log.
|
|
97
|
+
log.warning("consistency", `[${diagnostic.code}] ${diagnostic.message}`);
|
|
98
98
|
}
|
|
99
99
|
const reports = syncResolutionReports(manifest);
|
|
100
100
|
console.log(
|
|
@@ -159,6 +159,10 @@ function checkDefinitionRepeated(input, out) {
|
|
|
159
159
|
byKey.set(definition.key, list);
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
+
const sharedByGlyph = /* @__PURE__ */ new Map();
|
|
163
|
+
for (const definition of input.notationInput.sharedDefinitions) {
|
|
164
|
+
if (!sharedByGlyph.has(definition.notation.trim())) sharedByGlyph.set(definition.notation.trim(), definition.key);
|
|
165
|
+
}
|
|
162
166
|
for (const [key, copies] of [...byKey].sort(([a], [b]) => a.localeCompare(b))) {
|
|
163
167
|
if (copies.length < 2) continue;
|
|
164
168
|
if (input.lintIgnore.matches("definition-repeated", key)) continue;
|
|
@@ -167,6 +171,8 @@ function checkDefinitionRepeated(input, out) {
|
|
|
167
171
|
(field) => copies.some((copy) => JSON.stringify(copy.definition[field] ?? null) !== JSON.stringify(first.definition[field] ?? null))
|
|
168
172
|
);
|
|
169
173
|
const lessons = copies.map((copy) => copy.lessonId).sort();
|
|
174
|
+
const owner = sharedByGlyph.get(first.definition.notation.trim());
|
|
175
|
+
const advice = owner !== void 0 && owner !== key ? ` \u2014 its glyph is already shared card ${owner}'s, so it is a reused letter (rule N4) and promoting it would put two shared cards on one glyph (glyph-unique-in-corpus): keep the copies local and acknowledge them in lint-ignore.yml ("definition-repeated:${key} \u2014 <why>"), or, if they are two meanings, give one another key` : ` \u2014 move one copy to content/notation/${key}.md and delete the others: every [[${key}]] and \\explain{${key}} keeps working; if they are two meanings, give one another key`;
|
|
170
176
|
out.push({
|
|
171
177
|
code: "definition-repeated",
|
|
172
178
|
severity: "warning",
|
|
@@ -174,7 +180,7 @@ function checkDefinitionRepeated(input, out) {
|
|
|
174
180
|
lessonId: first.lessonId,
|
|
175
181
|
file: first.file,
|
|
176
182
|
count: copies.length,
|
|
177
|
-
message: `local entry ${key} (${first.definition.notation}) is declared in ${copies.length} lessons (${lessons.join(", ")}); ` + (differing.length === 0 ? "the copies agree" : `the copies differ in ${differing.join(", ")}`) +
|
|
183
|
+
message: `local entry ${key} (${first.definition.notation}) is declared in ${copies.length} lessons (${lessons.join(", ")}); ` + (differing.length === 0 ? "the copies agree" : `the copies differ in ${differing.join(", ")}`) + advice
|
|
178
184
|
});
|
|
179
185
|
}
|
|
180
186
|
}
|
|
@@ -29,7 +29,11 @@ declare const DEFAULT_COLUMN_WIDTH = 707;
|
|
|
29
29
|
* `warnAboveWidth` replaces the course's column width for that diagram.
|
|
30
30
|
*/
|
|
31
31
|
declare function diagramWidthLimit(warnAboveWidth: number | false | undefined, rules: Pick<ManifestRules, 'disabled' | 'settings'> | undefined): number | undefined;
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* What to tell the author about one diagram wider than its limit. The
|
|
34
|
+
* competency graph is `<CurriculumMap>`'s, whose nodes the author does not
|
|
35
|
+
* write, so its advice names the map's own props.
|
|
36
|
+
*/
|
|
33
37
|
declare function diagramTooWideMessage(id: string, width: number, limit: number): string;
|
|
34
38
|
/** A diagram on a built page that is wider than its limit. */
|
|
35
39
|
interface OversizedDiagram {
|
|
@@ -7,7 +7,11 @@ function diagramWidthLimit(warnAboveWidth, rules) {
|
|
|
7
7
|
return typeof columnWidth === "number" ? columnWidth : DEFAULT_COLUMN_WIDTH;
|
|
8
8
|
}
|
|
9
9
|
function diagramTooWideMessage(id, width, limit) {
|
|
10
|
-
|
|
10
|
+
const head = `Diagram "${id}" lays out ${Math.round(width)}px wide, wider than the ${limit}px it may take (rule V8), so a reader has to scroll it or widen it to see it whole. `;
|
|
11
|
+
if (id.startsWith("competency-graph")) {
|
|
12
|
+
return head + `It is <CurriculumMap>'s competency graph: try graph="per-part" (one graph per part) and direction="LR" or "TB" on the map; set warnAboveWidth={false} there when no layout fits and the graph is meant to scroll.`;
|
|
13
|
+
}
|
|
14
|
+
return head + `Try direction="TB", a shorter label, or a narrower node \`width\` (its label wraps inside it); set warnAboveWidth={false} only when the diagram is meant to scroll.`;
|
|
11
15
|
}
|
|
12
16
|
function isElement(node) {
|
|
13
17
|
return "tagName" in node;
|
package/dist/rules/catalog.js
CHANGED
|
@@ -1050,6 +1050,22 @@ const RULES = [
|
|
|
1050
1050
|
doc: "deck.md#d10",
|
|
1051
1051
|
summary: "The title frame (the lesson, the course, its authors and version) and the closing frame (the outcomes of the competencies the lesson teaches, `deck.outcomesFrame`) are generated: do not write them. With `deck.outcomesFrame` off, end on a `###` frame that says what the student can now do.",
|
|
1052
1052
|
checks: []
|
|
1053
|
+
},
|
|
1054
|
+
{
|
|
1055
|
+
kind: "rule",
|
|
1056
|
+
id: "D11",
|
|
1057
|
+
editions: "deck",
|
|
1058
|
+
short: "A deck is flagged when its chapter changed after it did",
|
|
1059
|
+
title: "A deck is flagged when its chapter changed, by rule C5\u2019s version, after the deck itself last did.",
|
|
1060
|
+
elements: ["slide", "changelog"],
|
|
1061
|
+
doc: "deck.md#d11",
|
|
1062
|
+
summary: "No authoring action by itself: two files mean the chapter and the deck can drift apart with nothing to notice it. `deck-stale` is an info diagnostic naming a lesson\u2019s own deck whose chapter\u2019s rule C5 version is newer than the deck\u2019s; read the chapter\u2019s change and judge whether the deck needs it too. Scoped to a lesson\u2019s own deck \u2014 a part\u2019s deck covers more than one chapter, so it has none to compare against.",
|
|
1063
|
+
checks: [
|
|
1064
|
+
check("deck-stale", "info", false, {
|
|
1065
|
+
module: "src/compiler/page-versions.ts",
|
|
1066
|
+
fn: "deckStaleDiagnostics"
|
|
1067
|
+
})
|
|
1068
|
+
]
|
|
1053
1069
|
}
|
|
1054
1070
|
];
|
|
1055
1071
|
const RETIRED = [
|
package/dist/rules/in-effect.js
CHANGED
|
@@ -93,7 +93,8 @@ function renderRulesInEffect(input, filter = {}) {
|
|
|
93
93
|
""
|
|
94
94
|
] : [],
|
|
95
95
|
"Generated by `pnpm validate:content` (and `pnpm content rules`) from the rule",
|
|
96
|
-
"catalog and `rules` / `courseRules`
|
|
96
|
+
"catalog and the course's `rules` / `courseRules` (`course.settings.json`, or",
|
|
97
|
+
"`course.config.ts`). Do not edit. Read this before",
|
|
97
98
|
"drafting, and open a rule page only when it is listed here and you need the",
|
|
98
99
|
"example: a rule absent from this page is either off for this course or does",
|
|
99
100
|
"not exist, and is neither applied nor flagged in review.",
|
|
@@ -104,8 +105,8 @@ function renderRulesInEffect(input, filter = {}) {
|
|
|
104
105
|
].filter(Boolean).join("; ")} \u2014 ${visible.length} shown.` : ""),
|
|
105
106
|
"",
|
|
106
107
|
"A line is `ID \xB7 enforcement \xB7 codes \u2014 what to do (settings in effect) \u2192 full",
|
|
107
|
-
"text`. **course**: this course wrote the rule in `courseRules` in",
|
|
108
|
-
"`course.settings.json
|
|
108
|
+
"text`. **course**: this course wrote the rule in `courseRules` (in",
|
|
109
|
+
"`course.settings.json` or `course.config.ts`), where it can be edited; the `X` area is reserved for",
|
|
109
110
|
"them. **script**: the build reports every violation under those codes.",
|
|
110
111
|
"**partial**: the build reports the codes; the rest is on you. **guide**: nothing",
|
|
111
112
|
"runs; it is on you. **locked**: script, and the course cannot turn it off.",
|
|
@@ -134,7 +135,7 @@ function renderRulesInEffect(input, filter = {}) {
|
|
|
134
135
|
}
|
|
135
136
|
lines.push("", "## Off", "");
|
|
136
137
|
lines.push(
|
|
137
|
-
offHere.length === 0 ? "- Nothing \u2014 every rule is on." : `- ${offHere.join(", ")} \u2014 turned off in
|
|
138
|
+
offHere.length === 0 ? "- Nothing \u2014 every rule is on." : `- ${offHere.join(", ")} \u2014 turned off in the course's \`rules\` (or \`courseRules\` for an \`X\` rule), in \`course.settings.json\` or \`course.config.ts\`. Do not apply them; do not flag them in review.`
|
|
138
139
|
);
|
|
139
140
|
lines.push("", "## Locked", "");
|
|
140
141
|
lines.push(`- ${locked.join(", ")} \u2014 cannot be turned off; listed above under their element.`);
|
|
@@ -244,6 +244,27 @@
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
},
|
|
247
|
+
"D11": {
|
|
248
|
+
"title": "D11 — A deck is flagged when its chapter changed after it did",
|
|
249
|
+
"description": "No authoring action by itself: two files mean the chapter and the deck can drift apart with nothing to notice it. `deck-stale` is an info diagnostic naming a lesson’s own deck whose chapter’s rule C5 version is newer than the deck’s; read the chapter’s change and judge whether the deck needs it too. Scoped to a lesson’s own deck — a part’s deck covers more than one chapter, so it has none to compare against.",
|
|
250
|
+
"x-element": "slide",
|
|
251
|
+
"x-elements": [
|
|
252
|
+
"slide",
|
|
253
|
+
"changelog"
|
|
254
|
+
],
|
|
255
|
+
"x-enforcement": "script",
|
|
256
|
+
"x-doc": "docs/authoring/deck.md#d11",
|
|
257
|
+
"type": "object",
|
|
258
|
+
"additionalProperties": false,
|
|
259
|
+
"properties": {
|
|
260
|
+
"enabled": {
|
|
261
|
+
"title": "Enabled",
|
|
262
|
+
"description": "Off: the check does not run, and the rule leaves the rules-in-effect page.",
|
|
263
|
+
"type": "boolean",
|
|
264
|
+
"default": true
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
},
|
|
247
268
|
"N9": {
|
|
248
269
|
"title": "N9 — A quantity/unit/operator is typeset in `$…$`, never as plain text",
|
|
249
270
|
"description": "Never write `45 m/s`, `t (s)`, `vx` or `KE/m` as plain text, and never spell a stated quantity's unit out: \"4 seconds\", \"2 meters per second squared\", \"30 degrees\", \"$x = 3$ meters\" are `$4\\ \\text{s}$`, `$2\\ \\text{m/s}^2$`, `$30^{\\circ}$`, `$x = 3\\ \\text{m}$`. A unit inside math is `\\text{}`-wrapped, short form, in the same span as its number or symbol. A unit named on its own (\"measured in meters\") stays a word; a sentence that defines a unit is waived. JSX prop strings (`caption=\"…\"`) render no math, so N9 does not apply there; nor does it apply to an assessment choice label, which may still hold math (`$30^{\\circ}$`).",
|
package/docs/authoring/README.md
CHANGED
|
@@ -241,6 +241,7 @@ of to the settings file.
|
|
|
241
241
|
| [deck.md](deck.md) | `D` | a lesson's deck: its frames, steps, notes and recalls, and what fits a frame |
|
|
242
242
|
| [components.md](components.md) | — | _catalog_: inline/block features available in a lesson body — tooltips, tables, figures, diagrams, memos, abbreviations, citations |
|
|
243
243
|
| [agents.md](agents.md) | — | _guide_: working on a course repo (with an AI agent) |
|
|
244
|
+
| [cli.md](cli.md) | — | _reference_, generated: every `content` verb, its flags, what it writes, and the shape of its `--json` ([cli-reference.json](cli-reference.json) as JSON Schema) |
|
|
244
245
|
| [migrating.md](migrating.md) | — | _guide_: per-version steps when bumping the `explico` dep |
|
|
245
246
|
| [retired.md](retired.md) | — | _history_: one generated row per rule no longer in force, and its successor |
|
|
246
247
|
|
|
@@ -308,6 +309,7 @@ of to the settings file.
|
|
|
308
309
|
| [D8](deck.md#d8) | slide, equation | Recall a chapter’s result; never retype it _(deck only)_ | Script — `frame-retyped` (warning) in [`decks-validate.ts`](../../src/reference/decks-validate.ts) `validateDecks` |
|
|
309
310
|
| [D9](deck.md#d9) | slide, math, notation | A frame introduces at most `maxNewSymbols` symbols _(deck only)_ — settings: `maxNewSymbols` = `4` | Script — `frame-new-symbols` (info) in [`decks-validate.ts`](../../src/reference/decks-validate.ts) `validateDecks` |
|
|
310
311
|
| [D10](deck.md#d10) | slide | A deck opens on its title and closes on what the student can now do _(deck only)_ | Guide |
|
|
312
|
+
| [D11](deck.md#d11) | slide, changelog | A deck is flagged when its chapter changed after it did _(deck only)_ | Script — `deck-stale` (info) in [`page-versions.ts`](../../src/compiler/page-versions.ts) `deckStaleDiagnostics` |
|
|
311
313
|
|
|
312
314
|
_Element_ is what the rule is about; the first tag is primary. _Enforcement_ is
|
|
313
315
|
derived from the catalog: **Script** — every violation a check can see is
|
package/docs/authoring/agents.md
CHANGED
|
@@ -24,7 +24,7 @@ and a rule disagree, the engine wins — see [`README.md`](README.md).
|
|
|
24
24
|
- **Read the rules in effect first, one rule at a time after that.**
|
|
25
25
|
`pnpm content rule <ID>` prints one rule whole: the entry as this course
|
|
26
26
|
has it and the page's own section with its example, about half a thousand
|
|
27
|
-
tokens. `pnpm --filter <course> content rules`
|
|
27
|
+
tokens (N4, the largest, about three thousand). `pnpm --filter <course> content rules`
|
|
28
28
|
(or `build/rules-in-effect.md` after a `validate:content`) lists every rule
|
|
29
29
|
this course has *on*, grouped by the element it is about — prose, math,
|
|
30
30
|
equation, notation, heading, part-page, plot, diagram, table, assessment,
|
|
@@ -46,17 +46,22 @@ and a rule disagree, the engine wins — see [`README.md`](README.md).
|
|
|
46
46
|
([README.md § Rules of your own](README.md#rules-of-your-own)) — never by
|
|
47
47
|
editing this folder.
|
|
48
48
|
- **Start from the CLI, not a file hunt.** Run it from the course package;
|
|
49
|
-
`pnpm content help` lists every verb,
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
`pnpm content help` lists every verb, `pnpm content help <verb>` prints one
|
|
50
|
+
verb's flags and the shape of its `--json` ([`cli.md`](cli.md) has them all),
|
|
51
|
+
and every verb takes `--json` for the whole report. Read what it prints
|
|
52
|
+
before opening files. The table under "The CLI" says which verb answers which
|
|
53
|
+
question.
|
|
52
54
|
- **Validate once.** `pnpm --filter <course> validate:content` rewrites
|
|
53
|
-
`build/resolution/*` and
|
|
54
|
-
|
|
55
|
-
commits the rewritten
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
`build/resolution/*` and fails only on a report git tracks that it had to
|
|
56
|
+
rewrite. A course whose `build/` is git-ignored passes on the first run; one
|
|
57
|
+
that commits `build/resolution/` commits the rewritten reports with the
|
|
58
|
+
change and runs it again (after an engine upgrade every report is
|
|
59
|
+
rewritten).
|
|
60
|
+
- **Before handing work back:** `pnpm content verify` runs `validate:content`
|
|
61
|
+
→ `content check --changed` → `astro check` → the course's tests →
|
|
62
|
+
`astro build` (`--e2e` adds the browser tests), one line per step and the
|
|
63
|
+
tail of a failed step's log; a rerun on an unchanged tree answers from its
|
|
64
|
+
last pass. `astro build` is the only step that runs every gate the CI does.
|
|
60
65
|
|
|
61
66
|
## The CLI
|
|
62
67
|
|
|
@@ -83,6 +88,8 @@ release that introduced it). Read-only except `new` and `init-agent`, and
|
|
|
83
88
|
| the mechanical repairs | `fix <lesson> [--write]` | redundant `\explain` wraps unwrapped, as a diff |
|
|
84
89
|
| a draft to start from | `new lesson <id>`, `new deck <lesson \| part>`, `new term <key>` | the files it created; never overwrites |
|
|
85
90
|
| install these procedures | `init-agent [--write]` | the skills and the `rules-reviewer` agent copied into `.claude/`, a baseline `AGENTS.md` when the course has none |
|
|
91
|
+
| is it ready to hand back | `verify [--e2e] [--force]` | one line per step — validate, check, `astro check`, tests, `astro build` — and a failed step's log tail; exit 1 if one failed |
|
|
92
|
+
| a verb's flags and `--json` | `help [<verb>]` | the usage; with a verb, its flags, what it writes, its exit code and its `--json` type |
|
|
86
93
|
|
|
87
94
|
The procedures — drafting a lesson, upgrading the engine, releasing the
|
|
88
95
|
course's content, reviewing a lesson against the guide-only rules — ship in
|