lens-content-processor 0.4.0 → 0.5.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/bundler/video.d.ts +12 -6
- package/dist/bundler/video.js +57 -34
- package/dist/bundler/video.js.map +1 -1
- package/dist/cli.d.ts +11 -2
- package/dist/cli.js +36 -19
- package/dist/cli.js.map +1 -1
- package/dist/content-schema.js +64 -16
- package/dist/content-schema.js.map +1 -1
- package/dist/dedupe-errors.d.ts +14 -0
- package/dist/dedupe-errors.js +33 -0
- package/dist/dedupe-errors.js.map +1 -0
- package/dist/flattener/index.d.ts +3 -28
- package/dist/flattener/index.js +497 -741
- package/dist/flattener/index.js.map +1 -1
- package/dist/flattener/resolve-text-links.d.ts +25 -0
- package/dist/flattener/resolve-text-links.js +248 -0
- package/dist/flattener/resolve-text-links.js.map +1 -0
- package/dist/flattener/test-helpers.d.ts +1 -1
- package/dist/flattener/test-helpers.js +1 -3
- package/dist/flattener/test-helpers.js.map +1 -1
- package/dist/index.d.ts +51 -21
- package/dist/index.js +285 -140
- package/dist/index.js.map +1 -1
- package/dist/parser/article.d.ts +2 -1
- package/dist/parser/article.js +75 -14
- package/dist/parser/article.js.map +1 -1
- package/dist/parser/course.d.ts +1 -1
- package/dist/parser/course.js +79 -22
- package/dist/parser/course.js.map +1 -1
- package/dist/parser/frontmatter.d.ts +1 -1
- package/dist/parser/frontmatter.js +38 -12
- package/dist/parser/frontmatter.js.map +1 -1
- package/dist/parser/learning-outcome.d.ts +5 -2
- package/dist/parser/learning-outcome.js +75 -37
- package/dist/parser/learning-outcome.js.map +1 -1
- package/dist/parser/lens.d.ts +33 -34
- package/dist/parser/lens.js +205 -271
- package/dist/parser/lens.js.map +1 -1
- package/dist/parser/module.d.ts +7 -15
- package/dist/parser/module.js +135 -171
- package/dist/parser/module.js.map +1 -1
- package/dist/parser/sections.d.ts +12 -1
- package/dist/parser/sections.js +74 -95
- package/dist/parser/sections.js.map +1 -1
- package/dist/parser/video-transcript.d.ts +2 -1
- package/dist/parser/video-transcript.js +8 -7
- package/dist/parser/video-transcript.js.map +1 -1
- package/dist/reference-graph.d.ts +8 -0
- package/dist/reference-graph.js +105 -0
- package/dist/reference-graph.js.map +1 -0
- package/dist/validator/card-module-membership.d.ts +2 -0
- package/dist/validator/card-module-membership.js +80 -0
- package/dist/validator/card-module-membership.js.map +1 -0
- package/dist/validator/output-integrity.js +5 -5
- package/dist/validator/output-integrity.js.map +1 -1
- package/dist/validator/segment-fields.d.ts +1 -1
- package/dist/validator/segment-fields.js +2 -2
- package/dist/validator/segment-fields.js.map +1 -1
- package/dist/validator/test-segments.d.ts +12 -0
- package/dist/validator/test-segments.js +40 -0
- package/dist/validator/test-segments.js.map +1 -0
- package/dist/validator/url-reachability.d.ts +2 -1
- package/dist/validator/url-reachability.js +40 -20
- package/dist/validator/url-reachability.js.map +1 -1
- package/package.json +1 -1
- package/dist/validator/chat-precedence.d.ts +0 -9
- package/dist/validator/chat-precedence.js +0 -32
- package/dist/validator/chat-precedence.js.map +0 -1
package/dist/parser/sections.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { ALL_KNOWN_FIELDS } from '../content-schema.js';
|
|
2
2
|
import { levenshtein } from '../validator/field-typos.js';
|
|
3
3
|
// Valid section types per file type (exported for use by other parsers)
|
|
4
|
-
export const MODULE_SECTION_TYPES = new Set(['learning outcome', '
|
|
4
|
+
export const MODULE_SECTION_TYPES = new Set(['learning outcome', 'lens']);
|
|
5
5
|
export const LO_SECTION_TYPES = new Set(['lens', 'test']);
|
|
6
|
-
// Lens
|
|
7
|
-
// Output types are `lens-article`, `lens-video`, `page` (v2 format)
|
|
6
|
+
// Legacy: Lens H3 section types (no longer used — lenses are now flat H4 segments)
|
|
8
7
|
export const LENS_SECTION_TYPES = new Set(['page', 'article', 'video']);
|
|
9
8
|
// All known structural header types (sections + segments) for markdown heading detection
|
|
10
9
|
const ALL_STRUCTURAL_TYPES = new Set([
|
|
11
10
|
// Section types
|
|
12
|
-
'learning outcome', '
|
|
11
|
+
'learning outcome', 'lens', 'test', 'module', 'meeting', 'article', 'video',
|
|
13
12
|
// Segment types
|
|
14
|
-
'text', 'chat', '
|
|
13
|
+
'text', 'chat', 'question', 'roleplay',
|
|
15
14
|
]);
|
|
16
15
|
// Fields that commonly contain markdown with headings
|
|
17
16
|
const MARKDOWN_CONTENT_FIELDS = new Set(['content', 'instructions', 'ai-instructions']);
|
|
@@ -21,34 +20,27 @@ export const LENS_OUTPUT_TYPE = {
|
|
|
21
20
|
'article': 'lens-article',
|
|
22
21
|
'video': 'lens-video',
|
|
23
22
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (adjLevel >= 1 && adjLevel <= 4) {
|
|
39
|
-
wrongLevelPatterns.push({ pattern: makeSectionPattern(adjLevel), level: adjLevel });
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const parentSubmodulePattern = validTypes.has('submodule') && headerLevel > 1
|
|
43
|
-
? makeSectionPattern((headerLevel - 1))
|
|
44
|
-
: null;
|
|
45
|
-
let insideParentSubmodule = false;
|
|
23
|
+
/**
|
|
24
|
+
* Parse sections from markdown content based on header hierarchy.
|
|
25
|
+
*
|
|
26
|
+
* @param content - The markdown body to parse
|
|
27
|
+
* @param parentLevel - Headers deeper than this level are candidates (0 = match any header)
|
|
28
|
+
* @param validTypes - Set of valid section/segment type names
|
|
29
|
+
* @param file - Source file path for error reporting
|
|
30
|
+
* @param flat - When true, all matching headers are siblings (for segments).
|
|
31
|
+
* When false, deeper headers nest inside preceding siblings (for sections).
|
|
32
|
+
*/
|
|
33
|
+
export function parseSections(content, parentLevel, validTypes, file = '', flat = false) {
|
|
34
|
+
const minLevel = parentLevel + 1;
|
|
35
|
+
// Match any header from minLevel to 6 with named capture groups
|
|
36
|
+
const HEADER_PATTERN = new RegExp(`^(?<hashes>#{${minLevel},6})\\s+(?<type>[^:]+?)(?:\\:\\s*(?<title>.*?))?\\s*$`, 'i');
|
|
46
37
|
const lines = content.split('\n');
|
|
47
38
|
const sections = [];
|
|
48
39
|
const errors = [];
|
|
49
40
|
let currentSection = null;
|
|
50
41
|
let currentBody = [];
|
|
51
42
|
let preHeaderWarned = false;
|
|
43
|
+
let currentSiblingLevel = 0; // Track sibling level for hierarchical mode
|
|
52
44
|
function finalizeSection() {
|
|
53
45
|
if (!currentSection)
|
|
54
46
|
return;
|
|
@@ -56,11 +48,10 @@ export function parseSections(content, headerLevel, validTypes, file = '') {
|
|
|
56
48
|
const { warnings } = parseFields(currentSection, file);
|
|
57
49
|
errors.push(...warnings);
|
|
58
50
|
// Submodule sections get recursive children parsing
|
|
59
|
-
if (currentSection.type === 'submodule'
|
|
51
|
+
if (currentSection.type === 'submodule') {
|
|
60
52
|
const childValidTypes = new Set([...validTypes]);
|
|
61
53
|
childValidTypes.delete('submodule'); // No nesting
|
|
62
|
-
const
|
|
63
|
-
const childResult = parseSections(currentSection.body, childLevel, childValidTypes, file);
|
|
54
|
+
const childResult = parseSections(currentSection.body, currentSection.level, childValidTypes, file);
|
|
64
55
|
currentSection.children = childResult.sections;
|
|
65
56
|
errors.push(...childResult.errors);
|
|
66
57
|
}
|
|
@@ -69,45 +60,42 @@ export function parseSections(content, headerLevel, validTypes, file = '') {
|
|
|
69
60
|
for (let i = 0; i < lines.length; i++) {
|
|
70
61
|
const line = lines[i];
|
|
71
62
|
const lineNum = i + 1;
|
|
72
|
-
|
|
73
|
-
if (parentSubmodulePattern) {
|
|
74
|
-
const parentMatch = line.match(parentSubmodulePattern);
|
|
75
|
-
if (parentMatch && parentMatch[1].trim().toLowerCase() === 'submodule') {
|
|
76
|
-
finalizeSection();
|
|
77
|
-
currentSection = {
|
|
78
|
-
type: 'submodule',
|
|
79
|
-
title: (parentMatch[2] ?? '').trim(),
|
|
80
|
-
rawType: parentMatch[1].trim(),
|
|
81
|
-
fields: {}, body: '', line: lineNum,
|
|
82
|
-
level: (headerLevel - 1),
|
|
83
|
-
};
|
|
84
|
-
currentBody = [];
|
|
85
|
-
insideParentSubmodule = true;
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
// Inside parent submodule: accumulate body, skip normal header detection
|
|
90
|
-
if (insideParentSubmodule && currentSection) {
|
|
91
|
-
currentBody.push(line);
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
const headerMatch = line.match(SECTION_HEADER_PATTERN);
|
|
63
|
+
const headerMatch = line.match(HEADER_PATTERN);
|
|
95
64
|
if (headerMatch) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const rawType = headerMatch
|
|
65
|
+
const hashes = headerMatch.groups.hashes;
|
|
66
|
+
const headerLevel = hashes.length;
|
|
67
|
+
const rawType = headerMatch.groups.type.trim();
|
|
99
68
|
const normalizedType = rawType.toLowerCase();
|
|
100
|
-
const title = (headerMatch
|
|
69
|
+
const title = (headerMatch.groups.title ?? '').trim();
|
|
70
|
+
// Check if this type is valid for the current context
|
|
101
71
|
if (!validTypes.has(normalizedType)) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
72
|
+
// Only error for unknown types at sibling level (not deeper body content)
|
|
73
|
+
const wouldBeSibling = flat || currentSiblingLevel === 0 || headerLevel <= currentSiblingLevel;
|
|
74
|
+
if (wouldBeSibling) {
|
|
75
|
+
const capitalized = [...validTypes].map(t => t.split(' ').map(w => w[0].toUpperCase() + w.slice(1)).join(' '));
|
|
76
|
+
errors.push({
|
|
77
|
+
file,
|
|
78
|
+
line: lineNum,
|
|
79
|
+
message: `Unknown section type: ${rawType}`,
|
|
80
|
+
suggestion: `Valid types: ${capitalized.join(', ')}`,
|
|
81
|
+
severity: 'error',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Treat as body content of current section
|
|
85
|
+
if (currentSection) {
|
|
86
|
+
currentBody.push(line);
|
|
87
|
+
}
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
// Sibling boundary rule (hierarchical mode only):
|
|
91
|
+
// Deeper headers go into the preceding sibling's body
|
|
92
|
+
if (!flat && currentSiblingLevel > 0 && headerLevel > currentSiblingLevel) {
|
|
93
|
+
currentBody.push(line);
|
|
94
|
+
continue;
|
|
110
95
|
}
|
|
96
|
+
// New sibling — finalize previous section
|
|
97
|
+
finalizeSection();
|
|
98
|
+
currentSiblingLevel = headerLevel;
|
|
111
99
|
currentSection = {
|
|
112
100
|
type: normalizedType.replaceAll(' ', '-'),
|
|
113
101
|
title,
|
|
@@ -118,43 +106,26 @@ export function parseSections(content, headerLevel, validTypes, file = '') {
|
|
|
118
106
|
level: headerLevel,
|
|
119
107
|
};
|
|
120
108
|
currentBody = [];
|
|
109
|
+
preHeaderWarned = false;
|
|
121
110
|
}
|
|
122
111
|
else {
|
|
123
112
|
if (currentSection) {
|
|
124
113
|
currentBody.push(line);
|
|
125
114
|
}
|
|
126
|
-
else
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
message: `Found '${actual} ${rawType}:' (heading level ${level}) but expected heading level ${headerLevel} (${expected} ${rawType}:)`,
|
|
139
|
-
suggestion: `Change '${actual}' to '${expected}'`,
|
|
140
|
-
severity: 'warning',
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
115
|
+
else {
|
|
116
|
+
// Before first section header — suppress warnings for field:: lines and blank lines
|
|
117
|
+
const isFieldLine = /^[\w-]+::\s/.test(line);
|
|
118
|
+
if (line.trim() && !isFieldLine && !preHeaderWarned) {
|
|
119
|
+
preHeaderWarned = true;
|
|
120
|
+
errors.push({
|
|
121
|
+
file,
|
|
122
|
+
line: lineNum,
|
|
123
|
+
message: 'Content found before first section header — this text will be ignored',
|
|
124
|
+
suggestion: 'Move this text into a section, or remove it',
|
|
125
|
+
severity: 'error',
|
|
126
|
+
});
|
|
145
127
|
}
|
|
146
128
|
}
|
|
147
|
-
if (!currentSection && line.trim() && !preHeaderWarned) {
|
|
148
|
-
// Non-blank content before any section header — will be silently lost
|
|
149
|
-
preHeaderWarned = true;
|
|
150
|
-
errors.push({
|
|
151
|
-
file,
|
|
152
|
-
line: lineNum,
|
|
153
|
-
message: 'Content found before first section header — this text will be ignored',
|
|
154
|
-
suggestion: 'Move this text into a section, or remove it',
|
|
155
|
-
severity: 'error',
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
129
|
}
|
|
159
130
|
}
|
|
160
131
|
// Don't forget last section
|
|
@@ -260,6 +231,14 @@ function parseFields(section, file) {
|
|
|
260
231
|
if (currentField) {
|
|
261
232
|
section.fields[currentField] = currentValue.join('\n').trim();
|
|
262
233
|
}
|
|
234
|
+
// Unescape \# → # at start of lines in markdown content fields.
|
|
235
|
+
// Authors use \## to prevent the parser from treating ## as a structural header;
|
|
236
|
+
// the escape must be stripped before the content reaches consumers (e.g. ReactMarkdown).
|
|
237
|
+
for (const field of MARKDOWN_CONTENT_FIELDS) {
|
|
238
|
+
if (section.fields[field]) {
|
|
239
|
+
section.fields[field] = section.fields[field].replace(/^\\(#+)/gm, '$1');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
263
242
|
return { warnings };
|
|
264
243
|
}
|
|
265
244
|
//# sourceMappingURL=sections.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sections.js","sourceRoot":"","sources":["../../src/parser/sections.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"sections.js","sourceRoot":"","sources":["../../src/parser/sections.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { ContentError } from
|
|
1
|
+
import type { ContentError } from "../index.js";
|
|
2
2
|
export interface ParsedVideoTranscript {
|
|
3
3
|
title: string;
|
|
4
4
|
channel: string;
|
|
5
5
|
url: string;
|
|
6
|
+
allowUnreachableUrl?: boolean;
|
|
6
7
|
}
|
|
7
8
|
export interface VideoTranscriptParseResult {
|
|
8
9
|
transcript: ParsedVideoTranscript | null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { parseFrontmatter } from
|
|
2
|
-
import { validateFrontmatter } from
|
|
3
|
-
import { stripAuthoringMarkup } from
|
|
1
|
+
import { parseFrontmatter } from "./frontmatter.js";
|
|
2
|
+
import { validateFrontmatter } from "../validator/validate-frontmatter.js";
|
|
3
|
+
import { stripAuthoringMarkup } from "./lens.js";
|
|
4
4
|
export function parseVideoTranscript(content, file) {
|
|
5
5
|
const errors = [];
|
|
6
6
|
// Strip authoring markup (CriticMarkup + Obsidian comments) before parsing
|
|
@@ -12,14 +12,14 @@ export function parseVideoTranscript(content, file) {
|
|
|
12
12
|
}
|
|
13
13
|
const { frontmatter } = frontmatterResult;
|
|
14
14
|
// Validate frontmatter against schema (typo detection + required fields)
|
|
15
|
-
const frontmatterErrors = validateFrontmatter(frontmatter,
|
|
15
|
+
const frontmatterErrors = validateFrontmatter(frontmatter, "video-transcript", file);
|
|
16
16
|
errors.push(...frontmatterErrors);
|
|
17
|
-
const hasRequiredError = frontmatterErrors.some(e => e.severity ===
|
|
17
|
+
const hasRequiredError = frontmatterErrors.some((e) => e.severity === "error");
|
|
18
18
|
if (hasRequiredError) {
|
|
19
19
|
return { transcript: null, errors };
|
|
20
20
|
}
|
|
21
21
|
// Check text fields for wikilinks (not url — that's a URL)
|
|
22
|
-
const textFields = [
|
|
22
|
+
const textFields = ["title", "channel"];
|
|
23
23
|
for (const field of textFields) {
|
|
24
24
|
const value = String(frontmatter[field]);
|
|
25
25
|
if (/\[\[.+?\]\]/.test(value)) {
|
|
@@ -28,7 +28,7 @@ export function parseVideoTranscript(content, file) {
|
|
|
28
28
|
line: 2,
|
|
29
29
|
message: `Field '${field}' contains a wikilink — use plain text`,
|
|
30
30
|
suggestion: `Remove [[...]] from '${field}'`,
|
|
31
|
-
severity:
|
|
31
|
+
severity: "error",
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -36,6 +36,7 @@ export function parseVideoTranscript(content, file) {
|
|
|
36
36
|
title: String(frontmatter.title),
|
|
37
37
|
channel: String(frontmatter.channel),
|
|
38
38
|
url: String(frontmatter.url),
|
|
39
|
+
allowUnreachableUrl: frontmatter.allowUnreachableUrl === true,
|
|
39
40
|
};
|
|
40
41
|
return { transcript, errors };
|
|
41
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video-transcript.js","sourceRoot":"","sources":["../../src/parser/video-transcript.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"video-transcript.js","sourceRoot":"","sources":["../../src/parser/video-transcript.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAgBjD,MAAM,UAAU,oBAAoB,CAClC,OAAe,EACf,IAAY;IAEZ,MAAM,MAAM,GAAmB,EAAE,CAAC;IAElC,2EAA2E;IAC3E,OAAO,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAExC,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC1D,IAAI,iBAAiB,CAAC,KAAK,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QACrC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACtC,CAAC;IAED,MAAM,EAAE,WAAW,EAAE,GAAG,iBAAiB,CAAC;IAE1C,yEAAyE;IACzE,MAAM,iBAAiB,GAAG,mBAAmB,CAC3C,WAAW,EACX,kBAAkB,EAClB,IAAI,CACL,CAAC;IACF,MAAM,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,CAAC;IAElC,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,IAAI,CAC7C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAC9B,CAAC;IACF,IAAI,gBAAgB,EAAE,CAAC;QACrB,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACtC,CAAC;IAED,2DAA2D;IAC3D,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,SAAS,CAAU,CAAC;IACjD,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;QACzC,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI;gBACJ,IAAI,EAAE,CAAC;gBACP,OAAO,EAAE,UAAU,KAAK,wCAAwC;gBAChE,UAAU,EAAE,wBAAwB,KAAK,GAAG;gBAC5C,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAA0B;QACxC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC;QAChC,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC;QACpC,GAAG,EAAE,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC;QAC5B,mBAAmB,EAAE,WAAW,CAAC,mBAAmB,KAAK,IAAI;KAC9D,CAAC;IAEF,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map each file path to the sorted list of course slugs whose reference chain
|
|
3
|
+
* transitively reaches it. Files reached by no course are simply absent (an
|
|
4
|
+
* absent or empty list means "orphaned").
|
|
5
|
+
*
|
|
6
|
+
* @param files all content files (path -> raw markdown)
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildFileToCourses(files: Map<string, string>): Record<string, string[]>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// src/reference-graph.ts
|
|
2
|
+
//
|
|
3
|
+
// Builds a file -> course-slugs map by walking the reference chain of each
|
|
4
|
+
// course (course -> module -> learning outcome -> lens -> article/transcript).
|
|
5
|
+
//
|
|
6
|
+
// Every reference in the content model is a wikilink: course->module sits in an
|
|
7
|
+
// H1 title, module->LO/lens and lens->article are `source::` embeds (`![[...]]`
|
|
8
|
+
// / `[[...]]`), and `::card[[...]]` links cross-reference lenses/modules. A
|
|
9
|
+
// single wikilink scan over the markup-stripped content therefore captures every
|
|
10
|
+
// edge type uniformly, with no per-parser wiring to drift out of sync.
|
|
11
|
+
//
|
|
12
|
+
// The scan is intentionally permissive (a wikilink in prose still counts as a
|
|
13
|
+
// reference). For the consumer — "which courses use this file / is it orphaned"
|
|
14
|
+
// — over-capture is the safe direction: it never marks a still-referenced file
|
|
15
|
+
// as orphaned.
|
|
16
|
+
import { resolveWikilinkPath, findFileWithExtension, } from "./parser/wikilink.js";
|
|
17
|
+
import { stripAuthoringMarkup } from "./parser/lens.js";
|
|
18
|
+
import { parseFrontmatter } from "./parser/frontmatter.js";
|
|
19
|
+
// Matches [[path]], [[path|alias]], [[path#anchor]] and (via the leading `[[`)
|
|
20
|
+
// the `![[...]]` / `::card[[...]]` forms too. Captures only the path segment.
|
|
21
|
+
const WIKILINK_RE = /\[\[\s*([^\]|#]+?)\s*(?:#[^\]|]*)?(?:\|[^\]]*)?\]\]/g;
|
|
22
|
+
function isCourseFile(path) {
|
|
23
|
+
return path.startsWith("courses/") || path.includes("/courses/");
|
|
24
|
+
}
|
|
25
|
+
function isTranscriptMd(path) {
|
|
26
|
+
return (path.endsWith(".md") &&
|
|
27
|
+
(path.startsWith("video_transcripts/") ||
|
|
28
|
+
path.includes("/video_transcripts/")));
|
|
29
|
+
}
|
|
30
|
+
/** Resolve every wikilink in `content` to a real file path in `files`. */
|
|
31
|
+
function outgoingReferences(path, content, files) {
|
|
32
|
+
const targets = new Set();
|
|
33
|
+
const stripped = stripAuthoringMarkup(content);
|
|
34
|
+
for (const match of stripped.matchAll(WIKILINK_RE)) {
|
|
35
|
+
const rawTarget = match[1].trim();
|
|
36
|
+
if (!rawTarget)
|
|
37
|
+
continue;
|
|
38
|
+
const resolved = resolveWikilinkPath(rawTarget, path);
|
|
39
|
+
const actual = findFileWithExtension(resolved, files);
|
|
40
|
+
if (actual && actual !== path)
|
|
41
|
+
targets.add(actual);
|
|
42
|
+
}
|
|
43
|
+
// A transcript's `.timestamps.json` is paired by filename, not wikilinked, but
|
|
44
|
+
// belongs to the same course as its `.md`, so carry it along.
|
|
45
|
+
if (isTranscriptMd(path)) {
|
|
46
|
+
const ts = path.replace(/\.md$/, ".timestamps.json");
|
|
47
|
+
if (files.has(ts))
|
|
48
|
+
targets.add(ts);
|
|
49
|
+
}
|
|
50
|
+
return targets;
|
|
51
|
+
}
|
|
52
|
+
/** Read a course file's frontmatter slug, or null if absent/unparseable. */
|
|
53
|
+
function courseSlug(path, content) {
|
|
54
|
+
const { frontmatter } = parseFrontmatter(stripAuthoringMarkup(content), path);
|
|
55
|
+
const slug = frontmatter.slug;
|
|
56
|
+
return typeof slug === "string" && slug.trim() ? slug.trim() : null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Map each file path to the sorted list of course slugs whose reference chain
|
|
60
|
+
* transitively reaches it. Files reached by no course are simply absent (an
|
|
61
|
+
* absent or empty list means "orphaned").
|
|
62
|
+
*
|
|
63
|
+
* @param files all content files (path -> raw markdown)
|
|
64
|
+
*/
|
|
65
|
+
export function buildFileToCourses(files) {
|
|
66
|
+
// 1. Adjacency: file -> files it references (resolved to real files only).
|
|
67
|
+
const adjacency = new Map();
|
|
68
|
+
for (const [path, content] of files.entries()) {
|
|
69
|
+
adjacency.set(path, outgoingReferences(path, content, files));
|
|
70
|
+
}
|
|
71
|
+
// 2. BFS from each course root, tagging every reached file with the slug.
|
|
72
|
+
const fileToCourses = new Map();
|
|
73
|
+
for (const [coursePath, courseContent] of files.entries()) {
|
|
74
|
+
if (!isCourseFile(coursePath))
|
|
75
|
+
continue;
|
|
76
|
+
const slug = courseSlug(coursePath, courseContent);
|
|
77
|
+
if (!slug)
|
|
78
|
+
continue; // course that failed to parse a slug — skip
|
|
79
|
+
const queue = [coursePath];
|
|
80
|
+
const seen = new Set([coursePath]);
|
|
81
|
+
// Head index instead of Array.shift() to keep the BFS dequeue O(1).
|
|
82
|
+
for (let head = 0; head < queue.length; head++) {
|
|
83
|
+
const file = queue[head];
|
|
84
|
+
let slugs = fileToCourses.get(file);
|
|
85
|
+
if (!slugs) {
|
|
86
|
+
slugs = new Set();
|
|
87
|
+
fileToCourses.set(file, slugs);
|
|
88
|
+
}
|
|
89
|
+
slugs.add(slug);
|
|
90
|
+
for (const next of adjacency.get(file) ?? []) {
|
|
91
|
+
if (!seen.has(next)) {
|
|
92
|
+
seen.add(next);
|
|
93
|
+
queue.push(next);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// 3. Serialize to a stable, sorted plain object for JSON output.
|
|
99
|
+
const result = {};
|
|
100
|
+
for (const [file, slugs] of fileToCourses.entries()) {
|
|
101
|
+
result[file] = [...slugs].sort();
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=reference-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference-graph.js","sourceRoot":"","sources":["../src/reference-graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const CARD_HTML_RE = /<div data-lens-card='([^']+)'><\/div>/g;
|
|
2
|
+
export function validateCardModuleMembership(modules) {
|
|
3
|
+
const errors = [];
|
|
4
|
+
// A module that contains `# Submodule:` boundaries is split into multiple
|
|
5
|
+
// FlattenedModules at flatten time (slug `parent/sub-1`, `parent/sub-2`, …),
|
|
6
|
+
// each carrying its own sections. From an authoring perspective these are
|
|
7
|
+
// still one curriculum unit — a hidden Lens import in one submodule should
|
|
8
|
+
// satisfy a card link from a sibling submodule. Group siblings by their
|
|
9
|
+
// parent slug for the membership check.
|
|
10
|
+
const groupContentIds = new Map();
|
|
11
|
+
for (const mod of modules) {
|
|
12
|
+
if (mod.slug.startsWith('lens/'))
|
|
13
|
+
continue;
|
|
14
|
+
const groupKey = mod.parentSlug ?? mod.slug;
|
|
15
|
+
let s = groupContentIds.get(groupKey);
|
|
16
|
+
if (!s) {
|
|
17
|
+
s = new Set();
|
|
18
|
+
groupContentIds.set(groupKey, s);
|
|
19
|
+
}
|
|
20
|
+
for (const section of mod.sections) {
|
|
21
|
+
if (section.contentId)
|
|
22
|
+
s.add(section.contentId);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const mod of modules) {
|
|
26
|
+
// Skip standalone-lens pseudo-modules (lens/ prefix). They are single-lens
|
|
27
|
+
// entries created for lenses not imported by any real module. Checking
|
|
28
|
+
// card membership from their narrow perspective generates false positives.
|
|
29
|
+
if (mod.slug.startsWith('lens/'))
|
|
30
|
+
continue;
|
|
31
|
+
const groupKey = mod.parentSlug ?? mod.slug;
|
|
32
|
+
const groupIds = groupContentIds.get(groupKey) ?? new Set();
|
|
33
|
+
for (const section of mod.sections) {
|
|
34
|
+
for (const seg of section.segments) {
|
|
35
|
+
if (seg.type !== 'text')
|
|
36
|
+
continue;
|
|
37
|
+
if (!seg.content.includes('data-lens-card='))
|
|
38
|
+
continue;
|
|
39
|
+
const matches = seg.content.matchAll(CARD_HTML_RE);
|
|
40
|
+
for (const match of matches) {
|
|
41
|
+
let data;
|
|
42
|
+
try {
|
|
43
|
+
data = JSON.parse(match[1].replace(/'/g, "'"));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (data.targetType !== 'lens')
|
|
49
|
+
continue;
|
|
50
|
+
const targetId = data.contentId;
|
|
51
|
+
if (!targetId)
|
|
52
|
+
continue; // missing contentId is a separate validation problem
|
|
53
|
+
if (groupIds.has(targetId))
|
|
54
|
+
continue; // same module-group: OK
|
|
55
|
+
if (data.allowExternal)
|
|
56
|
+
continue; // explicit external opt-in: OK
|
|
57
|
+
// Echo back the exact directive with the flag appended so the
|
|
58
|
+
// author can copy-paste it. Falls back to a `<path>` placeholder
|
|
59
|
+
// for older card data without targetPath.
|
|
60
|
+
const directive = data.targetPath
|
|
61
|
+
? `\`::card[[${data.targetPath}]]{allow-external}\``
|
|
62
|
+
: '`::card[[<path>]]{allow-external}`';
|
|
63
|
+
errors.push({
|
|
64
|
+
// Card directives live inside the section's source — typically an
|
|
65
|
+
// imported Lens, not the module file itself. Prefer the section's
|
|
66
|
+
// sourcePath so the error points at the file the author needs to
|
|
67
|
+
// edit. Falls back to mod.sourcePath for inline lenses defined in
|
|
68
|
+
// the module file directly.
|
|
69
|
+
file: section.sourcePath ?? mod.sourcePath ?? `module:${mod.slug}`,
|
|
70
|
+
message: `Card targets Lens "${data.title ?? targetId}" which is not imported by module "${groupKey}".`,
|
|
71
|
+
suggestion: `Either import the Lens into module "${groupKey}", or mark the directive as intentional by appending {allow-external}: ${directive}.`,
|
|
72
|
+
severity: 'error',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return errors;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=card-module-membership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card-module-membership.js","sourceRoot":"","sources":["../../src/validator/card-module-membership.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,20 +29,20 @@ export function validateOutputIntegrity(modules, slugToPath) {
|
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
break;
|
|
32
|
-
case 'article
|
|
32
|
+
case 'article':
|
|
33
33
|
if (!segment.content?.trim()) {
|
|
34
34
|
errors.push({
|
|
35
35
|
file,
|
|
36
|
-
message: `Empty article
|
|
36
|
+
message: `Empty article segment in "${sectionLabel}" (segment ${i + 1})`,
|
|
37
37
|
severity: 'error',
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
40
|
break;
|
|
41
|
-
case 'video
|
|
42
|
-
if (!segment.transcript?.
|
|
41
|
+
case 'video':
|
|
42
|
+
if (!segment.transcript?.length) {
|
|
43
43
|
errors.push({
|
|
44
44
|
file,
|
|
45
|
-
message: `Empty video
|
|
45
|
+
message: `Empty video transcript in "${sectionLabel}" (segment ${i + 1})`,
|
|
46
46
|
severity: 'error',
|
|
47
47
|
});
|
|
48
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-integrity.js","sourceRoot":"","sources":["../../src/validator/output-integrity.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAA0B,EAC1B,UAAgC;IAEhC,MAAM,MAAM,GAAmB,EAAE,CAAC;IAElC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC;QAEzD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAExD,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClC,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI;oBACJ,OAAO,EAAE,YAAY,YAAY,mBAAmB;oBACpD,QAAQ,EAAE,OAAO;iBAClB,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACjD,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAEpC,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;oBACrB,KAAK,MAAM;wBACT,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;4BAC7B,MAAM,CAAC,IAAI,CAAC;gCACV,IAAI;gCACJ,OAAO,EAAE,0BAA0B,YAAY,cAAc,CAAC,GAAG,CAAC,GAAG;gCACrE,QAAQ,EAAE,OAAO;6BAClB,CAAC,CAAC;wBACL,CAAC;wBACD,MAAM;oBACR,KAAK,
|
|
1
|
+
{"version":3,"file":"output-integrity.js","sourceRoot":"","sources":["../../src/validator/output-integrity.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAA0B,EAC1B,UAAgC;IAEhC,MAAM,MAAM,GAAmB,EAAE,CAAC;IAElC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC;QAEzD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAExD,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClC,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI;oBACJ,OAAO,EAAE,YAAY,YAAY,mBAAmB;oBACpD,QAAQ,EAAE,OAAO;iBAClB,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACjD,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAEpC,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;oBACrB,KAAK,MAAM;wBACT,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;4BAC7B,MAAM,CAAC,IAAI,CAAC;gCACV,IAAI;gCACJ,OAAO,EAAE,0BAA0B,YAAY,cAAc,CAAC,GAAG,CAAC,GAAG;gCACrE,QAAQ,EAAE,OAAO;6BAClB,CAAC,CAAC;wBACL,CAAC;wBACD,MAAM;oBACR,KAAK,SAAS;wBACZ,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;4BAC7B,MAAM,CAAC,IAAI,CAAC;gCACV,IAAI;gCACJ,OAAO,EAAE,6BAA6B,YAAY,cAAc,CAAC,GAAG,CAAC,GAAG;gCACxE,QAAQ,EAAE,OAAO;6BAClB,CAAC,CAAC;wBACL,CAAC;wBACD,MAAM;oBACR,KAAK,OAAO;wBACV,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;4BAChC,MAAM,CAAC,IAAI,CAAC;gCACV,IAAI;gCACJ,OAAO,EAAE,8BAA8B,YAAY,cAAc,CAAC,GAAG,CAAC,GAAG;gCACzE,QAAQ,EAAE,OAAO;6BAClB,CAAC,CAAC;wBACL,CAAC;wBACD,MAAM;oBACR,8CAA8C;gBAChD,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -3,7 +3,7 @@ import type { ContentError } from '../index.js';
|
|
|
3
3
|
* Validate that fields are appropriate for the segment type.
|
|
4
4
|
* Warns about fields that belong to different segment types.
|
|
5
5
|
*
|
|
6
|
-
* @param segmentType - The type of segment (text, chat, article
|
|
6
|
+
* @param segmentType - The type of segment (text, chat, article, video, question, roleplay)
|
|
7
7
|
* @param fields - Object mapping field names to their values
|
|
8
8
|
* @param file - File path for error reporting
|
|
9
9
|
* @param line - Line number for error reporting
|