lens-content-processor 0.51.0 → 0.54.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/dist/content-schema.js +21 -6
- package/dist/content-schema.js.map +1 -1
- package/dist/flattener/index.js +11 -4
- package/dist/flattener/index.js.map +1 -1
- package/dist/glossary.d.ts +38 -0
- package/dist/glossary.js +365 -0
- package/dist/glossary.js.map +1 -0
- package/dist/index.d.ts +21 -2
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -1
- package/dist/parser/course.js +54 -3
- package/dist/parser/course.js.map +1 -1
- package/dist/parser/lens.d.ts +3 -1
- package/dist/parser/lens.js +4 -1
- package/dist/parser/lens.js.map +1 -1
- package/dist/parser/meeting-doc.d.ts +14 -0
- package/dist/parser/meeting-doc.js +230 -0
- package/dist/parser/meeting-doc.js.map +1 -0
- package/dist/parser/response-segments.d.ts +2 -2
- package/dist/parser/response-segments.js +39 -4
- package/dist/parser/response-segments.js.map +1 -1
- package/dist/parser/survey.js +9 -0
- package/dist/parser/survey.js.map +1 -1
- package/package.json +1 -1
package/dist/glossary.js
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { parseMarkdown } from "./markdown-parse.js";
|
|
2
|
+
import { stripAuthoringMarkup } from "./authoring-markup.js";
|
|
3
|
+
import { parseFrontmatter } from "./parser/frontmatter.js";
|
|
4
|
+
import { findFileWithExtension, parseWikilink, resolveWikilinkPath, } from "./parser/wikilink.js";
|
|
5
|
+
import { validateFrontmatter } from "./validator/validate-frontmatter.js";
|
|
6
|
+
import { checkTierViolation } from "./validator/tier.js";
|
|
7
|
+
export function isGlossaryPath(path) {
|
|
8
|
+
return path.startsWith("glossary/") || path.includes("/glossary/");
|
|
9
|
+
}
|
|
10
|
+
function termFromPath(path) {
|
|
11
|
+
return (path.split("/").pop() ?? path).replace(/\.md$/, "");
|
|
12
|
+
}
|
|
13
|
+
export function parseGlossaryEntry(content, file) {
|
|
14
|
+
const errors = [];
|
|
15
|
+
const stripped = stripAuthoringMarkup(content);
|
|
16
|
+
// An entry with no aliases needs no frontmatter: the whole file is the definition.
|
|
17
|
+
const parsed = /^---\r?\n/.test(stripped)
|
|
18
|
+
? parseFrontmatter(stripped, file)
|
|
19
|
+
: { frontmatter: {}, body: stripped, bodyStartLine: 1 };
|
|
20
|
+
if ("error" in parsed && parsed.error) {
|
|
21
|
+
return { entry: null, errors: [parsed.error] };
|
|
22
|
+
}
|
|
23
|
+
const { frontmatter, body, bodyStartLine } = parsed;
|
|
24
|
+
errors.push(...validateFrontmatter(frontmatter, "glossary", file));
|
|
25
|
+
const fail = (message, suggestion, line = bodyStartLine) => {
|
|
26
|
+
errors.push({ file, line, message, suggestion, severity: "error" });
|
|
27
|
+
return { entry: null, errors };
|
|
28
|
+
};
|
|
29
|
+
const aliases = frontmatter.aliases ?? [];
|
|
30
|
+
if (!Array.isArray(aliases) ||
|
|
31
|
+
!aliases.every((a) => typeof a === "string" && a.trim())) {
|
|
32
|
+
return fail("Field 'aliases' must be a list of phrases", 'Write one phrase per line, e.g.\naliases:\n - "artificial general intelligence"', 2);
|
|
33
|
+
}
|
|
34
|
+
const definition = body.trim();
|
|
35
|
+
if (!definition) {
|
|
36
|
+
return fail("Glossary entry has no definition", "Write the definition below the frontmatter, in one or two sentences");
|
|
37
|
+
}
|
|
38
|
+
if (/^#{1,6}\s/m.test(definition)) {
|
|
39
|
+
return fail("A glossary definition cannot contain a heading", "Definitions show in a small popup; keep them to plain sentences");
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
entry: {
|
|
43
|
+
term: termFromPath(file),
|
|
44
|
+
aliases: aliases.map((a) => a.trim()),
|
|
45
|
+
definition,
|
|
46
|
+
},
|
|
47
|
+
errors,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** A phrase may belong to one entry only, or a highlight could not say which. */
|
|
51
|
+
export function validateGlossaryAliases(entries) {
|
|
52
|
+
const owner = new Map();
|
|
53
|
+
const errors = [];
|
|
54
|
+
for (const [path, entry] of entries) {
|
|
55
|
+
for (const phrase of phrasesOf(entry)) {
|
|
56
|
+
const key = normalizeApostrophes(phrase.toLowerCase());
|
|
57
|
+
const first = owner.get(key);
|
|
58
|
+
if (first === undefined) {
|
|
59
|
+
owner.set(key, path);
|
|
60
|
+
}
|
|
61
|
+
else if (first !== path) {
|
|
62
|
+
errors.push({
|
|
63
|
+
file: path,
|
|
64
|
+
message: `Glossary phrase "${phrase}" is already used by ${first}`,
|
|
65
|
+
suggestion: "Remove the phrase from one of the two entries (phrases differing only in case also clash)",
|
|
66
|
+
severity: "error",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return errors;
|
|
72
|
+
}
|
|
73
|
+
function phrasesOf(entry) {
|
|
74
|
+
return [entry.term, ...entry.aliases];
|
|
75
|
+
}
|
|
76
|
+
function normalizeApostrophes(text) {
|
|
77
|
+
return text.replace(/[’‘]/g, "'");
|
|
78
|
+
}
|
|
79
|
+
// ─── Applying a lens's glossary_terms ───
|
|
80
|
+
const WORD = String.raw `[\p{L}\p{N}_-]`;
|
|
81
|
+
function compilePhrase(term, phrase) {
|
|
82
|
+
const escaped = normalizeApostrophes(phrase)
|
|
83
|
+
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
84
|
+
.replace(/'/g, "['’‘]");
|
|
85
|
+
const capitals = /\p{L}/u.test(phrase) && phrase === phrase.toUpperCase();
|
|
86
|
+
return {
|
|
87
|
+
term,
|
|
88
|
+
pattern: new RegExp(`(?<!${WORD})${escaped}(?!${WORD})`, capitals ? "gu" : "giu"),
|
|
89
|
+
length: phrase.length,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/** Nodes whose text the learner does not read as running prose. */
|
|
93
|
+
const SKIPPED_NODES = new Set([
|
|
94
|
+
"heading",
|
|
95
|
+
"link",
|
|
96
|
+
"linkReference",
|
|
97
|
+
"inlineCode",
|
|
98
|
+
"code",
|
|
99
|
+
"html",
|
|
100
|
+
"inlineMath",
|
|
101
|
+
"math",
|
|
102
|
+
"footnoteDefinition",
|
|
103
|
+
]);
|
|
104
|
+
const SKIPPED_DIRECTIVES = new Set(["hide", "collapse", "footnote"]);
|
|
105
|
+
/** Inline markup a term may sit in; the mark then wraps it (see findHits). */
|
|
106
|
+
const EMPHASIS = new Set(["emphasis", "strong", "delete"]);
|
|
107
|
+
function isSkipped(node) {
|
|
108
|
+
if (SKIPPED_NODES.has(node.type))
|
|
109
|
+
return true;
|
|
110
|
+
if (node.type !== "textDirective" &&
|
|
111
|
+
node.type !== "leafDirective" &&
|
|
112
|
+
node.type !== "containerDirective")
|
|
113
|
+
return false;
|
|
114
|
+
return (SKIPPED_DIRECTIVES.has(node.name) ||
|
|
115
|
+
(node.name === "callout" && node.attributes?.collapse === "closed"));
|
|
116
|
+
}
|
|
117
|
+
// Inline raw HTML arrives as separate open and close tag nodes; the text
|
|
118
|
+
// between them (<a>, <code>, <abbr>...) is not running prose either.
|
|
119
|
+
const OPEN_TAG = /^<([a-z][\w-]*)\b[^>]*(?<!\/)>$/i;
|
|
120
|
+
const CLOSE_TAG = /^<\/[a-z][\w-]*\s*>$/i;
|
|
121
|
+
const VOID_TAGS = new Set(["br", "hr", "img", "input", "wbr"]);
|
|
122
|
+
/** Text nodes a learner reads, in document order, with their ancestors. */
|
|
123
|
+
function readableText(tree) {
|
|
124
|
+
const out = [];
|
|
125
|
+
const walk = (node, ancestors) => {
|
|
126
|
+
if (isSkipped(node))
|
|
127
|
+
return;
|
|
128
|
+
if (node.type === "text")
|
|
129
|
+
out.push({ text: node, ancestors });
|
|
130
|
+
if (!("children" in node))
|
|
131
|
+
return;
|
|
132
|
+
let insideTag = 0;
|
|
133
|
+
for (const c of node.children) {
|
|
134
|
+
if (c.type === "html") {
|
|
135
|
+
const open = OPEN_TAG.exec(c.value);
|
|
136
|
+
if (open && !VOID_TAGS.has(open[1].toLowerCase()))
|
|
137
|
+
insideTag++;
|
|
138
|
+
else if (CLOSE_TAG.test(c.value))
|
|
139
|
+
insideTag = Math.max(0, insideTag - 1);
|
|
140
|
+
}
|
|
141
|
+
else if (!insideTag)
|
|
142
|
+
walk(c, [...ancestors, node]);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
walk(tree, []);
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
// The renderer's inline math rule (web_frontend/src/lib/markdownMath.ts),
|
|
149
|
+
// which the parser here does not apply. A test fails if the two differ.
|
|
150
|
+
export const INLINE_MATH_RE = /(?<!\$)\$(?!\$)([^\s$](?:[^$\n]*?[^\s$])?)\$(?![\d$])/g;
|
|
151
|
+
const ENTITY = /^&(?:#\d+|#x[\da-f]+|[a-z][a-z\d]*);/i;
|
|
152
|
+
/** Source the parser drops from a text node's value, at the cursor. */
|
|
153
|
+
const DROPPED = /^(?:[ \t]+(?=\r?\n)|\\(?=[!-/:-@[-`{-~]))/;
|
|
154
|
+
const LINE_PREFIX = /^[ \t>]+/;
|
|
155
|
+
/**
|
|
156
|
+
* The source offset of each character of a text node's value. The value is
|
|
157
|
+
* not a plain slice of the source: its container drops what starts each line
|
|
158
|
+
* (`> `, list indentation), and escapes and entities are decoded. A character
|
|
159
|
+
* written as an entity has no offset; the walk stops at anything else it
|
|
160
|
+
* cannot place, and later characters have no offset either.
|
|
161
|
+
*/
|
|
162
|
+
function sourceOffsets(source, text) {
|
|
163
|
+
const end = text.position?.end.offset ?? 0;
|
|
164
|
+
let cursor = text.position?.start.offset ?? end;
|
|
165
|
+
const offsets = [];
|
|
166
|
+
// By UTF-16 unit, as the offsets are: an emoji is two.
|
|
167
|
+
walk: for (let i = 0; i < text.value.length; i++) {
|
|
168
|
+
while (cursor < end) {
|
|
169
|
+
const rest = source.slice(cursor, cursor + 40);
|
|
170
|
+
// Before the plain comparison: "&" is one "&" of the value. Text
|
|
171
|
+
// that only looks like an entity ("AT&T;") is still in the value.
|
|
172
|
+
const entity = ENTITY.exec(rest);
|
|
173
|
+
if (entity && !text.value.startsWith(entity[0], i)) {
|
|
174
|
+
cursor += entity[0].length;
|
|
175
|
+
continue walk;
|
|
176
|
+
}
|
|
177
|
+
if (source[cursor] === text.value[i]) {
|
|
178
|
+
offsets[i] = cursor++;
|
|
179
|
+
continue walk;
|
|
180
|
+
}
|
|
181
|
+
const dropped = DROPPED.exec(rest) ??
|
|
182
|
+
(source[cursor - 1] === "\n" ? LINE_PREFIX.exec(rest) : null);
|
|
183
|
+
if (!dropped)
|
|
184
|
+
break walk;
|
|
185
|
+
cursor += dropped[0].length;
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
return offsets;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Every non-overlapping occurrence of a phrase in `source`, as source
|
|
193
|
+
* offsets, longest phrase first at each place.
|
|
194
|
+
*
|
|
195
|
+
* The renderer wraps each mark in a `:glossary[...]` directive, so a mark is
|
|
196
|
+
* placed where that directive parses as the same text:
|
|
197
|
+
* - Not inside `$...$` math, and not right after `:` (it would read as
|
|
198
|
+
* `::glossary`) or `\` (an escaped colon). Such a place is still taken,
|
|
199
|
+
* so a shorter phrase inside it is not marked instead.
|
|
200
|
+
* - A term that is all of an emphasis (`**LLM**s`) is marked around the
|
|
201
|
+
* emphasis: a directive inside it would stop the closing `**` from
|
|
202
|
+
* closing. A term that only touches the edge of an emphasis is skipped.
|
|
203
|
+
*/
|
|
204
|
+
function findHits(source, phrases, footnotesRunToEnd) {
|
|
205
|
+
const hits = [];
|
|
206
|
+
const tree = parseMarkdown(source, "gfm-directive-math");
|
|
207
|
+
// An article's footnotes render leniently (web_frontend/src/lib/footnotes.ts):
|
|
208
|
+
// everything after the first definition belongs to the notes.
|
|
209
|
+
const end = footnotesRunToEnd
|
|
210
|
+
? Math.min(...tree.children
|
|
211
|
+
.filter((n) => n.type === "footnoteDefinition")
|
|
212
|
+
.map((n) => n.position?.start.offset ?? Infinity))
|
|
213
|
+
: Infinity;
|
|
214
|
+
for (const { text, ancestors } of readableText(tree)) {
|
|
215
|
+
if ((text.position?.start.offset ?? 0) >= end)
|
|
216
|
+
break;
|
|
217
|
+
const math = [...text.value.matchAll(INLINE_MATH_RE)].map((m) => ({
|
|
218
|
+
start: m.index,
|
|
219
|
+
end: m.index + m[0].length,
|
|
220
|
+
}));
|
|
221
|
+
const offsets = sourceOffsets(source, text);
|
|
222
|
+
const taken = [];
|
|
223
|
+
for (const phrase of phrases) {
|
|
224
|
+
for (const match of text.value.matchAll(phrase.pattern)) {
|
|
225
|
+
const at = { start: match.index, end: match.index + match[0].length };
|
|
226
|
+
if (taken.some((t) => at.start < t.end && t.start < at.end))
|
|
227
|
+
continue;
|
|
228
|
+
const from = offsets[at.start];
|
|
229
|
+
const to = offsets[at.end - 1];
|
|
230
|
+
const inMath = math.some((m) => at.start < m.end && m.start < at.end);
|
|
231
|
+
let mark = null;
|
|
232
|
+
if (from !== undefined &&
|
|
233
|
+
to !== undefined &&
|
|
234
|
+
source.slice(from, to + 1) === match[0] &&
|
|
235
|
+
!inMath) {
|
|
236
|
+
mark = placeMark({ start: from, end: to + 1, term: phrase.term }, text, ancestors);
|
|
237
|
+
if (mark && /[:\\]/.test(source[mark.start - 1] ?? ""))
|
|
238
|
+
mark = null;
|
|
239
|
+
}
|
|
240
|
+
taken.push({ ...at, mark });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
for (const t of taken)
|
|
244
|
+
if (t.mark)
|
|
245
|
+
hits.push(t.mark);
|
|
246
|
+
}
|
|
247
|
+
return hits.sort((a, b) => a.start - b.start);
|
|
248
|
+
}
|
|
249
|
+
/** Widen a mark around the emphasis it fills, or drop one that touches an emphasis edge. */
|
|
250
|
+
function placeMark(mark, text, ancestors) {
|
|
251
|
+
let node = text;
|
|
252
|
+
let covers = mark.start === text.position?.start.offset &&
|
|
253
|
+
mark.end === text.position?.end.offset;
|
|
254
|
+
let widened = mark;
|
|
255
|
+
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
256
|
+
const parent = ancestors[i];
|
|
257
|
+
if (!EMPHASIS.has(parent.type))
|
|
258
|
+
break;
|
|
259
|
+
const touches = widened.start === node.position?.start.offset ||
|
|
260
|
+
widened.end === node.position?.end.offset;
|
|
261
|
+
if (!touches)
|
|
262
|
+
break;
|
|
263
|
+
if (!covers || parent.children.length !== 1)
|
|
264
|
+
return null;
|
|
265
|
+
const start = parent.position?.start.offset;
|
|
266
|
+
const end = parent.position?.end.offset;
|
|
267
|
+
if (start === undefined || end === undefined)
|
|
268
|
+
return null;
|
|
269
|
+
widened = { ...mark, start, end };
|
|
270
|
+
node = parent;
|
|
271
|
+
covers = true;
|
|
272
|
+
}
|
|
273
|
+
return widened;
|
|
274
|
+
}
|
|
275
|
+
/** Text and article segments, unless they sit in a box that starts closed. */
|
|
276
|
+
function isMarkable(segment) {
|
|
277
|
+
return ((segment.type === "text" || segment.type === "article") &&
|
|
278
|
+
!segment.frames?.some((frame) => frame.collapse === "closed"));
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Mark the first occurrence of each term a lens lists in `glossary_terms`.
|
|
282
|
+
* `links` is the raw frontmatter value.
|
|
283
|
+
*/
|
|
284
|
+
export function applyGlossary(links, segments, lensPath, files, entriesByPath, tierMap) {
|
|
285
|
+
const errors = [];
|
|
286
|
+
const unchanged = { segments, glossary: [], errors };
|
|
287
|
+
// `glossary_terms:` with nothing under it is an empty list.
|
|
288
|
+
if (links === null)
|
|
289
|
+
return unchanged;
|
|
290
|
+
if (!Array.isArray(links) || !links.every((l) => typeof l === "string")) {
|
|
291
|
+
errors.push({
|
|
292
|
+
file: lensPath,
|
|
293
|
+
message: "Field 'glossary_terms' must be a list of wikilinks",
|
|
294
|
+
suggestion: 'Write one link per line, e.g.\nglossary_terms:\n - "[[../glossary/AGI]]"',
|
|
295
|
+
severity: "error",
|
|
296
|
+
});
|
|
297
|
+
return unchanged;
|
|
298
|
+
}
|
|
299
|
+
const entries = [];
|
|
300
|
+
for (const link of links) {
|
|
301
|
+
const entry = resolveEntry(link, lensPath, files, entriesByPath, tierMap, errors);
|
|
302
|
+
if (entry && !entries.some((e) => e.term === entry.term))
|
|
303
|
+
entries.push(entry);
|
|
304
|
+
}
|
|
305
|
+
const phrases = entries
|
|
306
|
+
.flatMap((entry) => phrasesOf(entry).map((p) => compilePhrase(entry.term, p)))
|
|
307
|
+
.sort((a, b) => b.length - a.length);
|
|
308
|
+
if (phrases.length === 0)
|
|
309
|
+
return unchanged;
|
|
310
|
+
const marked = new Set();
|
|
311
|
+
const out = segments.map((segment) => {
|
|
312
|
+
if (!isMarkable(segment))
|
|
313
|
+
return segment;
|
|
314
|
+
const glossaryMarks = findHits(segment.content, phrases, segment.type === "article").filter((hit) => {
|
|
315
|
+
if (marked.has(hit.term))
|
|
316
|
+
return false;
|
|
317
|
+
marked.add(hit.term);
|
|
318
|
+
return true;
|
|
319
|
+
});
|
|
320
|
+
return glossaryMarks.length ? { ...segment, glossaryMarks } : segment;
|
|
321
|
+
});
|
|
322
|
+
for (const entry of entries) {
|
|
323
|
+
if (!marked.has(entry.term)) {
|
|
324
|
+
errors.push({
|
|
325
|
+
file: lensPath,
|
|
326
|
+
message: `Glossary term "${entry.term}" does not appear in this lens`,
|
|
327
|
+
suggestion: "Remove it from glossary_terms, or add the phrase it is written as to the entry's aliases",
|
|
328
|
+
severity: "warning",
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
segments: out,
|
|
334
|
+
glossary: entries
|
|
335
|
+
.filter((e) => marked.has(e.term))
|
|
336
|
+
.map(({ term, definition }) => ({ term, definition })),
|
|
337
|
+
errors,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function resolveEntry(link, lensPath, files, entriesByPath, tierMap, errors) {
|
|
341
|
+
const parsed = parseWikilink(link.trim());
|
|
342
|
+
const target = parsed && !parsed.error
|
|
343
|
+
? findFileWithExtension(resolveWikilinkPath(parsed.path, lensPath), files)
|
|
344
|
+
: null;
|
|
345
|
+
if (!target || !isGlossaryPath(target)) {
|
|
346
|
+
errors.push({
|
|
347
|
+
file: lensPath,
|
|
348
|
+
message: target
|
|
349
|
+
? `glossary_terms: ${link} is not a glossary entry`
|
|
350
|
+
: `glossary_terms: ${link} could not be resolved`,
|
|
351
|
+
suggestion: "Link to a file in the glossary/ folder, e.g. [[../glossary/AGI]]",
|
|
352
|
+
severity: "error",
|
|
353
|
+
});
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
const violation = checkTierViolation(lensPath, tierMap?.get(lensPath) ?? "production", target, tierMap?.get(target) ?? "production", "glossary entry");
|
|
357
|
+
if (violation) {
|
|
358
|
+
errors.push(violation);
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
// An entry that failed to parse is missing here; its own errors are
|
|
362
|
+
// reported once, against the entry.
|
|
363
|
+
return entriesByPath.get(target) ?? null;
|
|
364
|
+
}
|
|
365
|
+
//# sourceMappingURL=glossary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glossary.js","sourceRoot":"","sources":["../src/glossary.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,12 @@ export interface ProcessResult {
|
|
|
9
9
|
modules: FlattenedModule[];
|
|
10
10
|
courses: Course[];
|
|
11
11
|
surveys: FlattenedSurvey[];
|
|
12
|
+
/**
|
|
13
|
+
* Meeting-doc Markdown by repo path (meetings/ folder), published text:
|
|
14
|
+
* what the platform renders into a meeting's session-doc template. A
|
|
15
|
+
* meeting's `docPath` is a key of this map.
|
|
16
|
+
*/
|
|
17
|
+
meetingDocs?: Record<string, string>;
|
|
12
18
|
errors: ContentError[];
|
|
13
19
|
urlsToValidate: UrlToValidate[];
|
|
14
20
|
skillTree: SkillTree;
|
|
@@ -33,6 +39,7 @@ export interface SurveyQuestionSegment {
|
|
|
33
39
|
type: "question";
|
|
34
40
|
key: string;
|
|
35
41
|
content: string;
|
|
42
|
+
description?: string;
|
|
36
43
|
required: boolean;
|
|
37
44
|
maxChars?: number;
|
|
38
45
|
placeholder?: string;
|
|
@@ -42,6 +49,7 @@ export interface SurveyRatingSegment {
|
|
|
42
49
|
type: "rating";
|
|
43
50
|
key: string;
|
|
44
51
|
content: string;
|
|
52
|
+
description?: string;
|
|
45
53
|
scale: number;
|
|
46
54
|
required: boolean;
|
|
47
55
|
lowLabel?: string;
|
|
@@ -52,6 +60,7 @@ export interface SurveyChoiceSegment {
|
|
|
52
60
|
type: "choice";
|
|
53
61
|
key: string;
|
|
54
62
|
content: string;
|
|
63
|
+
description?: string;
|
|
55
64
|
options: string[];
|
|
56
65
|
multi: boolean;
|
|
57
66
|
required: boolean;
|
|
@@ -97,6 +106,9 @@ export interface Course {
|
|
|
97
106
|
outcomes?: string;
|
|
98
107
|
timeCommitment?: string;
|
|
99
108
|
applicationNotes?: string;
|
|
109
|
+
/** The language the course is written in, a name like `Spanish`; absent =
|
|
110
|
+
* English. The read-aloud reader speaks it (core/tts/config.py). */
|
|
111
|
+
language?: string;
|
|
100
112
|
error?: string;
|
|
101
113
|
}
|
|
102
114
|
/**
|
|
@@ -128,7 +140,7 @@ export interface Section {
|
|
|
128
140
|
optional?: boolean;
|
|
129
141
|
hide?: boolean;
|
|
130
142
|
feedback?: boolean;
|
|
131
|
-
forceFeedback?:
|
|
143
|
+
forceFeedback?: true | "first";
|
|
132
144
|
contentId: string | null;
|
|
133
145
|
learningOutcomes: LearningOutcomeRef[];
|
|
134
146
|
wordCount?: number;
|
|
@@ -140,6 +152,8 @@ export interface Section {
|
|
|
140
152
|
readingMinutes?: number;
|
|
141
153
|
tutorMinutes?: number;
|
|
142
154
|
addToAiContext?: string[] | null;
|
|
155
|
+
glossary?: GlossaryDefinition[];
|
|
156
|
+
glossaryTerms?: unknown;
|
|
143
157
|
}
|
|
144
158
|
export interface SectionMeta {
|
|
145
159
|
title?: string;
|
|
@@ -156,6 +170,7 @@ export interface ProgressionItem {
|
|
|
156
170
|
facilitatorSurveyPath?: string;
|
|
157
171
|
facilitatorSurveyId?: string;
|
|
158
172
|
docTemplateUrl?: string;
|
|
173
|
+
docPath?: string;
|
|
159
174
|
}
|
|
160
175
|
export interface ContentError {
|
|
161
176
|
file: string;
|
|
@@ -171,6 +186,7 @@ export interface TextSegment {
|
|
|
171
186
|
type: "text";
|
|
172
187
|
content: string;
|
|
173
188
|
optional?: boolean;
|
|
189
|
+
glossaryMarks?: GlossaryMark[];
|
|
174
190
|
}
|
|
175
191
|
export interface ChatSegment {
|
|
176
192
|
type: "chat";
|
|
@@ -181,6 +197,7 @@ export interface ChatSegment {
|
|
|
181
197
|
export interface ArticleSegment {
|
|
182
198
|
type: "article";
|
|
183
199
|
content: string;
|
|
200
|
+
glossaryMarks?: GlossaryMark[];
|
|
184
201
|
collapsed_before?: string;
|
|
185
202
|
collapsed_after?: string;
|
|
186
203
|
skipEndMarker?: boolean;
|
|
@@ -225,6 +242,7 @@ export interface QuestionSegment {
|
|
|
225
242
|
graded: boolean;
|
|
226
243
|
feedbackInstructions?: string;
|
|
227
244
|
content: string;
|
|
245
|
+
description?: string;
|
|
228
246
|
assessmentInstructions?: string;
|
|
229
247
|
maxTime?: string;
|
|
230
248
|
maxChars?: number;
|
|
@@ -232,7 +250,7 @@ export interface QuestionSegment {
|
|
|
232
250
|
placeholder?: string;
|
|
233
251
|
optional?: boolean;
|
|
234
252
|
feedback?: boolean;
|
|
235
|
-
forceFeedback?:
|
|
253
|
+
forceFeedback?: true | "first";
|
|
236
254
|
scale?: number;
|
|
237
255
|
lowLabel?: string;
|
|
238
256
|
highLabel?: string;
|
|
@@ -318,6 +336,7 @@ export type Segment = (TextSegment | ChatSegment | ArticleSegment | VideoSegment
|
|
|
318
336
|
import { type TranscriptSentence } from "./bundler/video.js";
|
|
319
337
|
export type { TranscriptSentence } from "./bundler/video.js";
|
|
320
338
|
export { parseWidget, isWidgetPath } from "./parser/widget.js";
|
|
339
|
+
import { type GlossaryDefinition, type GlossaryMark } from "./glossary.js";
|
|
321
340
|
export type { ContentTier } from "./validator/tier.js";
|
|
322
341
|
export { checkTierViolation } from "./validator/tier.js";
|
|
323
342
|
import { type SkillTree } from "./skill-tree.js";
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { populateCardModuleSlugs, resolveInlineLensModuleSlugs, } from "./flatte
|
|
|
4
4
|
import { parseModule } from "./parser/module.js";
|
|
5
5
|
import { parseCourse } from "./parser/course.js";
|
|
6
6
|
import { parseSurvey } from "./parser/survey.js";
|
|
7
|
+
import { MEETING_DOC_SHARED_DIR, parseMeetingDoc, } from "./parser/meeting-doc.js";
|
|
7
8
|
import { parseLearningOutcome, } from "./parser/learning-outcome.js";
|
|
8
9
|
import { parseLens } from "./parser/lens.js";
|
|
9
10
|
import { parseWikilink, resolveWikilinkPath, findFileWithExtension, findSimilarFiles, formatSuggestion, } from "./parser/wikilink.js";
|
|
@@ -17,6 +18,7 @@ import { parseWidget, isWidgetPath } from "./parser/widget.js";
|
|
|
17
18
|
export { parseWidget, isWidgetPath } from "./parser/widget.js";
|
|
18
19
|
import { parseVideoTranscript } from "./parser/video-transcript.js";
|
|
19
20
|
import { parsePrompt, isPromptPath } from "./parser/prompt.js";
|
|
21
|
+
import { applyGlossary, isGlossaryPath, parseGlossaryEntry, validateGlossaryAliases, } from "./glossary.js";
|
|
20
22
|
import { validateTimestamps } from "./validator/timestamps.js";
|
|
21
23
|
import { levenshtein } from "./validator/field-typos.js";
|
|
22
24
|
import { buildTagMap, buildTierMap, checkTierViolation, } from "./validator/tier.js";
|
|
@@ -156,6 +158,7 @@ function processContentRun(files, cache) {
|
|
|
156
158
|
const courses = [];
|
|
157
159
|
const surveys = [];
|
|
158
160
|
const surveyPathToId = new Map();
|
|
161
|
+
const meetingDocs = {};
|
|
159
162
|
const errors = [];
|
|
160
163
|
const urlsToValidate = [];
|
|
161
164
|
const uuidEntries = [];
|
|
@@ -177,6 +180,7 @@ function processContentRun(files, cache) {
|
|
|
177
180
|
// LO path → module path → the submodules (local slug → title) that
|
|
178
181
|
// reference it; empty when the module is not split or the LO is module-level.
|
|
179
182
|
const moduleLoRefs = new Map();
|
|
183
|
+
const glossaryEntries = new Map();
|
|
180
184
|
const skillTreeModules = new Map();
|
|
181
185
|
// Record that `modulePath` references each LO reachable from `sections`,
|
|
182
186
|
// remembering which submodule it sits in. A "# Submodule:" heading opens a
|
|
@@ -307,6 +311,14 @@ function processContentRun(files, cache) {
|
|
|
307
311
|
}
|
|
308
312
|
errors.push(...result.errors);
|
|
309
313
|
}
|
|
314
|
+
else if (path.startsWith("meetings/") &&
|
|
315
|
+
path.endsWith(".md") &&
|
|
316
|
+
!path.startsWith(MEETING_DOC_SHARED_DIR)) {
|
|
317
|
+
const result = parseMeetingDoc(content, path, files);
|
|
318
|
+
errors.push(...result.errors);
|
|
319
|
+
if (result.markdown !== null)
|
|
320
|
+
meetingDocs[path] = result.markdown;
|
|
321
|
+
}
|
|
310
322
|
else if (path.startsWith("surveys/") || path.includes("/surveys/")) {
|
|
311
323
|
const result = parseSurvey(content, path);
|
|
312
324
|
errors.push(...result.errors);
|
|
@@ -555,6 +567,12 @@ function processContentRun(files, cache) {
|
|
|
555
567
|
});
|
|
556
568
|
}
|
|
557
569
|
}
|
|
570
|
+
else if (isGlossaryPath(path) && path.endsWith(".md")) {
|
|
571
|
+
const result = parseGlossaryEntry(content, path);
|
|
572
|
+
errors.push(...result.errors);
|
|
573
|
+
if (result.entry)
|
|
574
|
+
glossaryEntries.set(path, result.entry);
|
|
575
|
+
}
|
|
558
576
|
else if (isPromptPath(path)) {
|
|
559
577
|
// Fully validate prompt file (frontmatter + non-empty prompt text)
|
|
560
578
|
const result = parsePrompt(content, path);
|
|
@@ -585,6 +603,7 @@ function processContentRun(files, cache) {
|
|
|
585
603
|
"Domains and Topics",
|
|
586
604
|
"prompts",
|
|
587
605
|
"widgets",
|
|
606
|
+
"glossary",
|
|
588
607
|
];
|
|
589
608
|
let closest = "";
|
|
590
609
|
let minDist = Infinity;
|
|
@@ -607,6 +626,7 @@ function processContentRun(files, cache) {
|
|
|
607
626
|
}
|
|
608
627
|
}
|
|
609
628
|
}
|
|
629
|
+
errors.push(...validateGlossaryAliases(glossaryEntries));
|
|
610
630
|
// Build contentId → moduleSlug mapping and populate moduleSlug in cross-module card links.
|
|
611
631
|
// Prefer real modules over standalone lenses (lens/ prefix) — a lens that appears in
|
|
612
632
|
// both a module and as a standalone should map to the module.
|
|
@@ -624,6 +644,21 @@ function processContentRun(files, cache) {
|
|
|
624
644
|
populateCardModuleSlugs(mod.sections, contentIdToModuleSlug);
|
|
625
645
|
resolveInlineLensModuleSlugs(mod.sections, contentIdToModuleSlug);
|
|
626
646
|
}
|
|
647
|
+
// Glossary marks are offsets into segment text, so they are placed after
|
|
648
|
+
// every pass that rewrites it (card links and lens links above, card
|
|
649
|
+
// metadata in the flattener).
|
|
650
|
+
for (const mod of modules) {
|
|
651
|
+
for (const section of mod.sections) {
|
|
652
|
+
if (section.glossaryTerms === undefined)
|
|
653
|
+
continue;
|
|
654
|
+
const result = applyGlossary(section.glossaryTerms, section.segments, section.sourcePath ?? mod.sourcePath ?? mod.slug, files, glossaryEntries, tierMap);
|
|
655
|
+
errors.push(...result.errors);
|
|
656
|
+
section.segments = result.segments;
|
|
657
|
+
if (result.glossary.length)
|
|
658
|
+
section.glossary = result.glossary;
|
|
659
|
+
delete section.glossaryTerms;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
627
662
|
// Card-to-module membership check. Runs after card link resolution above so
|
|
628
663
|
// every card has its data-lens-card JSON populated, including allowExternal.
|
|
629
664
|
errors.push(...validateCardModuleMembership(modules));
|
|
@@ -708,6 +743,22 @@ function processContentRun(files, cache) {
|
|
|
708
743
|
label: "facilitator-survey",
|
|
709
744
|
},
|
|
710
745
|
];
|
|
746
|
+
if (item.docPath) {
|
|
747
|
+
const resolved = resolveWikilinkPath(item.docPath, courseFile);
|
|
748
|
+
const actualFile = findFileWithExtension(resolved, files);
|
|
749
|
+
if (actualFile && actualFile in meetingDocs) {
|
|
750
|
+
item.docPath = actualFile;
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
errors.push({
|
|
754
|
+
file: courseFile,
|
|
755
|
+
message: `Meeting doc reference could not be resolved: "${item.docPath}"`,
|
|
756
|
+
suggestion: "Check that meeting-doc:: points to an existing, error-free file in meetings/",
|
|
757
|
+
severity: "error",
|
|
758
|
+
});
|
|
759
|
+
delete item.docPath;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
711
762
|
for (const field of surveyFields) {
|
|
712
763
|
const surveyPath = item[field.pathKey];
|
|
713
764
|
if (!surveyPath) {
|
|
@@ -907,6 +958,7 @@ function processContentRun(files, cache) {
|
|
|
907
958
|
modules,
|
|
908
959
|
courses,
|
|
909
960
|
surveys,
|
|
961
|
+
...(Object.keys(meetingDocs).length > 0 ? { meetingDocs } : {}),
|
|
910
962
|
errors: dedupeErrors(errors),
|
|
911
963
|
urlsToValidate,
|
|
912
964
|
skillTree: skillTreeResult.skillTree,
|