lens-content-processor 0.2.2 → 0.3.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/article.js +4 -16
- package/dist/bundler/article.js.map +1 -1
- package/dist/cli.d.ts +1 -2
- package/dist/cli.js +6 -10
- package/dist/cli.js.map +1 -1
- package/dist/content-schema.js +6 -5
- package/dist/content-schema.js.map +1 -1
- package/dist/flattener/index.d.ts +21 -1
- package/dist/flattener/index.js +258 -11
- package/dist/flattener/index.js.map +1 -1
- package/dist/fs/read-vault.d.ts +1 -4
- package/dist/fs/read-vault.js +4 -8
- package/dist/fs/read-vault.js.map +1 -1
- package/dist/index.d.ts +17 -5
- package/dist/index.js +123 -16
- package/dist/index.js.map +1 -1
- package/dist/parser/article.js +6 -3
- package/dist/parser/article.js.map +1 -1
- package/dist/parser/course.d.ts +2 -2
- package/dist/parser/course.js +5 -15
- package/dist/parser/course.js.map +1 -1
- package/dist/parser/learning-outcome.d.ts +4 -2
- package/dist/parser/learning-outcome.js +49 -19
- package/dist/parser/learning-outcome.js.map +1 -1
- package/dist/parser/lens.d.ts +38 -1
- package/dist/parser/lens.js +47 -9
- package/dist/parser/lens.js.map +1 -1
- package/dist/parser/module.js +1 -1
- package/dist/parser/module.js.map +1 -1
- package/dist/parser/sections.js +5 -3
- package/dist/parser/sections.js.map +1 -1
- package/dist/parser/wikilink.js +6 -1
- package/dist/parser/wikilink.js.map +1 -1
- package/dist/utils/slug.d.ts +5 -0
- package/dist/utils/slug.js +16 -0
- package/dist/utils/slug.js.map +1 -0
- package/dist/validator/chat-precedence.d.ts +9 -0
- package/dist/validator/chat-precedence.js +32 -0
- package/dist/validator/chat-precedence.js.map +1 -0
- package/dist/validator/field-typos.js +0 -9
- package/dist/validator/field-typos.js.map +1 -1
- package/dist/validator/tier.d.ts +18 -0
- package/dist/validator/tier.js +74 -0
- package/dist/validator/tier.js.map +1 -0
- package/package.json +5 -5
package/dist/bundler/article.js
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* (which produce curly quotes) and source articles (which may use straight quotes).
|
|
5
|
-
*/
|
|
6
|
-
function normalizeQuotes(text) {
|
|
7
|
-
return text
|
|
8
|
-
.replaceAll('\u2018', "'") // left single quote
|
|
9
|
-
.replaceAll('\u2019', "'") // right single quote (smart apostrophe)
|
|
10
|
-
.replaceAll('\u201C', '"') // left double quote
|
|
11
|
-
.replaceAll('\u201D', '"'); // right double quote
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Find all occurrences of a substring in text (case-insensitive, quote-normalized).
|
|
15
|
-
* Returns array of start indices in the ORIGINAL text.
|
|
2
|
+
* Find all occurrences of a substring in text (case-insensitive).
|
|
3
|
+
* Returns array of start indices.
|
|
16
4
|
*/
|
|
17
5
|
function findAllOccurrences(text, anchor) {
|
|
18
|
-
const lowerText =
|
|
19
|
-
const lowerAnchor =
|
|
6
|
+
const lowerText = text.toLowerCase();
|
|
7
|
+
const lowerAnchor = anchor.toLowerCase();
|
|
20
8
|
const indices = [];
|
|
21
9
|
let pos = 0;
|
|
22
10
|
while (true) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"article.js","sourceRoot":"","sources":["../../src/bundler/article.ts"],"names":[],"mappings":"AAsBA
|
|
1
|
+
{"version":3,"file":"article.js","sourceRoot":"","sources":["../../src/bundler/article.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { ProcessResult } from './index.js';
|
|
2
|
+
import { type ProcessResult } from './index.js';
|
|
3
3
|
export interface CliOptions {
|
|
4
4
|
vaultPath: string | null;
|
|
5
5
|
outputPath: string | null;
|
|
6
|
-
includeWip: boolean;
|
|
7
6
|
stdin: boolean;
|
|
8
7
|
}
|
|
9
8
|
export declare function parseArgs(argv: string[]): CliOptions;
|
package/dist/cli.js
CHANGED
|
@@ -8,24 +8,20 @@ export function parseArgs(argv) {
|
|
|
8
8
|
const args = argv.slice(2);
|
|
9
9
|
let vaultPath = null;
|
|
10
10
|
let outputPath = null;
|
|
11
|
-
let
|
|
12
|
-
let stdin = false; // NEW
|
|
11
|
+
let stdin = false;
|
|
13
12
|
for (let i = 0; i < args.length; i++) {
|
|
14
13
|
if (args[i] === '--output' || args[i] === '-o') {
|
|
15
14
|
outputPath = args[i + 1] || null;
|
|
16
15
|
i++; // skip next arg
|
|
17
16
|
}
|
|
18
|
-
else if (args[i] === '--
|
|
19
|
-
includeWip = true;
|
|
20
|
-
}
|
|
21
|
-
else if (args[i] === '--stdin') { // NEW
|
|
17
|
+
else if (args[i] === '--stdin') {
|
|
22
18
|
stdin = true;
|
|
23
19
|
}
|
|
24
20
|
else if (!args[i].startsWith('-')) {
|
|
25
21
|
vaultPath = args[i];
|
|
26
22
|
}
|
|
27
23
|
}
|
|
28
|
-
return { vaultPath, outputPath,
|
|
24
|
+
return { vaultPath, outputPath, stdin };
|
|
29
25
|
}
|
|
30
26
|
async function readStdin() {
|
|
31
27
|
const chunks = [];
|
|
@@ -46,14 +42,14 @@ export async function run(options) {
|
|
|
46
42
|
if (!options.vaultPath) {
|
|
47
43
|
throw new Error('Vault path is required (or use --stdin)');
|
|
48
44
|
}
|
|
49
|
-
files = await readVaultFiles(options.vaultPath
|
|
45
|
+
files = await readVaultFiles(options.vaultPath);
|
|
50
46
|
}
|
|
51
|
-
return processContent(files
|
|
47
|
+
return processContent(files);
|
|
52
48
|
}
|
|
53
49
|
async function main() {
|
|
54
50
|
const options = parseArgs(process.argv);
|
|
55
51
|
if (!options.vaultPath && !options.stdin) {
|
|
56
|
-
console.error('Usage: npx tsx src/cli.ts <vault-path> [--output <file>]
|
|
52
|
+
console.error('Usage: npx tsx src/cli.ts <vault-path> [--output <file>]');
|
|
57
53
|
console.error(' npx tsx src/cli.ts --stdin [--output <file>]');
|
|
58
54
|
process.exit(1);
|
|
59
55
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,aAAa;AACb,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/content-schema.js
CHANGED
|
@@ -6,18 +6,19 @@ function segmentSchema(required, optional, booleanFields) {
|
|
|
6
6
|
return { requiredFields: required, optionalFields: optional, allFields: [...required, ...optional], booleanFields };
|
|
7
7
|
}
|
|
8
8
|
export const CONTENT_SCHEMAS = {
|
|
9
|
-
'module': contentSchema(['slug', 'title'], ['contentId', 'id', 'discussion']),
|
|
10
|
-
'course': contentSchema(['slug', 'title'], ['id']),
|
|
11
|
-
'lens': contentSchema(['id'], []),
|
|
12
|
-
'learning-outcome': contentSchema(['id'], ['discussion', 'learning-outcome']),
|
|
9
|
+
'module': contentSchema(['slug', 'title'], ['contentId', 'id', 'discussion', 'tags']),
|
|
10
|
+
'course': contentSchema(['slug', 'title'], ['id', 'tags']),
|
|
11
|
+
'lens': contentSchema(['id'], ['tags']),
|
|
12
|
+
'learning-outcome': contentSchema(['id'], ['discussion', 'learning-outcome', 'tags']),
|
|
13
13
|
'article': contentSchema(['title', 'author', 'source_url'], ['date', 'published', 'created', 'description', 'tags', 'url']),
|
|
14
|
-
'video-transcript': contentSchema(['title', 'channel', 'url'], []),
|
|
14
|
+
'video-transcript': contentSchema(['title', 'channel', 'url'], ['tags']),
|
|
15
15
|
};
|
|
16
16
|
export const SEGMENT_SCHEMAS = {
|
|
17
17
|
'text': segmentSchema(['content'], ['optional'], ['optional']),
|
|
18
18
|
'chat': segmentSchema(['instructions'], ['optional', 'hidePreviousContentFromUser', 'hidePreviousContentFromTutor'], ['optional', 'hidePreviousContentFromUser', 'hidePreviousContentFromTutor']),
|
|
19
19
|
'article-excerpt': segmentSchema([], ['from', 'to', 'optional'], ['optional']),
|
|
20
20
|
'video-excerpt': segmentSchema(['to'], ['from', 'optional'], ['optional']),
|
|
21
|
+
'question': segmentSchema(['content'], ['assessment-instructions', 'max-time', 'max-chars', 'enforce-voice', 'optional', 'feedback'], ['enforce-voice', 'optional', 'feedback']),
|
|
21
22
|
};
|
|
22
23
|
/**
|
|
23
24
|
* Valid fields per segment type, derived from SEGMENT_SCHEMAS.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-schema.js","sourceRoot":"","sources":["../src/content-schema.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAsBlG,SAAS,aAAa,CAAC,QAAkB,EAAE,QAAkB;IAC3D,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,GAAG,QAAQ,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;AACvG,CAAC;AAED,SAAS,aAAa,CAAC,QAAkB,EAAE,QAAkB,EAAE,aAAuB;IACpF,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,GAAG,QAAQ,EAAE,GAAG,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;AACtH,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAsC;IAChE,QAAQ,EAAE,aAAa,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"content-schema.js","sourceRoot":"","sources":["../src/content-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { FlattenedModule, ContentError } from '../index.js';
|
|
2
|
+
import type { ContentTier } from '../validator/tier.js';
|
|
3
|
+
import { type ParsedLens } from '../parser/lens.js';
|
|
2
4
|
export interface FlattenModuleResult {
|
|
3
5
|
module: FlattenedModule | null;
|
|
4
6
|
errors: ContentError[];
|
|
@@ -17,4 +19,22 @@ export interface FlattenModuleResult {
|
|
|
17
19
|
* @param visitedPaths - Optional set of already-visited paths for cycle detection
|
|
18
20
|
* @returns Flattened module with resolved sections and segments, plus any errors
|
|
19
21
|
*/
|
|
20
|
-
export declare function flattenModule(modulePath: string, files: Map<string, string>, visitedPaths?: Set<string>): FlattenModuleResult;
|
|
22
|
+
export declare function flattenModule(modulePath: string, files: Map<string, string>, visitedPaths?: Set<string>, tierMap?: Map<string, ContentTier>): FlattenModuleResult;
|
|
23
|
+
/**
|
|
24
|
+
* Flatten a single Lens file into a FlattenedModule.
|
|
25
|
+
*
|
|
26
|
+
* This wraps a standalone Lens as a single-section module so it can be
|
|
27
|
+
* rendered by the frontend using the existing Module.tsx component.
|
|
28
|
+
*
|
|
29
|
+
* The resulting module has:
|
|
30
|
+
* - slug: 'lens/' + fileNameToSlug(lensPath)
|
|
31
|
+
* - title: extracted from source metadata or lens header
|
|
32
|
+
* - sections: exactly one section (lens-article, lens-video, or page)
|
|
33
|
+
*
|
|
34
|
+
* @param lensPath - Path to the lens file within the files Map
|
|
35
|
+
* @param files - Map of all file paths to their content
|
|
36
|
+
* @param tierMap - Optional tier map for filtering ignored content
|
|
37
|
+
* @param preParsedLens - Optional pre-parsed lens to avoid re-parsing
|
|
38
|
+
* @returns Flattened module with a single section, plus any errors
|
|
39
|
+
*/
|
|
40
|
+
export declare function flattenLens(lensPath: string, files: Map<string, string>, tierMap?: Map<string, ContentTier>, preParsedLens?: ParsedLens): FlattenModuleResult;
|
package/dist/flattener/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { checkTierViolation } from '../validator/tier.js';
|
|
1
2
|
import { parseModule, parsePageSegments } from '../parser/module.js';
|
|
2
3
|
import { parseLearningOutcome } from '../parser/learning-outcome.js';
|
|
3
4
|
import { parseLens } from '../parser/lens.js';
|
|
4
5
|
import { parseWikilink, resolveWikilinkPath, findFileWithExtension, findSimilarFiles, formatSuggestion } from '../parser/wikilink.js';
|
|
5
6
|
import { parseFrontmatter } from '../parser/frontmatter.js';
|
|
7
|
+
import { fileNameToSlug } from '../utils/slug.js';
|
|
6
8
|
import { extractArticleExcerpt } from '../bundler/article.js';
|
|
7
9
|
import { extractVideoExcerpt } from '../bundler/video.js';
|
|
8
10
|
/**
|
|
@@ -34,7 +36,7 @@ function extractVideoIdFromUrl(url) {
|
|
|
34
36
|
* @param visitedPaths - Optional set of already-visited paths for cycle detection
|
|
35
37
|
* @returns Flattened module with resolved sections and segments, plus any errors
|
|
36
38
|
*/
|
|
37
|
-
export function flattenModule(modulePath, files, visitedPaths = new Set()) {
|
|
39
|
+
export function flattenModule(modulePath, files, visitedPaths = new Set(), tierMap) {
|
|
38
40
|
// Check for circular reference
|
|
39
41
|
if (visitedPaths.has(modulePath)) {
|
|
40
42
|
return {
|
|
@@ -75,7 +77,7 @@ export function flattenModule(modulePath, files, visitedPaths = new Set()) {
|
|
|
75
77
|
// This allows the same file to be referenced in different sections
|
|
76
78
|
// while still detecting cycles within a single chain
|
|
77
79
|
const sectionVisitedPaths = new Set(visitedPaths);
|
|
78
|
-
const result = flattenLearningOutcomeSection(section, modulePath, files, sectionVisitedPaths);
|
|
80
|
+
const result = flattenLearningOutcomeSection(section, modulePath, files, sectionVisitedPaths, tierMap);
|
|
79
81
|
errors.push(...result.errors);
|
|
80
82
|
flattenedSections.push(...result.sections);
|
|
81
83
|
}
|
|
@@ -100,7 +102,7 @@ export function flattenModule(modulePath, files, visitedPaths = new Set()) {
|
|
|
100
102
|
// Uncategorized sections can contain ## Lens: references
|
|
101
103
|
// Each lens becomes its own section (lens-video, lens-article, or page)
|
|
102
104
|
const sectionVisitedPaths = new Set(visitedPaths);
|
|
103
|
-
const result = flattenUncategorizedSection(section, modulePath, files, sectionVisitedPaths);
|
|
105
|
+
const result = flattenUncategorizedSection(section, modulePath, files, sectionVisitedPaths, tierMap);
|
|
104
106
|
errors.push(...result.errors);
|
|
105
107
|
flattenedSections.push(...result.sections);
|
|
106
108
|
}
|
|
@@ -120,7 +122,7 @@ export function flattenModule(modulePath, files, visitedPaths = new Set()) {
|
|
|
120
122
|
* Flatten a Learning Outcome section by resolving its LO file and all referenced lenses.
|
|
121
123
|
* Each lens in the LO becomes its own section.
|
|
122
124
|
*/
|
|
123
|
-
function flattenLearningOutcomeSection(section, modulePath, files, visitedPaths) {
|
|
125
|
+
function flattenLearningOutcomeSection(section, modulePath, files, visitedPaths, tierMap) {
|
|
124
126
|
const errors = [];
|
|
125
127
|
const sections = [];
|
|
126
128
|
// Get the source wikilink
|
|
@@ -144,7 +146,9 @@ function flattenLearningOutcomeSection(section, modulePath, files, visitedPaths)
|
|
|
144
146
|
errors.push({
|
|
145
147
|
file: modulePath,
|
|
146
148
|
line: section.line,
|
|
147
|
-
message:
|
|
149
|
+
message: wikilink?.error
|
|
150
|
+
? `${wikilink.error}: ${source}`
|
|
151
|
+
: `Invalid wikilink format: ${source}`,
|
|
148
152
|
suggestion,
|
|
149
153
|
severity: 'error',
|
|
150
154
|
});
|
|
@@ -166,6 +170,19 @@ function flattenLearningOutcomeSection(section, modulePath, files, visitedPaths)
|
|
|
166
170
|
});
|
|
167
171
|
return { sections: [], errors };
|
|
168
172
|
}
|
|
173
|
+
// Check tier violation (module → LO)
|
|
174
|
+
if (tierMap) {
|
|
175
|
+
const parentTier = tierMap.get(modulePath) ?? 'production';
|
|
176
|
+
const childTier = tierMap.get(loPath) ?? 'production';
|
|
177
|
+
const violation = checkTierViolation(modulePath, parentTier, loPath, childTier, 'learning outcome', section.line);
|
|
178
|
+
if (violation) {
|
|
179
|
+
errors.push(violation);
|
|
180
|
+
}
|
|
181
|
+
// Skip ignored children silently (they're not processed)
|
|
182
|
+
if (childTier === 'ignored') {
|
|
183
|
+
return { sections: [], errors };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
169
186
|
// Check for circular reference
|
|
170
187
|
if (visitedPaths.has(loPath)) {
|
|
171
188
|
errors.push({
|
|
@@ -200,6 +217,18 @@ function flattenLearningOutcomeSection(section, modulePath, files, visitedPaths)
|
|
|
200
217
|
});
|
|
201
218
|
continue;
|
|
202
219
|
}
|
|
220
|
+
// Check tier violation (LO → Lens)
|
|
221
|
+
if (tierMap) {
|
|
222
|
+
const parentTier = tierMap.get(loPath) ?? 'production';
|
|
223
|
+
const childTier = tierMap.get(lensPath) ?? 'production';
|
|
224
|
+
const violation = checkTierViolation(loPath, parentTier, lensPath, childTier, 'lens');
|
|
225
|
+
if (violation) {
|
|
226
|
+
errors.push(violation);
|
|
227
|
+
}
|
|
228
|
+
if (childTier === 'ignored') {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
203
232
|
// Check for circular reference
|
|
204
233
|
if (visitedPaths.has(lensPath)) {
|
|
205
234
|
errors.push({
|
|
@@ -242,7 +271,8 @@ function flattenLearningOutcomeSection(section, modulePath, files, visitedPaths)
|
|
|
242
271
|
meta.title = articleFrontmatter.frontmatter.title;
|
|
243
272
|
}
|
|
244
273
|
if (articleFrontmatter.frontmatter.author) {
|
|
245
|
-
|
|
274
|
+
const raw = articleFrontmatter.frontmatter.author;
|
|
275
|
+
meta.author = Array.isArray(raw) ? raw.join(', ') : String(raw);
|
|
246
276
|
}
|
|
247
277
|
if (articleFrontmatter.frontmatter.source_url) {
|
|
248
278
|
meta.sourceUrl = articleFrontmatter.frontmatter.source_url;
|
|
@@ -289,7 +319,7 @@ function flattenLearningOutcomeSection(section, modulePath, files, visitedPaths)
|
|
|
289
319
|
}
|
|
290
320
|
// Process segments
|
|
291
321
|
for (const parsedSegment of lensSection.segments) {
|
|
292
|
-
const segmentResult = convertSegment(parsedSegment, lensSection, lensPath, files, visitedPaths);
|
|
322
|
+
const segmentResult = convertSegment(parsedSegment, lensSection, lensPath, files, visitedPaths, tierMap);
|
|
293
323
|
errors.push(...segmentResult.errors);
|
|
294
324
|
if (segmentResult.segment) {
|
|
295
325
|
segments.push(segmentResult.segment);
|
|
@@ -312,13 +342,46 @@ function flattenLearningOutcomeSection(section, modulePath, files, visitedPaths)
|
|
|
312
342
|
};
|
|
313
343
|
sections.push(resultSection);
|
|
314
344
|
}
|
|
345
|
+
// After lens processing, add test section if present with inline segments
|
|
346
|
+
if (lo.test && lo.test.segments.length > 0) {
|
|
347
|
+
const testSegments = [];
|
|
348
|
+
for (const parsedSegment of lo.test.segments) {
|
|
349
|
+
// For test sections with question/chat/text segments, no source file resolution needed
|
|
350
|
+
// Create a minimal stub lensSection since these segment types don't need it
|
|
351
|
+
const stubLensSection = {
|
|
352
|
+
type: 'page',
|
|
353
|
+
title: 'Test',
|
|
354
|
+
segments: [],
|
|
355
|
+
line: 0,
|
|
356
|
+
};
|
|
357
|
+
const segmentResult = convertSegment(parsedSegment, stubLensSection, loPath, files, visitedPaths, tierMap);
|
|
358
|
+
errors.push(...segmentResult.errors);
|
|
359
|
+
if (segmentResult.segment) {
|
|
360
|
+
testSegments.push(segmentResult.segment);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (testSegments.length > 0) {
|
|
364
|
+
const hasFeedback = testSegments.some((s) => s.type === 'question' && s.feedback);
|
|
365
|
+
sections.push({
|
|
366
|
+
type: 'test',
|
|
367
|
+
meta: { title: 'Test' },
|
|
368
|
+
segments: testSegments,
|
|
369
|
+
optional: false,
|
|
370
|
+
...(hasFeedback && { feedback: true }),
|
|
371
|
+
contentId: null,
|
|
372
|
+
learningOutcomeId: lo.id ?? null,
|
|
373
|
+
learningOutcomeName: loPath.split('/').pop()?.replace(/\.md$/i, '') ?? null,
|
|
374
|
+
videoId: null,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
315
378
|
return { sections, errors };
|
|
316
379
|
}
|
|
317
380
|
/**
|
|
318
381
|
* Flatten an Uncategorized section by parsing its ## Lens: references.
|
|
319
382
|
* Each lens becomes its own section with the appropriate type (lens-video, lens-article, or page).
|
|
320
383
|
*/
|
|
321
|
-
function flattenUncategorizedSection(section, modulePath, files, visitedPaths) {
|
|
384
|
+
function flattenUncategorizedSection(section, modulePath, files, visitedPaths, tierMap) {
|
|
322
385
|
const errors = [];
|
|
323
386
|
const sections = [];
|
|
324
387
|
// Parse the section body for ## Lens: subsections
|
|
@@ -349,6 +412,18 @@ function flattenUncategorizedSection(section, modulePath, files, visitedPaths) {
|
|
|
349
412
|
});
|
|
350
413
|
continue;
|
|
351
414
|
}
|
|
415
|
+
// Check tier violation (Uncategorized/Module → Lens)
|
|
416
|
+
if (tierMap) {
|
|
417
|
+
const parentTier = tierMap.get(modulePath) ?? 'production';
|
|
418
|
+
const childTier = tierMap.get(lensPath) ?? 'production';
|
|
419
|
+
const violation = checkTierViolation(modulePath, parentTier, lensPath, childTier, 'lens');
|
|
420
|
+
if (violation) {
|
|
421
|
+
errors.push(violation);
|
|
422
|
+
}
|
|
423
|
+
if (childTier === 'ignored') {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
352
427
|
// Check for circular reference
|
|
353
428
|
if (visitedPaths.has(lensPath)) {
|
|
354
429
|
errors.push({
|
|
@@ -391,7 +466,8 @@ function flattenUncategorizedSection(section, modulePath, files, visitedPaths) {
|
|
|
391
466
|
meta.title = articleFrontmatter.frontmatter.title;
|
|
392
467
|
}
|
|
393
468
|
if (articleFrontmatter.frontmatter.author) {
|
|
394
|
-
|
|
469
|
+
const raw = articleFrontmatter.frontmatter.author;
|
|
470
|
+
meta.author = Array.isArray(raw) ? raw.join(', ') : String(raw);
|
|
395
471
|
}
|
|
396
472
|
if (articleFrontmatter.frontmatter.source_url) {
|
|
397
473
|
meta.sourceUrl = articleFrontmatter.frontmatter.source_url;
|
|
@@ -438,7 +514,7 @@ function flattenUncategorizedSection(section, modulePath, files, visitedPaths) {
|
|
|
438
514
|
}
|
|
439
515
|
// Process segments
|
|
440
516
|
for (const parsedSegment of lensSection.segments) {
|
|
441
|
-
const segmentResult = convertSegment(parsedSegment, lensSection, lensPath, files, visitedPaths);
|
|
517
|
+
const segmentResult = convertSegment(parsedSegment, lensSection, lensPath, files, visitedPaths, tierMap);
|
|
442
518
|
errors.push(...segmentResult.errors);
|
|
443
519
|
if (segmentResult.segment) {
|
|
444
520
|
segments.push(segmentResult.segment);
|
|
@@ -574,7 +650,7 @@ function parseUncategorizedLensRefs(body, parentPath) {
|
|
|
574
650
|
* Convert a parsed lens segment into a final flattened segment.
|
|
575
651
|
* For article-excerpt and video-excerpt, this involves extracting content from source files.
|
|
576
652
|
*/
|
|
577
|
-
function convertSegment(parsedSegment, lensSection, lensPath, files, visitedPaths) {
|
|
653
|
+
function convertSegment(parsedSegment, lensSection, lensPath, files, visitedPaths, tierMap) {
|
|
578
654
|
const errors = [];
|
|
579
655
|
switch (parsedSegment.type) {
|
|
580
656
|
case 'text': {
|
|
@@ -642,6 +718,18 @@ function convertSegment(parsedSegment, lensSection, lensPath, files, visitedPath
|
|
|
642
718
|
});
|
|
643
719
|
return { segment: null, errors };
|
|
644
720
|
}
|
|
721
|
+
// Check tier violation (Lens → Article)
|
|
722
|
+
if (tierMap) {
|
|
723
|
+
const parentTier = tierMap.get(lensPath) ?? 'production';
|
|
724
|
+
const childTier = tierMap.get(articlePath) ?? 'production';
|
|
725
|
+
const violation = checkTierViolation(lensPath, parentTier, articlePath, childTier, 'article');
|
|
726
|
+
if (violation) {
|
|
727
|
+
errors.push(violation);
|
|
728
|
+
}
|
|
729
|
+
if (childTier === 'ignored') {
|
|
730
|
+
return { segment: null, errors };
|
|
731
|
+
}
|
|
732
|
+
}
|
|
645
733
|
// Check if the article path points back to an already-visited structural file
|
|
646
734
|
// This would indicate a circular reference (e.g., a lens source pointing back to an LO)
|
|
647
735
|
// Note: We only check, we don't add article paths to visitedPaths since
|
|
@@ -707,6 +795,18 @@ function convertSegment(parsedSegment, lensSection, lensPath, files, visitedPath
|
|
|
707
795
|
});
|
|
708
796
|
return { segment: null, errors };
|
|
709
797
|
}
|
|
798
|
+
// Check tier violation (Lens → Video)
|
|
799
|
+
if (tierMap) {
|
|
800
|
+
const parentTier = tierMap.get(lensPath) ?? 'production';
|
|
801
|
+
const childTier = tierMap.get(videoPath) ?? 'production';
|
|
802
|
+
const violation = checkTierViolation(lensPath, parentTier, videoPath, childTier, 'video transcript');
|
|
803
|
+
if (violation) {
|
|
804
|
+
errors.push(violation);
|
|
805
|
+
}
|
|
806
|
+
if (childTier === 'ignored') {
|
|
807
|
+
return { segment: null, errors };
|
|
808
|
+
}
|
|
809
|
+
}
|
|
710
810
|
// Check if the video path points back to an already-visited structural file
|
|
711
811
|
// This would indicate a circular reference
|
|
712
812
|
// Note: We only check, we don't add video paths to visitedPaths since
|
|
@@ -754,8 +854,155 @@ function convertSegment(parsedSegment, lensSection, lensPath, files, visitedPath
|
|
|
754
854
|
}
|
|
755
855
|
return { segment, errors };
|
|
756
856
|
}
|
|
857
|
+
case 'question': {
|
|
858
|
+
const segment = {
|
|
859
|
+
type: 'question',
|
|
860
|
+
content: parsedSegment.content,
|
|
861
|
+
};
|
|
862
|
+
if (parsedSegment.assessmentInstructions)
|
|
863
|
+
segment.assessmentInstructions = parsedSegment.assessmentInstructions;
|
|
864
|
+
if (parsedSegment.maxTime)
|
|
865
|
+
segment.maxTime = parsedSegment.maxTime;
|
|
866
|
+
if (parsedSegment.maxChars !== undefined)
|
|
867
|
+
segment.maxChars = parsedSegment.maxChars;
|
|
868
|
+
if (parsedSegment.enforceVoice)
|
|
869
|
+
segment.enforceVoice = true;
|
|
870
|
+
if (parsedSegment.optional)
|
|
871
|
+
segment.optional = true;
|
|
872
|
+
if (parsedSegment.feedback)
|
|
873
|
+
segment.feedback = true;
|
|
874
|
+
return { segment, errors };
|
|
875
|
+
}
|
|
757
876
|
default:
|
|
758
877
|
return { segment: null, errors };
|
|
759
878
|
}
|
|
760
879
|
}
|
|
880
|
+
/**
|
|
881
|
+
* Flatten a single Lens file into a FlattenedModule.
|
|
882
|
+
*
|
|
883
|
+
* This wraps a standalone Lens as a single-section module so it can be
|
|
884
|
+
* rendered by the frontend using the existing Module.tsx component.
|
|
885
|
+
*
|
|
886
|
+
* The resulting module has:
|
|
887
|
+
* - slug: 'lens/' + fileNameToSlug(lensPath)
|
|
888
|
+
* - title: extracted from source metadata or lens header
|
|
889
|
+
* - sections: exactly one section (lens-article, lens-video, or page)
|
|
890
|
+
*
|
|
891
|
+
* @param lensPath - Path to the lens file within the files Map
|
|
892
|
+
* @param files - Map of all file paths to their content
|
|
893
|
+
* @param tierMap - Optional tier map for filtering ignored content
|
|
894
|
+
* @param preParsedLens - Optional pre-parsed lens to avoid re-parsing
|
|
895
|
+
* @returns Flattened module with a single section, plus any errors
|
|
896
|
+
*/
|
|
897
|
+
export function flattenLens(lensPath, files, tierMap, preParsedLens) {
|
|
898
|
+
const errors = [];
|
|
899
|
+
// Skip ignored lenses
|
|
900
|
+
if (tierMap?.get(lensPath) === 'ignored') {
|
|
901
|
+
return { module: null, errors };
|
|
902
|
+
}
|
|
903
|
+
const lensContent = files.get(lensPath);
|
|
904
|
+
if (!lensContent) {
|
|
905
|
+
errors.push({
|
|
906
|
+
file: lensPath,
|
|
907
|
+
message: `Lens file not found: ${lensPath}`,
|
|
908
|
+
severity: 'error',
|
|
909
|
+
});
|
|
910
|
+
return { module: null, errors };
|
|
911
|
+
}
|
|
912
|
+
// Use pre-parsed lens if provided, otherwise parse
|
|
913
|
+
const lens = preParsedLens ?? (() => {
|
|
914
|
+
const lensResult = parseLens(lensContent, lensPath);
|
|
915
|
+
errors.push(...lensResult.errors);
|
|
916
|
+
return lensResult.lens;
|
|
917
|
+
})();
|
|
918
|
+
if (!lens) {
|
|
919
|
+
return { module: null, errors };
|
|
920
|
+
}
|
|
921
|
+
const visitedPaths = new Set([lensPath]);
|
|
922
|
+
// Process lens sections into a single flattened Section
|
|
923
|
+
let sectionType = 'page';
|
|
924
|
+
const meta = {};
|
|
925
|
+
const segments = [];
|
|
926
|
+
let videoId;
|
|
927
|
+
for (const lensSection of lens.sections) {
|
|
928
|
+
if (lensSection.type === 'lens-article') {
|
|
929
|
+
sectionType = 'lens-article';
|
|
930
|
+
// Extract article metadata from the article file's frontmatter
|
|
931
|
+
if (lensSection.source) {
|
|
932
|
+
const articleWikilink = parseWikilink(lensSection.source);
|
|
933
|
+
if (articleWikilink && !articleWikilink.error) {
|
|
934
|
+
const articlePathResolved = resolveWikilinkPath(articleWikilink.path, lensPath);
|
|
935
|
+
const articlePath = findFileWithExtension(articlePathResolved, files);
|
|
936
|
+
if (articlePath) {
|
|
937
|
+
const articleContent = files.get(articlePath);
|
|
938
|
+
const articleFrontmatter = parseFrontmatter(articleContent, articlePath);
|
|
939
|
+
if (articleFrontmatter.frontmatter.title)
|
|
940
|
+
meta.title = articleFrontmatter.frontmatter.title;
|
|
941
|
+
if (articleFrontmatter.frontmatter.author)
|
|
942
|
+
meta.author = articleFrontmatter.frontmatter.author;
|
|
943
|
+
if (articleFrontmatter.frontmatter.source_url)
|
|
944
|
+
meta.sourceUrl = articleFrontmatter.frontmatter.source_url;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
else if (lensSection.type === 'page') {
|
|
950
|
+
sectionType = 'page';
|
|
951
|
+
if (lensSection.title)
|
|
952
|
+
meta.title = lensSection.title;
|
|
953
|
+
}
|
|
954
|
+
else if (lensSection.type === 'lens-video') {
|
|
955
|
+
sectionType = 'lens-video';
|
|
956
|
+
// Extract video metadata from the video transcript file's frontmatter
|
|
957
|
+
if (lensSection.source) {
|
|
958
|
+
const videoWikilink = parseWikilink(lensSection.source);
|
|
959
|
+
if (videoWikilink && !videoWikilink.error) {
|
|
960
|
+
const videoPathResolved = resolveWikilinkPath(videoWikilink.path, lensPath);
|
|
961
|
+
const videoPath = findFileWithExtension(videoPathResolved, files);
|
|
962
|
+
if (videoPath) {
|
|
963
|
+
const videoContent = files.get(videoPath);
|
|
964
|
+
const videoFrontmatter = parseFrontmatter(videoContent, videoPath);
|
|
965
|
+
if (videoFrontmatter.frontmatter.title)
|
|
966
|
+
meta.title = videoFrontmatter.frontmatter.title;
|
|
967
|
+
if (videoFrontmatter.frontmatter.channel)
|
|
968
|
+
meta.channel = videoFrontmatter.frontmatter.channel;
|
|
969
|
+
if (videoFrontmatter.frontmatter.url) {
|
|
970
|
+
const extractedId = extractVideoIdFromUrl(videoFrontmatter.frontmatter.url);
|
|
971
|
+
if (extractedId)
|
|
972
|
+
videoId = extractedId;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
// Process segments
|
|
979
|
+
for (const parsedSegment of lensSection.segments) {
|
|
980
|
+
const segmentResult = convertSegment(parsedSegment, lensSection, lensPath, files, visitedPaths, tierMap);
|
|
981
|
+
errors.push(...segmentResult.errors);
|
|
982
|
+
if (segmentResult.segment)
|
|
983
|
+
segments.push(segmentResult.segment);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
// Fallback title from filename if not extracted from source metadata
|
|
987
|
+
if (!meta.title) {
|
|
988
|
+
meta.title = fileNameToSlug(lensPath).replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
989
|
+
}
|
|
990
|
+
const section = {
|
|
991
|
+
type: sectionType,
|
|
992
|
+
meta,
|
|
993
|
+
segments,
|
|
994
|
+
optional: false,
|
|
995
|
+
learningOutcomeId: null,
|
|
996
|
+
learningOutcomeName: null,
|
|
997
|
+
contentId: lens.id ?? null,
|
|
998
|
+
videoId: videoId ?? null,
|
|
999
|
+
};
|
|
1000
|
+
const flattenedModule = {
|
|
1001
|
+
slug: 'lens/' + fileNameToSlug(lensPath),
|
|
1002
|
+
title: meta.title ?? fileNameToSlug(lensPath),
|
|
1003
|
+
contentId: lens.id ?? null,
|
|
1004
|
+
sections: [section],
|
|
1005
|
+
};
|
|
1006
|
+
return { module: flattenedModule, errors };
|
|
1007
|
+
}
|
|
761
1008
|
//# sourceMappingURL=index.js.map
|