Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions docs/LESSON-FORMAT.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,17 +291,29 @@ sets:
version: '1.0.0'
lesson_count: 1
domain: language
description: >-
…
metadata:
author: your-handle
license: CC-BY-SA-4.0
lessons:
- 01-greetings.json
```

Then add the same set block to the **root** `manifest.yaml` under `sets:`, and
add a row to the README table (keeping the totals line in sync).
Then add the same set block to the **root** `manifest.yaml` under `sets:`, plus
the set's `description`, and add a row to the README table (keeping the totals
line in sync).

A set is described twice, so each field has one home:

- The **root** entry carries the `description`. Write it there only; a reader
of the set manifest inherits it, so a second copy can only drift.
- The **set manifest** carries the lesson list (`metadata.lessons`).
- Every other field appears in both and must be **identical**: `title`,
`title_native`, `tags`, `visibility`, `version`, `lesson_count` and the
rest. `make lint` compares the two entries of every set
(`validateManifestPair`, `MANIFEST-ENTRY-MISMATCH`).
- Leave the lesson count out of the `title` and `description` prose, or keep
it equal to `lesson_count`: a digit count that differs is flagged
(`W-LESSON-COUNT-CLAIM`).

Run `python scripts/validate_content.py` until it prints
`All N set(s) passed validation.`
2 changes: 1 addition & 1 deletion schema/engine-version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.36.0
0.37.0
26 changes: 24 additions & 2 deletions scripts/validate_with_engine.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
* validated instead of refused - `make lint-warnings` used to shell out to the
* bare CLI (no registry) and died on ext content (content-test#71).
*/
import { validateLesson, validateLessonQuality, validateManifest } from "learn-content-engine";
import { validateLesson, validateLessonQuality, validateManifest, validateManifestPair } from "learn-content-engine";
import { readFileSync, readdirSync, statSync } from "node:fs";
import { join, relative } from "node:path";
import { parse as parseYaml } from "yaml";
Expand Down Expand Up @@ -283,6 +283,21 @@ function selfTest() {
console.log("self-test OK: the source language reaches the card-back script lint");
}

// Root and set manifest (learn-content-engine#246): a field both carry
// with different values is surfaced, as a warning or an error depending
// on the pinned engine's tier.
const setEntry = { id: "s1", title: "Set", target_language: "es", level: "A1", version: "1.0.0", lesson_count: 1 };
const pair = validateManifestPair(
{ name: "Root", sets: [{ ...setEntry, description: "Root text" }] },
{ name: "Set", sets: [{ ...setEntry, description: "Set text" }] },
);
if (![...pair.errors, ...pair.warnings].some((issue) => issue.id.endsWith("-MANIFEST-ENTRY-MISMATCH"))) {
failures++;
console.error("SELF-TEST FAIL: a root/set manifest mismatch must be surfaced (MANIFEST-ENTRY-MISMATCH)");
} else {
console.log("self-test OK: a root/set manifest mismatch is surfaced");
}

if (failures) return 1;
console.log(`\nSelf-test passed: the gate rejects all bad-lesson classes, applies the quality minimums, gates the extension tier, and surfaces author warnings.`);
return 0;
Expand Down Expand Up @@ -325,8 +340,15 @@ function validateAll(repoRoot, { showWarnings = false } = {}) {
if (showWarnings && res.warnings.length) warned.push({ file: rel, warnings: res.warnings });
} else if (rel.endsWith("manifest.yaml")) {
manifests += 1;
const res = validateManifest(parseYaml(readFileSync(file, "utf8")));
const setManifest = parseYaml(readFileSync(file, "utf8"));
const res = validateManifest(setManifest);
if (!res.valid) report(rel, res.errors);
// The set is described twice, here and in the root manifest; the two
// must agree (learn-content-engine#246). The engine's tier decides:
// a warning in 0.37.x, an error from the release that raises it.
const pair = validateManifestPair(rootManifest, setManifest);
if (!pair.valid) report(rel, pair.errors);
if (showWarnings && pair.warnings.length) warned.push({ file: rel, warnings: pair.warnings });
// Manifest warnings were collected for lessons but DROPPED here, so the
// set-level ordering gate (learn-content-engine#110, W-SET-ORDER-*)
// reached this runner and reported nothing. A connected gate that
Expand Down
4 changes: 0 additions & 4 deletions sets/de/python-basics/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ sets:
path: sets/de/python-basics
version: '1.0.0'
lesson_count: 15
description: >-
Fünfzehn Einführungslektionen Python, auf Deutsch erklärt,
vom ersten print() bis zu List Comprehensions, mit
lauffähigen Code-Beispielen und erwarteter Ausgabe.
tags:
- beginner
- programming
Expand Down
9 changes: 0 additions & 9 deletions sets/de/react-19/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,6 @@ sets:
path: sets/de/react-19
version: '1.0.0'
lesson_count: 8
description: >-
React 19 an lauffähigem Code: pro Lektion eine Komponente, acht
Übungen darauf. Deckt 19.0 (Actions, use, ref als Prop, Server
Components), 19.2 (gebündelte Suspense-Enthüllung, useEffectEvent,
Activity) und 19.3 (ViewTransition, Fragment Refs) ab. Übungstypen
mit Schwerpunkt auf Code lesen: reading-comprehension,
error-correction, parsons, dazu ordering, categorization,
multiple_choice, matching, cloze mit echtem Trägersatz und eine
benotete Abschlussprüfung.
tags:
- programming
- react
Expand Down
7 changes: 0 additions & 7 deletions sets/de/react-fortgeschritten/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,6 @@ sets:
path: sets/de/react-fortgeschritten
version: '1.0.1'
lesson_count: 8
description: >-
Teil 2 der React-Reihe (Fortgeschritten, B1): acht Lektionen mit
überwiegend Multiple-Choice, Single-Answer als cloze select-mode und
„Wähle alle zutreffenden" als cloze multiselect, plus matching und
kurze Lückentext-Items. Themen: useEffect, Datenfetching, useContext,
useRef, Performance, Listen/Keys, Formulare, Komposition/Patterns.
Distraktoren aus klassischen React-Irrtümern. React-18, Hooks-first.
tags:
- react
- javascript
Expand Down
7 changes: 0 additions & 7 deletions sets/de/react-grundlagen/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,6 @@ sets:
path: sets/de/react-grundlagen
version: '1.0.1'
lesson_count: 5
description: >-
React-Grundlagen auf Deutsch für Einsteiger: fünf Lektionen mit
insgesamt 31 Karten, Theorieschritten und überwiegend
Multiple-Choice-Übungen (picture_choice mit plausiblen
Distraktoren) plus wenigen Lückentext-Items (cloze) zu React als
Library, Komponenten, JSX, Props, State/useState, Events und
Grundprinzipien.
tags:
- react
- javascript
Expand Down
Loading