explico 0.13.1 → 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.
Files changed (256) hide show
  1. package/CHANGELOG.md +990 -1
  2. package/README.md +200 -30
  3. package/dist/compiler/astro.d.ts +15 -2
  4. package/dist/compiler/astro.js +40 -11
  5. package/dist/compiler/cli-main.d.ts +100 -0
  6. package/dist/compiler/cli-main.js +433 -0
  7. package/dist/compiler/cli-reference.d.ts +50 -0
  8. package/dist/compiler/cli-reference.js +213 -0
  9. package/dist/compiler/cli-verbs.d.ts +45 -0
  10. package/dist/compiler/cli-verbs.js +224 -0
  11. package/dist/compiler/cli-verify.d.ts +51 -0
  12. package/dist/compiler/cli-verify.js +151 -0
  13. package/dist/compiler/cli.d.ts +408 -24
  14. package/dist/compiler/cli.js +1328 -8
  15. package/dist/compiler/collections.d.ts +88 -1
  16. package/dist/compiler/collections.js +170 -25
  17. package/dist/compiler/content-release.d.ts +49 -0
  18. package/dist/compiler/content-release.js +63 -0
  19. package/dist/compiler/content-schemas.d.ts +2 -2
  20. package/dist/compiler/course-config.d.ts +1 -1
  21. package/dist/compiler/course-config.js +105 -1
  22. package/dist/compiler/decks.d.ts +161 -0
  23. package/dist/compiler/decks.js +235 -0
  24. package/dist/compiler/exports/combined-html.d.ts +5 -1
  25. package/dist/compiler/exports/deck-fit.d.ts +55 -0
  26. package/dist/compiler/exports/deck-fit.js +90 -0
  27. package/dist/compiler/exports/export-chapters.d.ts +37 -6
  28. package/dist/compiler/exports/export-chapters.js +64 -5
  29. package/dist/compiler/exports/export-pages.d.ts +30 -3
  30. package/dist/compiler/exports/export-pages.js +24 -1
  31. package/dist/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  32. package/dist/compiler/exports/latex/cache.d.ts +78 -0
  33. package/dist/compiler/exports/latex/cache.js +129 -0
  34. package/dist/compiler/exports/latex/capture.d.ts +10 -1
  35. package/dist/compiler/exports/latex/capture.js +3 -1
  36. package/dist/compiler/exports/latex/compile.d.ts +13 -2
  37. package/dist/compiler/exports/latex/compile.js +12 -1
  38. package/dist/compiler/exports/latex/deck.d.ts +92 -0
  39. package/dist/compiler/exports/latex/deck.js +349 -0
  40. package/dist/compiler/exports/latex/document.d.ts +51 -6
  41. package/dist/compiler/exports/latex/document.js +71 -12
  42. package/dist/compiler/exports/latex/explico-common.sty +198 -0
  43. package/dist/compiler/exports/latex/explico.cls +13 -183
  44. package/dist/compiler/exports/latex/serialize.d.ts +60 -4
  45. package/dist/compiler/exports/latex/serialize.js +4 -2
  46. package/dist/compiler/exports/latex-export.d.ts +24 -2
  47. package/dist/compiler/exports/latex-export.js +107 -26
  48. package/dist/compiler/exports/markdown/serialize.d.ts +24 -4
  49. package/dist/compiler/exports/markdown/serialize.js +85 -8
  50. package/dist/compiler/exports/markdown-export.d.ts +6 -1
  51. package/dist/compiler/exports/markdown-export.js +50 -11
  52. package/dist/compiler/exports/standalone-html.d.ts +10 -5
  53. package/dist/compiler/exports/standalone-html.js +3 -3
  54. package/dist/compiler/exports/static-tree.d.ts +38 -1
  55. package/dist/compiler/exports/static-tree.js +120 -4
  56. package/dist/compiler/knowledge-version.d.ts +26 -2
  57. package/dist/compiler/knowledge-version.js +27 -17
  58. package/dist/compiler/lesson-derivation.d.ts +143 -6
  59. package/dist/compiler/lesson-derivation.js +145 -13
  60. package/dist/compiler/manifest.d.ts +133 -4
  61. package/dist/compiler/manifest.js +364 -58
  62. package/dist/compiler/page-versions.d.ts +184 -0
  63. package/dist/compiler/page-versions.js +217 -0
  64. package/dist/compiler/render-diagnostics.d.ts +5 -1
  65. package/dist/compiler/resolution-report.d.ts +24 -6
  66. package/dist/compiler/resolution-report.js +22 -4
  67. package/dist/compiler/validate-content.d.ts +7 -0
  68. package/dist/compiler/validate-content.js +160 -0
  69. package/dist/{course-config-rRtME5UV.d.ts → course-config-BLtIdkei.d.ts} +164 -1
  70. package/dist/curriculum/competency-graph.d.ts +54 -0
  71. package/dist/curriculum/competency-graph.js +41 -0
  72. package/dist/reference/base-library.d.mts +8 -0
  73. package/dist/reference/base-library.mjs +19 -0
  74. package/dist/reference/consistency.d.ts +8 -4
  75. package/dist/reference/consistency.js +82 -26
  76. package/dist/reference/deck-remark.d.mts +123 -0
  77. package/dist/reference/deck-remark.mjs +600 -0
  78. package/dist/reference/deck-source.d.ts +107 -0
  79. package/dist/reference/deck-source.js +140 -0
  80. package/dist/reference/decks-validate.d.ts +133 -0
  81. package/dist/reference/decks-validate.js +359 -0
  82. package/dist/reference/derived-entries.d.ts +5 -1
  83. package/dist/reference/diagram-fit.d.ts +6 -2
  84. package/dist/reference/diagram-fit.js +5 -1
  85. package/dist/reference/figure-symbols.d.ts +23 -6
  86. package/dist/reference/figure-symbols.js +51 -15
  87. package/dist/reference/float-name.d.ts +43 -0
  88. package/dist/reference/float-name.js +58 -0
  89. package/dist/reference/folds-validate.d.ts +1 -1
  90. package/dist/reference/gate-abbreviations.d.ts +1 -1
  91. package/dist/reference/gate-indices.js +5 -4
  92. package/dist/reference/gate-math.d.ts +29 -2
  93. package/dist/reference/gate-math.js +99 -18
  94. package/dist/reference/gate-part-pages.d.ts +1 -1
  95. package/dist/reference/lab-katex-validate.d.ts +10 -6
  96. package/dist/reference/lab-katex-validate.js +17 -9
  97. package/dist/reference/lab-math-scope.js +29 -4
  98. package/dist/reference/math-glyphs.d.mts +25 -0
  99. package/dist/reference/math-glyphs.mjs +189 -25
  100. package/dist/reference/page-notation.d.ts +22 -3
  101. package/dist/reference/page-notation.js +32 -10
  102. package/dist/reference/references.d.ts +14 -1
  103. package/dist/reference/references.js +13 -0
  104. package/dist/reference/registry-scope.d.mts +33 -0
  105. package/dist/reference/registry-scope.mjs +34 -0
  106. package/dist/reference/registry.js +29 -10
  107. package/dist/reference/rehype-deck.d.mts +6 -0
  108. package/dist/reference/rehype-deck.mjs +286 -0
  109. package/dist/reference/rehype-heading-anchors.d.mts +20 -0
  110. package/dist/reference/rehype-heading-anchors.mjs +48 -6
  111. package/dist/reference/remark-notation.mjs +249 -40
  112. package/dist/reference/source-lines.d.mts +12 -0
  113. package/dist/reference/source-lines.mjs +65 -0
  114. package/dist/reference/types.d.ts +28 -0
  115. package/dist/rules/catalog.d.ts +21 -4
  116. package/dist/rules/catalog.js +237 -2
  117. package/dist/rules/course-rules.d.ts +5 -1
  118. package/dist/rules/course-rules.js +17 -3
  119. package/dist/rules/course-rules.schema.json +12 -0
  120. package/dist/rules/in-effect.d.ts +13 -3
  121. package/dist/rules/in-effect.js +34 -10
  122. package/dist/rules/rules.schema.json +280 -1
  123. package/dist/rules/settings.d.ts +1 -1
  124. package/dist/session/PreferenceStore.d.ts +6 -0
  125. package/dist/session/PreferenceStore.js +18 -1
  126. package/dist/session/deck-memory.d.ts +116 -0
  127. package/dist/session/deck-memory.js +143 -0
  128. package/dist/session/reading-memory.d.ts +96 -0
  129. package/dist/session/reading-memory.js +133 -0
  130. package/docs/VERSIONING.md +21 -18
  131. package/docs/authoring/README.md +31 -9
  132. package/docs/authoring/agents.md +72 -18
  133. package/docs/authoring/cli-reference.json +2172 -0
  134. package/docs/authoring/cli.md +887 -0
  135. package/docs/authoring/components.md +15 -0
  136. package/docs/authoring/deck.md +612 -0
  137. package/docs/authoring/migrating.md +1195 -16
  138. package/docs/authoring/notation.md +49 -6
  139. package/docs/authoring/structure.md +80 -1
  140. package/docs/authoring/versioning.md +56 -3
  141. package/docs/authoring/visualizations.md +7 -2
  142. package/docs/course-agent/AGENTS.md +40 -0
  143. package/docs/course-agent/agents/rules-reviewer.md +33 -0
  144. package/docs/course-agent/skills/draft-lesson/SKILL.md +38 -0
  145. package/docs/course-agent/skills/migrate-course/SKILL.md +32 -0
  146. package/docs/course-agent/skills/release-content/SKILL.md +26 -0
  147. package/docs/diagram-scaling.md +148 -0
  148. package/docs/privacy.md +59 -0
  149. package/package.json +8 -2
  150. package/src/compiler/astro.ts +60 -21
  151. package/src/compiler/cli-main.ts +557 -0
  152. package/src/compiler/cli-reference.ts +285 -0
  153. package/src/compiler/cli-verbs.ts +270 -0
  154. package/src/compiler/cli-verify.ts +241 -0
  155. package/src/compiler/cli.ts +2119 -170
  156. package/src/compiler/collections.ts +284 -25
  157. package/src/compiler/content-release.ts +91 -0
  158. package/src/compiler/course-config.ts +267 -0
  159. package/src/compiler/decks.ts +455 -0
  160. package/src/compiler/exports/deck-fit.ts +127 -0
  161. package/src/compiler/exports/export-chapters.ts +105 -6
  162. package/src/compiler/exports/export-pages.ts +46 -2
  163. package/src/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  164. package/src/compiler/exports/latex/cache.ts +183 -0
  165. package/src/compiler/exports/latex/capture.ts +5 -2
  166. package/src/compiler/exports/latex/compile.ts +18 -0
  167. package/src/compiler/exports/latex/deck.ts +419 -0
  168. package/src/compiler/exports/latex/document.ts +117 -21
  169. package/src/compiler/exports/latex/explico-common.sty +198 -0
  170. package/src/compiler/exports/latex/explico.cls +13 -183
  171. package/src/compiler/exports/latex/serialize.ts +25 -21
  172. package/src/compiler/exports/latex/texlive-packages.txt +6 -2
  173. package/src/compiler/exports/latex-export.ts +187 -45
  174. package/src/compiler/exports/markdown/serialize.ts +118 -9
  175. package/src/compiler/exports/markdown-export.ts +56 -11
  176. package/src/compiler/exports/standalone-html.ts +8 -7
  177. package/src/compiler/exports/static-tree.ts +175 -3
  178. package/src/compiler/knowledge-version.ts +56 -21
  179. package/src/compiler/lesson-derivation.ts +282 -16
  180. package/src/compiler/manifest.ts +568 -63
  181. package/src/compiler/page-versions.ts +401 -0
  182. package/src/compiler/resolution-report.ts +38 -7
  183. package/src/compiler/validate-content.ts +215 -0
  184. package/src/components/CourseContents.astro +124 -6
  185. package/src/components/CurriculumMap.astro +49 -1
  186. package/src/components/FloatIndex.astro +1 -1
  187. package/src/components/PartOverview.astro +51 -5
  188. package/src/components/assessments/AssessmentSet.astro +41 -15
  189. package/src/components/block-heading.ts +15 -6
  190. package/src/components/card-carry.ts +7 -4
  191. package/src/components/card-manager.ts +14 -2
  192. package/src/components/card-memory.ts +13 -9
  193. package/src/components/deck/DeckEnhancer.astro +13 -0
  194. package/src/components/deck/deck-context.ts +104 -0
  195. package/src/components/deck/deck-controller.ts +1287 -0
  196. package/src/components/deck/deck-map.ts +91 -0
  197. package/src/components/deck-lane.ts +163 -0
  198. package/src/components/diagram/Diagram.astro +131 -86
  199. package/src/components/disclosure/persisted-disclosure.ts +9 -1
  200. package/src/components/figure/Figure.astro +7 -2
  201. package/src/components/figure/FloatCaption.astro +5 -3
  202. package/src/components/fold/Fold.astro +2 -2
  203. package/src/components/notation/NotationFormula.astro +81 -11
  204. package/src/components/notation/NotationGlossary.astro +132 -1
  205. package/src/components/notation/NotationLayer.astro +15 -7
  206. package/src/components/starlight/AboutPanel.astro +186 -3
  207. package/src/components/starlight/CollapsibleSections.astro +24 -3
  208. package/src/components/starlight/DiagramEnhancer.astro +5 -1
  209. package/src/components/starlight/HeadingAnchorEnhancer.astro +1 -1
  210. package/src/components/starlight/LayoutHeader.astro +17 -0
  211. package/src/components/starlight/LayoutPageTitle.astro +110 -0
  212. package/src/components/starlight/LayoutSidebar.astro +807 -21
  213. package/src/components/starlight/LayoutSocialIcons.astro +62 -6
  214. package/src/components/starlight/LessonExportActions.astro +27 -9
  215. package/src/components/starlight/LessonFooter.astro +59 -0
  216. package/src/components/starlight/ReadingMemoryNotice.astro +114 -0
  217. package/src/components/starlight/TextSizeControl.astro +24 -13
  218. package/src/components/starlight/page-marks.ts +139 -0
  219. package/src/components/starlight/section-summary.ts +1 -1
  220. package/src/components/starlight/sidebar-editions.ts +245 -0
  221. package/src/components/table/Table.astro +15 -7
  222. package/src/content-config.ts +87 -1
  223. package/src/curriculum/competency-graph.ts +95 -0
  224. package/src/reference/base-library.mjs +19 -0
  225. package/src/reference/consistency.ts +137 -39
  226. package/src/reference/deck-remark.mjs +600 -0
  227. package/src/reference/deck-source.ts +273 -0
  228. package/src/reference/decks-validate.ts +604 -0
  229. package/src/reference/diagram-fit.ts +18 -4
  230. package/src/reference/diagram-layout.ts +3 -1
  231. package/src/reference/figure-symbols.ts +108 -31
  232. package/src/reference/folds-validate.ts +1 -1
  233. package/src/reference/gate-indices.ts +5 -3
  234. package/src/reference/gate-math.ts +169 -25
  235. package/src/reference/lab-katex-validate.ts +30 -15
  236. package/src/reference/lab-math-scope.ts +34 -3
  237. package/src/reference/math-glyphs.mjs +189 -25
  238. package/src/reference/page-notation.ts +69 -15
  239. package/src/reference/references.ts +28 -0
  240. package/src/reference/registry-scope.mjs +34 -0
  241. package/src/reference/registry.ts +41 -11
  242. package/src/reference/rehype-deck.mjs +286 -0
  243. package/src/reference/rehype-heading-anchors.mjs +48 -6
  244. package/src/reference/remark-notation.mjs +249 -40
  245. package/src/reference/source-lines.mjs +65 -0
  246. package/src/reference/types.ts +28 -0
  247. package/src/rules/catalog.ts +274 -4
  248. package/src/rules/course-rules.ts +21 -1
  249. package/src/rules/in-effect.ts +58 -11
  250. package/src/session/PreferenceStore.ts +23 -0
  251. package/src/session/deck-memory.ts +263 -0
  252. package/src/session/reading-memory.ts +243 -0
  253. package/src/styles/deck.css +1150 -0
  254. package/src/styles/global.css +958 -27
  255. package/src/styles/zones.css +277 -0
  256. /package/src/{components → reference}/float-name.ts +0 -0
@@ -0,0 +1,151 @@
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { join, relative } from "node:path";
5
+ const VERIFY_DIR = join("node_modules", ".cache", "explico", "verify");
6
+ const RECORD = "last-pass.json";
7
+ const TAIL_LINES = 20;
8
+ function verifyTreeHash(cwd) {
9
+ try {
10
+ const git = (args) => execFileSync("git", args, { cwd, maxBuffer: 1 << 30, stdio: ["ignore", "pipe", "ignore"] });
11
+ const hash = createHash("sha1");
12
+ hash.update(git(["rev-parse", "HEAD"]));
13
+ hash.update(git(["diff", "HEAD", "--binary"]));
14
+ const untracked = git(["ls-files", "--others", "--exclude-standard", "-z"]).toString("utf8").split("\0").filter(Boolean).sort();
15
+ for (const file of untracked) {
16
+ hash.update(`\0${file}\0`);
17
+ if (existsSync(join(cwd, file))) hash.update(readFileSync(join(cwd, file)));
18
+ }
19
+ return hash.digest("hex");
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+ function scriptCommand(script, args) {
25
+ const execpath = process.env.npm_execpath ?? "";
26
+ const manager = /pnpm/.test(execpath) ? "pnpm" : /yarn/.test(execpath) ? "yarn" : /npm/.test(execpath) ? "npm" : "pnpm";
27
+ const tail = manager === "npm" && args.length > 0 ? ["run", script, "--", ...args] : ["run", script, ...args];
28
+ const display = `${manager} ${tail.join(" ")}`;
29
+ if (execpath === "" || !execpath.includes(manager)) return { file: manager, argv: tail, display };
30
+ return /\.[cm]?js$/.test(execpath) ? { file: process.execPath, argv: [execpath, ...tail], display } : { file: execpath, argv: tail, display };
31
+ }
32
+ function binCommand(cwd, bin, args) {
33
+ return { file: join(cwd, "node_modules", ".bin", process.platform === "win32" ? `${bin}.cmd` : bin), argv: [...args], display: [bin, ...args].join(" ") };
34
+ }
35
+ function slug(name) {
36
+ return name.replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
37
+ }
38
+ function tailOf(file) {
39
+ const lines = readFileSync(file, "utf8").split("\n").map((line) => line.trimEnd()).filter((line) => line !== "");
40
+ return lines.slice(-TAIL_LINES);
41
+ }
42
+ function runVerify(options = {}) {
43
+ const cwd = options.cwd ?? process.cwd();
44
+ const e2e = options.e2e ?? false;
45
+ const dir = join(cwd, VERIFY_DIR);
46
+ const recordFile = join(dir, RECORD);
47
+ if (!options.force && existsSync(recordFile)) {
48
+ try {
49
+ const record = JSON.parse(readFileSync(recordFile, "utf8"));
50
+ const tree2 = verifyTreeHash(cwd);
51
+ if (tree2 !== null && record.tree === tree2 && (record.e2e || !e2e)) {
52
+ return { ok: true, cached: true, since: record.when, tree: tree2, e2e: record.e2e, steps: record.steps };
53
+ }
54
+ } catch {
55
+ }
56
+ }
57
+ mkdirSync(dir, { recursive: true });
58
+ const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
59
+ const scripts = pkg.scripts ?? {};
60
+ const has = (path) => existsSync(join(cwd, path));
61
+ const playwrightConfig = ["playwright.config.ts", "playwright.config.mts", "playwright.config.js", "playwright.config.mjs"].some(has);
62
+ const plans = [
63
+ scripts["validate:content"] === void 0 ? { name: "validate:content", reason: "package.json has no validate:content script", fail: true } : { name: "validate:content", command: scriptCommand("validate:content", []) },
64
+ scripts.content === void 0 ? { name: "check --changed", reason: "package.json has no content script", fail: true } : { name: "check --changed", command: scriptCommand("content", ["check", "--changed"]) },
65
+ // Without @astrojs/check, `astro check` stops to ask whether to install it.
66
+ has(join("node_modules", "@astrojs", "check", "package.json")) ? { name: "astro check", command: binCommand(cwd, "astro", ["check"]) } : { name: "astro check", reason: "@astrojs/check is not installed (pnpm add -D @astrojs/check typescript)" },
67
+ scripts.test === void 0 ? { name: "tests", reason: "package.json has no test script" } : { name: "tests", command: scriptCommand("test", []) },
68
+ { name: "astro build", command: binCommand(cwd, "astro", ["build"]) }
69
+ ];
70
+ if (e2e) {
71
+ plans.push(
72
+ scripts["test:e2e"] !== void 0 ? { name: "e2e", command: scriptCommand("test:e2e", []) } : playwrightConfig ? { name: "e2e", command: binCommand(cwd, "playwright", ["test"]) } : { name: "e2e", reason: "no test:e2e script and no playwright.config" }
73
+ );
74
+ }
75
+ const steps = [];
76
+ for (const plan of plans) {
77
+ options.onStep?.({ phase: "start", name: plan.name });
78
+ let step;
79
+ if (plan.command === void 0) {
80
+ step = { name: plan.name, status: plan.fail ? "fail" : "skipped", reason: plan.reason };
81
+ } else {
82
+ const log = join(dir, `${slug(plan.name)}.log`);
83
+ const fd = openSync(log, "w");
84
+ const started = Date.now();
85
+ let result;
86
+ try {
87
+ result = spawnSync(plan.command.file, plan.command.argv, {
88
+ cwd,
89
+ stdio: ["ignore", fd, fd],
90
+ shell: process.platform === "win32",
91
+ env: process.env
92
+ });
93
+ } finally {
94
+ closeSync(fd);
95
+ }
96
+ const seconds = Math.round((Date.now() - started) / 100) / 10;
97
+ const passed = result.error === void 0 && result.status === 0;
98
+ step = {
99
+ name: plan.name,
100
+ status: passed ? "pass" : "fail",
101
+ command: plan.command.display,
102
+ seconds,
103
+ log: relative(cwd, log),
104
+ ...result.error === void 0 ? {} : { reason: result.error.message },
105
+ ...passed ? {} : { tail: tailOf(log) }
106
+ };
107
+ }
108
+ steps.push(step);
109
+ options.onStep?.({ phase: "end", step });
110
+ }
111
+ const ok = steps.every((step) => step.status !== "fail");
112
+ const tree = verifyTreeHash(cwd);
113
+ if (ok && tree !== null) {
114
+ const record = { tree, e2e, when: (/* @__PURE__ */ new Date()).toISOString(), steps };
115
+ writeFileSync(recordFile, `${JSON.stringify(record, null, 2)}
116
+ `);
117
+ }
118
+ return { ok, cached: false, tree, e2e, steps };
119
+ }
120
+ function formatVerifyReport(report) {
121
+ if (report.cached) {
122
+ return `verify: PASS, unchanged since ${report.since} (${report.e2e ? "with" : "without"} e2e); --force reruns
123
+ `;
124
+ }
125
+ const out = [];
126
+ for (const step of report.steps) {
127
+ if (step.status !== "fail") continue;
128
+ out.push("", `${step.name} failed${step.reason === void 0 ? "" : `: ${step.reason}`}${step.log === void 0 ? "" : ` \u2014 last lines of ${step.log}:`}`);
129
+ for (const line of step.tail ?? []) out.push(` ${line}`);
130
+ }
131
+ const failed = report.steps.filter((step) => step.status === "fail").map((step) => step.name);
132
+ out.push(
133
+ "",
134
+ failed.length === 0 ? `verify: PASS${report.tree === null ? " (not a git repository: nothing recorded)" : ""}` : `verify: FAIL \u2014 ${failed.join(", ")}; whole output under ${VERIFY_DIR}/`
135
+ );
136
+ return `${out.join("\n").replace(/^\n/, "")}
137
+ `;
138
+ }
139
+ function formatVerifyStep(step) {
140
+ const verdict = step.status === "pass" ? "ok" : step.status === "fail" ? "FAIL" : "skipped";
141
+ const detail = step.seconds !== void 0 ? `${step.seconds.toFixed(1)}s` : step.reason ?? "";
142
+ return ` ${step.name.padEnd(18)} ${verdict.padEnd(7)} ${detail}
143
+ `;
144
+ }
145
+ export {
146
+ VERIFY_DIR,
147
+ formatVerifyReport,
148
+ formatVerifyStep,
149
+ runVerify,
150
+ verifyTreeHash
151
+ };
@@ -1,12 +1,12 @@
1
1
  import { DiagnosticSeverity } from '../reference/types.js';
2
- import { EquationLink } from '../reference/gate-math.js';
3
- import { Manifest, ManifestLesson } from './manifest.js';
2
+ import { EquationLink, ScopeEntry } from '../reference/gate-math.js';
3
+ import { ManifestLesson, ManifestLessonFloat, Manifest } from './manifest.js';
4
4
  import { RenderDiagnostic } from './render-diagnostics.js';
5
+ import { RetiredRule } from '../rules/catalog.js';
5
6
  import { RulesFilter, RuleInEffect } from '../rules/in-effect.js';
6
7
  import '../curriculum/validation.js';
7
8
  import '../reference/consistency.js';
8
- import '../course-config-rRtME5UV.js';
9
- import '../rules/catalog.js';
9
+ import '../course-config-BLtIdkei.js';
10
10
  import '../rules/course-rules.js';
11
11
  import './collections.js';
12
12
  import '../reference/gate-retired-keys.js';
@@ -25,12 +25,17 @@ import '../reference/mixed-content.js';
25
25
  import '../reference/gloss.js';
26
26
  import '../rules/waivers.js';
27
27
  import './knowledge-version.js';
28
+ import './page-versions.js';
29
+ import './decks.js';
30
+ import '../reference/decks-validate.js';
31
+ import '../reference/deck-source.js';
28
32
  import './lesson-derivation.js';
29
33
 
30
34
  /**
31
35
  * The `content` CLI core (Phase G1).
32
36
  *
33
- * `scripts/content.ts` is a thin argv wrapper; every command's logic lives here
37
+ * `./cli-main` is the router (`runContentCli`), a course's `scripts/content.ts`
38
+ * a three-line shim over it; every command's logic lives here
34
39
  * as a pure-ish function that takes a compiled {@link Manifest} and returns a
35
40
  * plain data object. The router is **read-only** — only `content new` touches
36
41
  * the filesystem, and even then `scaffoldLesson` / `scaffoldTerm` only return
@@ -59,7 +64,7 @@ import './lesson-derivation.js';
59
64
  * under the `render:` source.
60
65
  */
61
66
 
62
- type ContentCliCode = 'unknown-command' | 'usage' | 'unknown-lesson' | 'unknown-kind' | 'invalid-id' | 'exists' | 'not-a-lesson' | 'no-git';
67
+ type ContentCliCode = 'unknown-command' | 'usage' | 'unknown-lesson' | 'unknown-kind' | 'invalid-id' | 'exists' | 'not-a-lesson' | 'no-git' | 'unknown-version' | 'migration-gap' | 'unknown-key' | 'no-changelog';
63
68
  /** A user-facing failure with a stable code and no stack in `--json` output. */
64
69
  declare class ContentCliError extends Error {
65
70
  readonly code: ContentCliCode;
@@ -74,6 +79,8 @@ interface CliDiagnostic {
74
79
  * which `tests/rules/catalog.test.ts` treats as a bug.
75
80
  */
76
81
  readonly rule?: string;
82
+ /** The owner's page and anchor under `docs/authoring/`, `notation.md#n4`. */
83
+ readonly doc?: string;
77
84
  readonly severity: DiagnosticSeverity;
78
85
  readonly message: string;
79
86
  readonly file?: string;
@@ -87,13 +94,23 @@ interface CliDiagnostic {
87
94
  */
88
95
  readonly equation?: EquationLink;
89
96
  }
97
+ /** ` [N4 · notation.md#n4]` for a diagnostic the catalog owns; empty otherwise. */
98
+ declare function ownerTag(diagnostic: Pick<CliDiagnostic, 'rule' | 'doc'>): string;
99
+ /**
100
+ * One text line for a diagnostic, the shape every verb prints:
101
+ * `file:line:column: severity code [rule · doc]: message`. The location is
102
+ * left out when the diagnostic has no file, and reads `1:1` when the file is
103
+ * known but the position is not, so an editor opens the file. The rule and its page are on the
104
+ * line so a reader needs no second lookup to find what the code answers to.
105
+ */
106
+ declare function formatCliDiagnostic(diagnostic: CliDiagnostic): string;
90
107
  /**
91
108
  * Every manifest diagnostic that names this lesson (by id or file), normalized
92
109
  * to {@link CliDiagnostic}. Corpus-wide checks (curriculum) are included only
93
110
  * when their message mentions the lesson; per-file checks (notation, math,
94
111
  * equations, consistency) are filtered by `file`.
95
112
  */
96
- declare function lessonDiagnostics(manifest: Manifest, lesson: ManifestLesson, rendered?: readonly RenderDiagnostic[]): CliDiagnostic[];
113
+ declare function lessonDiagnostics(manifest: Manifest, lesson: CheckTarget, rendered?: readonly RenderDiagnostic[]): CliDiagnostic[];
97
114
  interface RenderResult {
98
115
  readonly rendered: boolean;
99
116
  readonly diagnostics: readonly CliDiagnostic[];
@@ -139,6 +156,34 @@ interface DomainFileSignatures {
139
156
  interface StatusReport {
140
157
  /** The course's published knowledge version (`about.version`). */
141
158
  readonly knowledgeVersion: string;
159
+ /**
160
+ * What an agent is told to read before working, in estimated tokens: the
161
+ * course's `AGENTS.md`, the repository's one level up when the course is a
162
+ * workspace package, and the rules-in-effect page. A budget to watch; the
163
+ * estimate is bytes over four.
164
+ */
165
+ readonly agentSurfaces: readonly {
166
+ readonly name: string;
167
+ readonly tokens: number;
168
+ }[];
169
+ /**
170
+ * Whether the course's own `AGENTS.md` points an agent at the engine's
171
+ * guide (`docs/authoring/agents.md`), and whether the procedures
172
+ * `content init-agent` installs are in `.claude/`.
173
+ */
174
+ readonly agentGuide: {
175
+ readonly agentsFile: string | null;
176
+ readonly linksGuide: boolean;
177
+ readonly skillsInstalled: boolean;
178
+ };
179
+ /**
180
+ * Rule C5: how many chapters and part pages carry a version of their own,
181
+ * and the slugs of those with a change `## [Unreleased]` has not released.
182
+ */
183
+ readonly pageVersions: {
184
+ readonly tracked: number;
185
+ readonly unreleased: readonly string[];
186
+ };
142
187
  readonly counts: {
143
188
  readonly lessons: number;
144
189
  readonly competencies: number;
@@ -166,6 +211,46 @@ interface DiagnosticsReport {
166
211
  readonly count: number;
167
212
  readonly diagnostics: readonly CliDiagnostic[];
168
213
  }
214
+ /**
215
+ * The diagnostics list folded to what an agent decides from: how many, of
216
+ * what, and where. The default text output, since a course can have hundreds
217
+ * of lines of two codes and the list is what `--all` is for.
218
+ */
219
+ interface DiagnosticsSummary {
220
+ readonly minimumSeverity: DiagnosticThreshold;
221
+ readonly count: number;
222
+ readonly bySeverity: {
223
+ readonly errors: number;
224
+ readonly warnings: number;
225
+ readonly infos: number;
226
+ };
227
+ /** One row per code and severity, most frequent first. */
228
+ readonly byCode: readonly {
229
+ readonly code: string;
230
+ readonly rule?: string;
231
+ readonly doc?: string;
232
+ readonly severity: DiagnosticSeverity;
233
+ readonly count: number;
234
+ }[];
235
+ /** One row per file, most diagnostics first; `(no file)` for the rest. */
236
+ readonly byFile: readonly {
237
+ readonly file: string;
238
+ readonly count: number;
239
+ readonly errors: number;
240
+ readonly warnings: number;
241
+ readonly infos: number;
242
+ }[];
243
+ }
244
+ declare function summarizeDiagnostics(report: DiagnosticsReport): DiagnosticsSummary;
245
+ /** The diagnostics of one code (with or without its `source:` prefix) or of some files. */
246
+ declare function filterDiagnostics(report: DiagnosticsReport, filter: {
247
+ readonly code?: string;
248
+ readonly files?: readonly string[];
249
+ }): DiagnosticsReport;
250
+ /** The summary as text: a severity line, then counts by code and by file, the file list capped. */
251
+ declare function formatDiagnosticsSummary(summary: DiagnosticsSummary, options?: {
252
+ readonly fileCap?: number;
253
+ }): string;
169
254
  interface WarningsReport {
170
255
  readonly count: number;
171
256
  readonly warnings: readonly CliDiagnostic[];
@@ -191,6 +276,8 @@ interface RulesReport {
191
276
  declare function parseRulesFilter(input: {
192
277
  readonly element?: string;
193
278
  readonly enforcement?: string;
279
+ /** `chapter` (the default page) or `deck` (`<lesson>.deck.mdx`). */
280
+ readonly edition?: string;
194
281
  }): RulesFilter;
195
282
  /** The rules this course has in effect, optionally narrowed. */
196
283
  declare function contentRules(manifest: Manifest, filter?: RulesFilter): RulesReport;
@@ -204,6 +291,8 @@ interface ContextReport {
204
291
  readonly teaches: readonly string[];
205
292
  readonly requires: readonly string[];
206
293
  readonly assumptions: readonly string[];
294
+ /** The version the page's content last changed in; `null` for a change not yet released (rule C5). */
295
+ readonly since: string | null;
207
296
  readonly notationLocal: unknown;
208
297
  readonly body: string;
209
298
  };
@@ -223,22 +312,10 @@ interface ContextReport {
223
312
  units?: string;
224
313
  formula?: string;
225
314
  }[];
226
- readonly equations: readonly {
227
- key: string;
228
- number: string;
229
- }[];
230
- readonly tables: readonly {
231
- key: string;
232
- number: string;
233
- }[];
234
- readonly figures: readonly {
235
- key: string;
236
- number: string;
237
- }[];
238
- readonly diagrams: readonly {
239
- key: string;
240
- number: string;
241
- }[];
315
+ readonly equations: readonly ManifestLessonFloat[];
316
+ readonly tables: readonly ManifestLessonFloat[];
317
+ readonly figures: readonly ManifestLessonFloat[];
318
+ readonly diagrams: readonly ManifestLessonFloat[];
242
319
  /** Worked-example sets: `short` is the title `[[ex-key]]` names. */
243
320
  readonly examples: readonly {
244
321
  key: string;
@@ -268,7 +345,298 @@ interface ContextReport {
268
345
  readonly diagnostics: readonly CliDiagnostic[];
269
346
  readonly tokens: TokenCounts;
270
347
  }
348
+ interface MigrationSection {
349
+ readonly from: string;
350
+ readonly to: string;
351
+ readonly heading: string;
352
+ readonly markdown: string;
353
+ readonly tokens: number;
354
+ }
355
+ interface MigrateReport {
356
+ readonly from: string;
357
+ readonly to: string;
358
+ readonly file: string;
359
+ readonly sections: readonly MigrationSection[];
360
+ readonly tokens: number;
361
+ }
362
+ /** Every `## <from> → <to>` section of the guide, in file order (newest first). */
363
+ declare function parseMigrationSections(markdown: string): MigrationSection[];
364
+ /**
365
+ * The sections that take a course from `from` to `to`, oldest first: each
366
+ * section's left side matches where the previous one ended. A hole in the
367
+ * chain is an error naming the version no section continues from, since a
368
+ * skipped section is a skipped migration.
369
+ */
370
+ declare function selectMigrationPath(sections: readonly MigrationSection[], from: string, to: string): MigrationSection[];
371
+ /** The engine version the course has installed: the package beside it, or this one. */
372
+ declare function installedEngineVersion(): string | undefined;
373
+ /**
374
+ * The engine version `package.json` pins at HEAD — what the course last
375
+ * migrated to, before the working tree bumped it. `workspace:*` or a range
376
+ * is no version, so the caller asks for `--from`.
377
+ */
378
+ declare function pinnedEngineVersion(): string | undefined;
379
+ declare function contentMigrate(options?: {
380
+ readonly from?: string;
381
+ readonly to?: string;
382
+ }): MigrateReport;
383
+ /** The sections, oldest first, under one line that says how much is coming. */
384
+ declare function formatMigrateReport(report: MigrateReport): string;
385
+ interface SpanBinding {
386
+ readonly token: string;
387
+ readonly key: string;
388
+ /** The resolver's match kind; `explicit` for an `\explain` wrap. */
389
+ readonly how: string;
390
+ }
391
+ interface ExplicitWrap {
392
+ readonly key: string;
393
+ readonly inner: string;
394
+ /** The bare `inner` resolves to exactly this card: the wrap costs tokens and binds nothing new. */
395
+ readonly redundant: boolean;
396
+ }
397
+ interface UnresolvedGlyph {
398
+ readonly token: string;
399
+ readonly suspended?: string;
400
+ /** Rule N23: indexed entries whose base this bare letter is; none lends it a card. */
401
+ readonly lenders?: readonly string[];
402
+ /** Where `content symbol` finds the token defined: exact hits, then near misses. */
403
+ readonly defined: readonly {
404
+ readonly tier: SymbolTier;
405
+ readonly key: string;
406
+ readonly where: string;
407
+ }[];
408
+ readonly near: readonly {
409
+ readonly latex: string;
410
+ readonly key: string;
411
+ }[];
412
+ }
413
+ interface SpanBindings {
414
+ readonly bindings: readonly SpanBinding[];
415
+ readonly wraps: readonly ExplicitWrap[];
416
+ readonly unresolved: readonly Omit<UnresolvedGlyph, 'defined' | 'near'>[];
417
+ /** The resolver rejected the span (a KaTeX parse error or a rule it enforces, N13 say); its message. */
418
+ readonly error?: string;
419
+ }
420
+ interface BindingsSpan extends Omit<SpanBindings, 'unresolved'> {
421
+ readonly line: number;
422
+ readonly column: number;
423
+ readonly display: boolean;
424
+ readonly latex: string;
425
+ readonly unresolved: readonly UnresolvedGlyph[];
426
+ }
427
+ interface BindingsReport {
428
+ readonly lesson: string;
429
+ readonly file: string;
430
+ readonly scope: readonly {
431
+ readonly key: string;
432
+ readonly notation: string;
433
+ readonly scope: 'shared' | 'local';
434
+ }[];
435
+ readonly spans: readonly BindingsSpan[];
436
+ readonly counts: {
437
+ readonly spans: number;
438
+ readonly bindings: number;
439
+ readonly unresolved: number;
440
+ readonly wraps: number;
441
+ readonly redundantWraps: number;
442
+ readonly errors: number;
443
+ };
444
+ }
445
+ /**
446
+ * One span against a scope. Exported for tests and for a caller with a span
447
+ * of its own (`content equation`).
448
+ */
449
+ declare function spanBindings(latex: string, scope: readonly ScopeEntry[], options?: {
450
+ readonly suspended?: readonly string[];
451
+ readonly derive?: boolean;
452
+ }): SpanBindings;
453
+ declare function contentBindings(manifest: Manifest, rawId: string): BindingsReport;
454
+ /** The text form: a counts line, the scope, then one line per span. */
455
+ declare function formatBindingsReport(report: BindingsReport): string;
456
+ /**
457
+ * One LaTeX string, resolved the way the gate will: against a lesson's
458
+ * scope, or against every shared card when no lesson is named (the table a
459
+ * notation entry's body and formula use). The inner loop while drafting:
460
+ * a few lines out, non-zero exit when the build would refuse the span.
461
+ */
462
+ interface EquationReport extends Omit<SpanBindings, 'unresolved'> {
463
+ readonly latex: string;
464
+ readonly against: {
465
+ readonly kind: 'lesson';
466
+ readonly lesson: string;
467
+ readonly cards: number;
468
+ } | {
469
+ readonly kind: 'shared';
470
+ readonly cards: number;
471
+ };
472
+ readonly unresolved: readonly UnresolvedGlyph[];
473
+ readonly ok: boolean;
474
+ }
475
+ declare function contentEquation(manifest: Manifest, latex: string, options?: {
476
+ readonly lesson?: string;
477
+ }): EquationReport;
478
+ declare function formatEquationReport(report: EquationReport): string;
479
+ interface FileEdit {
480
+ /** Course-relative path. */
481
+ readonly file: string;
482
+ /** `null` when the file is created. */
483
+ readonly before: string | null;
484
+ /** `null` when the file is deleted. */
485
+ readonly after: string | null;
486
+ }
487
+ interface EditPlan {
488
+ readonly summary: readonly string[];
489
+ readonly edits: readonly FileEdit[];
490
+ /** What to run after writing. */
491
+ readonly next: readonly string[];
492
+ }
493
+ /** `[[old]]` and `\explain{old}` become `new`; nothing else is touched. Returns the count. */
494
+ declare function rewriteKeyReferences(text: string, oldKey: string, newKey: string): {
495
+ text: string;
496
+ count: number;
497
+ };
498
+ /**
499
+ * Remove one `notation.local` item from a lesson file's text, by key, keeping
500
+ * the rest of the frontmatter byte for byte. An item runs from its `- key:`
501
+ * line to the next line indented no deeper than the dash; an emptied list
502
+ * becomes `local: []`.
503
+ */
504
+ declare function removeLocalItem(text: string, key: string): {
505
+ text: string;
506
+ removed: boolean;
507
+ };
508
+ /**
509
+ * A shared card's file from a page-local entry's fields, nothing invented: no
510
+ * body (the glossary shows the meaning; what a reader needs beyond it is the
511
+ * author's to write, and a placeholder would publish), and the label only
512
+ * when the entry has one. `domain` is the caller's pick, else the key's
513
+ * prefix, else `general`.
514
+ */
515
+ declare function sharedCardFromLocal(local: Record<string, unknown>, options: {
516
+ readonly key: string;
517
+ readonly fromLesson: string;
518
+ readonly domain?: string;
519
+ }): string;
520
+ /**
521
+ * `content move <lesson>:<key> [--to <key>]`: a page-local entry becomes a
522
+ * shared card. Every lesson that declares the key loses its copy (the card
523
+ * serves them all; copies that differ are reported, the named lesson's text
524
+ * wins), and with `--to` every `[[key]]` and `\explain{key}` follows.
525
+ */
526
+ declare function planMove(manifest: Manifest, lessonId: string, key: string, options?: {
527
+ readonly to?: string;
528
+ }): EditPlan;
529
+ /** `content rename <old> <new>`: a shared card's key, everywhere it is named. */
530
+ declare function planRename(manifest: Manifest, oldKey: string, newKey: string): EditPlan;
531
+ /**
532
+ * `content fix <lesson>`: the diagnostics with one mechanical repair, applied.
533
+ * Today: every `\explain{key}{inner}` the gate reports as `explain-redundant`
534
+ * becomes `inner`.
535
+ */
536
+ declare function planFix(manifest: Manifest, lessonId: string): EditPlan;
537
+ /** `content fix` over several lessons (or every one, `--all`) as one plan: the lessons with nothing to fix are counted, not listed. */
538
+ declare function planFixMany(manifest: Manifest, lessonIds: readonly string[]): EditPlan;
539
+ /** The plan as a unified diff (git's), file by file, under its summary lines. */
540
+ declare function renderEditPlan(plan: EditPlan): string;
541
+ /** Write a plan's edits. Returns the files touched. */
542
+ declare function applyEditPlan(plan: EditPlan): string[];
543
+ /**
544
+ * The engine's course-facing agent procedures (`docs/course-agent/`): skills,
545
+ * the `rules-reviewer` agent and the `AGENTS.md` baseline, beside the
546
+ * installed package or above this module.
547
+ */
548
+ declare function courseAgentDir(): string | undefined;
549
+ /**
550
+ * `content init-agent [--write]`: install or refresh the engine's agent
551
+ * procedures in this course — every skill under `.claude/skills/`, the
552
+ * reviewer under `.claude/agents/` — and, only when the course has no
553
+ * `AGENTS.md`, the baseline one. An existing `AGENTS.md` is never edited;
554
+ * when it does not point at the engine's guide, the plan says what to add.
555
+ */
556
+ declare function planInitAgent(): EditPlan;
557
+ /**
558
+ * One rule, whole: the catalog entry as this course has it (enforcement,
559
+ * codes, settings in effect, whether it is on) and the rule page's own
560
+ * section, example included. About half a thousand tokens where opening the
561
+ * page costs twenty thousand.
562
+ */
563
+ interface RuleReport {
564
+ readonly id: string;
565
+ readonly status: 'in-effect' | 'retired' | 'unknown';
566
+ readonly rule?: RuleInEffect;
567
+ readonly retired?: RetiredRule;
568
+ /** The rule page's section for this rule, when the page is at hand. */
569
+ readonly section?: {
570
+ readonly file: string;
571
+ readonly markdown: string;
572
+ readonly tokens: number;
573
+ };
574
+ /** For `unknown`: the IDs this course has, so the caller can pick one. */
575
+ readonly known: readonly string[];
576
+ }
577
+ /**
578
+ * The engine's `docs/authoring/`: beside the installed package in a course
579
+ * (`node_modules/explico/docs/authoring`, the path the rules-in-effect page
580
+ * links), or above this module in the repository and in the package itself.
581
+ */
582
+ declare function authoringDocsDir(): string | undefined;
583
+ /**
584
+ * The section of a rule page that `anchor` names: from the heading whose
585
+ * slug is the anchor (`## N4` → `n4`, `## Reference systems` →
586
+ * `reference-systems`) to the next heading of the same or a higher level,
587
+ * trailing rules and blank lines dropped.
588
+ */
589
+ declare function ruleSection(page: string, anchor: string): string | undefined;
590
+ declare function contentRule(manifest: Manifest, rawId: string): RuleReport;
591
+ /** The text form of a rule report: the entry's facts, then the page section. */
592
+ declare function formatRuleReport(report: RuleReport): string;
271
593
  declare function contentContext(manifest: Manifest, rawId: string): ContextReport;
594
+ /** Where a hit is defined: a shared card, a lesson's `notation.local`, or a card's gloss. */
595
+ type SymbolTier = 'shared' | 'local' | 'gloss';
596
+ interface SymbolHit {
597
+ readonly tier: SymbolTier;
598
+ readonly key: string;
599
+ readonly latex: string;
600
+ readonly label: string;
601
+ readonly meaning?: string;
602
+ readonly units?: string;
603
+ /** How the query matched: the latex as written, an alias, or the key. */
604
+ readonly matchedBy: 'latex' | 'alias' | 'key';
605
+ /** shared: the entry file; local: the lesson id; gloss: the owning card. */
606
+ readonly where: string;
607
+ /** shared only: the pages (lessons and overview pages) whose bundle binds the card. */
608
+ readonly boundIn?: readonly string[];
609
+ }
610
+ interface SymbolReport {
611
+ readonly query: string;
612
+ readonly shared: readonly SymbolHit[];
613
+ readonly local: readonly SymbolHit[];
614
+ readonly glosses: readonly SymbolHit[];
615
+ /** Entries whose latex contains the query as a glyph, not matched above. */
616
+ readonly contains: readonly SymbolHit[];
617
+ /** How many `contains` candidates there were before the list was capped. */
618
+ readonly containsTotal: number;
619
+ }
620
+ declare function contentSymbol(manifest: Manifest, rawQuery: string): SymbolReport;
621
+ /**
622
+ * The text form of a symbol report: one block per tier, then the near
623
+ * misses, then one line that counts what was found, worded for the next
624
+ * step an author takes.
625
+ */
626
+ declare function formatSymbolReport(report: SymbolReport): string;
627
+ /**
628
+ * A page `content check` checks: a lesson's (its chapter, or a slides-first
629
+ * lesson's deck), or a deck beside its chapter or a part's, by its page id.
630
+ */
631
+ type CheckTarget = Pick<ManifestLesson, 'id' | 'file' | 'assessments'> & {
632
+ readonly notation: Pick<ManifestLesson['notation'], 'definitionIds'>;
633
+ };
634
+ /**
635
+ * The lesson `rawId` names, or the deck whose page it names: a deck beside
636
+ * its chapter by its route (`motion/range/slides`), a part's deck by its own
637
+ * (`motion/slides`).
638
+ */
639
+ declare function findCheckTarget(manifest: Manifest, rawId: string): CheckTarget;
272
640
  interface CheckLessonResult {
273
641
  readonly lesson: string;
274
642
  readonly file: string;
@@ -299,6 +667,22 @@ interface Scaffold {
299
667
  }[];
300
668
  }
301
669
  declare function scaffoldLesson(rawId: string): Scaffold;
670
+ /**
671
+ * `content new deck <lesson>`: the slide edition of a lesson, as a draft to
672
+ * rewrite (`docs/authoring/deck.md`). Beside an existing chapter it starts
673
+ * from the chapter: a `##` section per chapter section, a `###` frame per
674
+ * subsection (or one for a section without any), a `<Recall>` for each
675
+ * numbered equation, figure, table and diagram there, and the section's first
676
+ * sentence in the frame's notes. Without a chapter it is a slides-first
677
+ * lesson, with the lesson's frontmatter and a skeleton frame.
678
+ *
679
+ * `content new deck <part>`, for a part's directory with its page
680
+ * (`index.mdx`), drafts the part's deck (`<part>/index.deck.mdx`): a
681
+ * `## <chapter title> [chapter: <name>]` section per chapter, in the order
682
+ * the tracks read them, and a frame per section of the chapter, with a
683
+ * `<Recall>` for each float it numbers and its first sentence in the notes.
684
+ */
685
+ declare function scaffoldDeck(rawId: string): Scaffold;
302
686
  declare function scaffoldTerm(rawKey: string): Scaffold;
303
687
 
304
- export { type CheckLessonResult, type CheckReport, type CliDiagnostic, type ContentCliCode, ContentCliError, type ContextReport, type DiagnosticThreshold, type DiagnosticsReport, type DomainFileSignatures, type RenderResult, type RulesReport, type Scaffold, type StatusReport, type TokenCounts, type WarningsReport, changedLessonIds, contentCheck, contentContext, contentDiagnostics, contentRules, contentStatus, contentWarnings, estimateTokens, findLesson, lessonDiagnostics, lessonSource, normalizeLessonId, parseRulesFilter, renderLesson, scaffoldLesson, scaffoldTerm };
688
+ export { type BindingsReport, type BindingsSpan, type CheckLessonResult, type CheckReport, type CheckTarget, type CliDiagnostic, type ContentCliCode, ContentCliError, type ContextReport, type DiagnosticThreshold, type DiagnosticsReport, type DiagnosticsSummary, type DomainFileSignatures, type EditPlan, type EquationReport, type ExplicitWrap, type FileEdit, type MigrateReport, type MigrationSection, type RenderResult, type RuleReport, type RulesReport, type Scaffold, type SpanBinding, type SpanBindings, type StatusReport, type SymbolHit, type SymbolReport, type SymbolTier, type TokenCounts, type UnresolvedGlyph, type WarningsReport, applyEditPlan, authoringDocsDir, changedLessonIds, contentBindings, contentCheck, contentContext, contentDiagnostics, contentEquation, contentMigrate, contentRule, contentRules, contentStatus, contentSymbol, contentWarnings, courseAgentDir, estimateTokens, filterDiagnostics, findCheckTarget, findLesson, formatBindingsReport, formatCliDiagnostic, formatDiagnosticsSummary, formatEquationReport, formatMigrateReport, formatRuleReport, formatSymbolReport, installedEngineVersion, lessonDiagnostics, lessonSource, normalizeLessonId, ownerTag, parseMigrationSections, parseRulesFilter, pinnedEngineVersion, planFix, planFixMany, planInitAgent, planMove, planRename, removeLocalItem, renderEditPlan, renderLesson, rewriteKeyReferences, ruleSection, scaffoldDeck, scaffoldLesson, scaffoldTerm, selectMigrationPath, sharedCardFromLocal, spanBindings, summarizeDiagnostics };