lens-content-processor 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/bundler/video.d.ts +12 -6
  2. package/dist/bundler/video.js +57 -34
  3. package/dist/bundler/video.js.map +1 -1
  4. package/dist/cli.d.ts +11 -2
  5. package/dist/cli.js +36 -19
  6. package/dist/cli.js.map +1 -1
  7. package/dist/content-schema.js +71 -16
  8. package/dist/content-schema.js.map +1 -1
  9. package/dist/dedupe-errors.d.ts +14 -0
  10. package/dist/dedupe-errors.js +33 -0
  11. package/dist/dedupe-errors.js.map +1 -0
  12. package/dist/flattener/index.d.ts +3 -28
  13. package/dist/flattener/index.js +507 -745
  14. package/dist/flattener/index.js.map +1 -1
  15. package/dist/flattener/resolve-text-links.d.ts +25 -0
  16. package/dist/flattener/resolve-text-links.js +248 -0
  17. package/dist/flattener/resolve-text-links.js.map +1 -0
  18. package/dist/flattener/test-helpers.d.ts +1 -1
  19. package/dist/flattener/test-helpers.js +1 -3
  20. package/dist/flattener/test-helpers.js.map +1 -1
  21. package/dist/index.d.ts +53 -21
  22. package/dist/index.js +285 -140
  23. package/dist/index.js.map +1 -1
  24. package/dist/parser/article.d.ts +2 -1
  25. package/dist/parser/article.js +75 -14
  26. package/dist/parser/article.js.map +1 -1
  27. package/dist/parser/course.d.ts +1 -1
  28. package/dist/parser/course.js +79 -22
  29. package/dist/parser/course.js.map +1 -1
  30. package/dist/parser/frontmatter.d.ts +1 -1
  31. package/dist/parser/frontmatter.js +38 -12
  32. package/dist/parser/frontmatter.js.map +1 -1
  33. package/dist/parser/learning-outcome.d.ts +5 -2
  34. package/dist/parser/learning-outcome.js +75 -37
  35. package/dist/parser/learning-outcome.js.map +1 -1
  36. package/dist/parser/lens.d.ts +40 -41
  37. package/dist/parser/lens.js +365 -349
  38. package/dist/parser/lens.js.map +1 -1
  39. package/dist/parser/module.d.ts +7 -15
  40. package/dist/parser/module.js +135 -171
  41. package/dist/parser/module.js.map +1 -1
  42. package/dist/parser/sections.d.ts +12 -1
  43. package/dist/parser/sections.js +74 -95
  44. package/dist/parser/sections.js.map +1 -1
  45. package/dist/parser/video-transcript.d.ts +2 -1
  46. package/dist/parser/video-transcript.js +8 -7
  47. package/dist/parser/video-transcript.js.map +1 -1
  48. package/dist/reference-graph.d.ts +8 -0
  49. package/dist/reference-graph.js +105 -0
  50. package/dist/reference-graph.js.map +1 -0
  51. package/dist/validator/card-module-membership.d.ts +2 -0
  52. package/dist/validator/card-module-membership.js +80 -0
  53. package/dist/validator/card-module-membership.js.map +1 -0
  54. package/dist/validator/output-integrity.js +5 -5
  55. package/dist/validator/output-integrity.js.map +1 -1
  56. package/dist/validator/segment-fields.d.ts +1 -1
  57. package/dist/validator/segment-fields.js +2 -2
  58. package/dist/validator/segment-fields.js.map +1 -1
  59. package/dist/validator/test-segments.d.ts +12 -0
  60. package/dist/validator/test-segments.js +40 -0
  61. package/dist/validator/test-segments.js.map +1 -0
  62. package/dist/validator/url-reachability.d.ts +2 -1
  63. package/dist/validator/url-reachability.js +40 -20
  64. package/dist/validator/url-reachability.js.map +1 -1
  65. package/package.json +1 -1
  66. package/dist/validator/chat-precedence.d.ts +0 -9
  67. package/dist/validator/chat-precedence.js +0 -32
  68. package/dist/validator/chat-precedence.js.map +0 -1
@@ -1,8 +1,13 @@
1
1
  import type { ContentError } from '../index.js';
2
+ export interface TranscriptSentence {
3
+ text: string;
4
+ start: number;
5
+ end: number;
6
+ }
2
7
  export interface VideoExcerptResult {
3
8
  from?: number;
4
9
  to?: number | null;
5
- transcript?: string;
10
+ transcript?: TranscriptSentence[];
6
11
  error?: ContentError;
7
12
  }
8
13
  /**
@@ -25,14 +30,15 @@ export interface TimestampEntry {
25
30
  */
26
31
  export declare function parseTimestamp(str: string): number | null;
27
32
  /**
28
- * Extract words from timestamps data that fall within the requested time range.
33
+ * Extract words from timestamps data that fall within the requested time range,
34
+ * grouped into sentences by punctuation.
29
35
  *
30
36
  * @param timestamps - Array of word-level timestamp entries
31
37
  * @param fromSeconds - Start time in seconds
32
- * @param toSeconds - End time in seconds
33
- * @returns Words joined with spaces
38
+ * @param toSeconds - End time in seconds (use Infinity for open-ended)
39
+ * @returns Sentences with start/end times in seconds
34
40
  */
35
- export declare function extractFromTimestamps(timestamps: TimestampEntry[], fromSeconds: number, toSeconds: number): string;
41
+ export declare function extractFromTimestamps(timestamps: TimestampEntry[], fromSeconds: number, toSeconds: number): TranscriptSentence[];
36
42
  /**
37
43
  * Extract transcript content between two timestamps.
38
44
  *
@@ -47,4 +53,4 @@ export declare function extractFromTimestamps(timestamps: TimestampEntry[], from
47
53
  * @param timestamps - Optional word-level timestamps from .timestamps.json
48
54
  * @returns Extracted transcript with from/to as seconds, or error
49
55
  */
50
- export declare function extractVideoExcerpt(transcript: string, fromTime: string, toTime: string, file: string, timestamps?: TimestampEntry[]): VideoExcerptResult;
56
+ export declare function extractVideoExcerpt(transcript: string, fromTime: string, toTime: string | undefined, file: string, timestamps?: TimestampEntry[]): VideoExcerptResult;
@@ -58,25 +58,46 @@ function parseTranscriptLineTimestamp(line) {
58
58
  return parseTimestamp(match[1]);
59
59
  }
60
60
  /**
61
- * Extract words from timestamps data that fall within the requested time range.
62
- *
63
- * @param timestamps - Array of word-level timestamp entries
64
- * @param fromSeconds - Start time in seconds
65
- * @param toSeconds - End time in seconds
66
- * @returns Words joined with spaces
61
+ * Group word-level timestamp entries into sentences by detecting sentence-ending punctuation.
62
+ * The end of each sentence is the start of the next sentence (or toSeconds for the last).
67
63
  */
68
- export function extractFromTimestamps(timestamps, fromSeconds, toSeconds) {
69
- const wordsInRange = [];
70
- for (const entry of timestamps) {
71
- const entryTime = parseTimestamp(entry.start);
64
+ function groupIntoSentences(entries, toSeconds) {
65
+ const sentences = [];
66
+ const words = [];
67
+ let sentenceStart = null;
68
+ for (let i = 0; i < entries.length; i++) {
69
+ const entryTime = parseTimestamp(entries[i].start);
72
70
  if (entryTime === null)
73
71
  continue;
74
- // Include word if its start time falls within the range
75
- if (entryTime >= fromSeconds && entryTime <= toSeconds) {
76
- wordsInRange.push(entry.text);
72
+ if (sentenceStart === null)
73
+ sentenceStart = entryTime;
74
+ words.push(entries[i].text);
75
+ if (/[.?!…]$/.test(entries[i].text.trim())) {
76
+ sentences.push({ text: words.join(' '), start: sentenceStart, end: entryTime });
77
+ words.length = 0;
78
+ sentenceStart = null;
77
79
  }
78
80
  }
79
- return wordsInRange.join(' ');
81
+ if (words.length > 0 && sentenceStart !== null) {
82
+ sentences.push({ text: words.join(' '), start: sentenceStart, end: toSeconds });
83
+ }
84
+ return sentences;
85
+ }
86
+ /**
87
+ * Extract words from timestamps data that fall within the requested time range,
88
+ * grouped into sentences by punctuation.
89
+ *
90
+ * @param timestamps - Array of word-level timestamp entries
91
+ * @param fromSeconds - Start time in seconds
92
+ * @param toSeconds - End time in seconds (use Infinity for open-ended)
93
+ * @returns Sentences with start/end times in seconds
94
+ */
95
+ export function extractFromTimestamps(timestamps, fromSeconds, toSeconds) {
96
+ const inRange = timestamps.filter(entry => {
97
+ const t = parseTimestamp(entry.start);
98
+ return t !== null && t >= fromSeconds && t <= toSeconds;
99
+ });
100
+ return groupIntoSentences(inRange, toSeconds);
80
101
  }
81
102
  /**
82
103
  * Extract transcript content between two timestamps.
@@ -105,8 +126,9 @@ export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestam
105
126
  },
106
127
  };
107
128
  }
108
- const toSeconds = parseTimestamp(toTime);
109
- if (toSeconds === null) {
129
+ // toTime is optional — undefined means "full video from fromSeconds"
130
+ const toSeconds = toTime !== undefined ? parseTimestamp(toTime) : null;
131
+ if (toTime !== undefined && toSeconds === null) {
110
132
  return {
111
133
  error: {
112
134
  file,
@@ -116,8 +138,8 @@ export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestam
116
138
  },
117
139
  };
118
140
  }
119
- // Validate that from is not after to
120
- if (fromSeconds > toSeconds) {
141
+ // Validate that from is not after to (only when to is specified)
142
+ if (toSeconds !== null && fromSeconds > toSeconds) {
121
143
  return {
122
144
  error: {
123
145
  file,
@@ -129,7 +151,8 @@ export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestam
129
151
  }
130
152
  // If timestamps data is provided, use word-level extraction
131
153
  if (timestamps && timestamps.length > 0) {
132
- const extractedText = extractFromTimestamps(timestamps, fromSeconds, toSeconds);
154
+ const effectiveTo = toSeconds ?? Infinity;
155
+ const extractedText = extractFromTimestamps(timestamps, fromSeconds, effectiveTo);
133
156
  return {
134
157
  from: fromSeconds,
135
158
  to: toSeconds,
@@ -149,12 +172,12 @@ export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestam
149
172
  // Find lines that fall within the requested range
150
173
  // Handle plain prose transcripts (no timestamps at all)
151
174
  if (timestampedLines.length === 0) {
152
- // If starting from 0:00, include entire transcript
175
+ // If starting from 0:00, include entire transcript as a single sentence
153
176
  if (fromSeconds === 0) {
154
177
  return {
155
178
  from: fromSeconds,
156
179
  to: toSeconds,
157
- transcript: transcript.trim(),
180
+ transcript: [{ text: transcript.trim(), start: fromSeconds, end: toSeconds ?? Infinity }],
158
181
  };
159
182
  }
160
183
  // Can't extract from a non-zero timestamp in a plain prose transcript
@@ -167,22 +190,16 @@ export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestam
167
190
  },
168
191
  };
169
192
  }
170
- // A line is included if its timestamp >= fromSeconds and < toSeconds
193
+ // A line is included if its timestamp >= fromSeconds and < toSeconds (or all remaining if no to)
171
194
  // Special case: fromSeconds === 0 means "start of video" - no need to find exact 0:00 marker
195
+ const effectiveTo = toSeconds ?? Infinity;
172
196
  let foundFrom = fromSeconds === 0;
173
- let foundTo = false;
174
- const excerptLines = [];
175
- for (const { timestamp, line } of timestampedLines) {
176
- if (timestamp === fromSeconds) {
197
+ let foundTo = toSeconds === null; // no to:: means we want everything — no need to find marker
198
+ for (const { timestamp } of timestampedLines) {
199
+ if (timestamp === fromSeconds)
177
200
  foundFrom = true;
178
- }
179
- if (timestamp === toSeconds) {
201
+ if (toSeconds !== null && timestamp === toSeconds)
180
202
  foundTo = true;
181
- }
182
- // Include lines from fromSeconds up to (but not including) toSeconds
183
- if (timestamp >= fromSeconds && timestamp < toSeconds) {
184
- excerptLines.push(line);
185
- }
186
203
  }
187
204
  if (!foundFrom) {
188
205
  return {
@@ -204,10 +221,16 @@ export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestam
204
221
  },
205
222
  };
206
223
  }
224
+ const inRange = timestampedLines.filter(({ timestamp }) => timestamp >= fromSeconds && timestamp < effectiveTo);
225
+ const excerptSentences = inRange.map(({ timestamp, line }) => ({
226
+ text: line.replace(/^\d+:\d+(?::\d+)?\s*-\s*/, '').trim(),
227
+ start: timestamp,
228
+ end: timestamp,
229
+ }));
207
230
  return {
208
231
  from: fromSeconds,
209
232
  to: toSeconds,
210
- transcript: excerptLines.join('\n'),
233
+ transcript: excerptSentences,
211
234
  };
212
235
  }
213
236
  //# sourceMappingURL=video.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"video.js","sourceRoot":"","sources":["../../src/bundler/video.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"video.js","sourceRoot":"","sources":["../../src/bundler/video.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts CHANGED
@@ -1,9 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { type 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
6
  stdin: boolean;
7
7
  }
8
+ /**
9
+ * The CLI's JSON output: the core ProcessResult plus a course-reachability map
10
+ * for the validator page. fileToCourses is a CLI-boundary concern (not part of
11
+ * core processing / the Python-parity output), so it lives here, not on
12
+ * ProcessResult.
13
+ */
14
+ export type CliResult = ProcessResult & {
15
+ fileToCourses: Record<string, string[]>;
16
+ };
8
17
  export declare function parseArgs(argv: string[]): CliOptions;
9
- export declare function run(options: CliOptions): Promise<ProcessResult>;
18
+ export declare function run(options: CliOptions): Promise<CliResult>;
package/dist/cli.js CHANGED
@@ -1,23 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  // src/cli.ts
3
- import { readVaultFiles } from './fs/read-vault.js';
4
- import { processContent } from './index.js';
5
- import { validateUrls } from './validator/url-reachability.js';
6
- import { writeFile } from 'fs/promises';
3
+ import { readVaultFiles } from "./fs/read-vault.js";
4
+ import { processContent } from "./index.js";
5
+ import { validateUrls } from "./validator/url-reachability.js";
6
+ import { buildFileToCourses } from "./reference-graph.js";
7
+ import { dedupeErrors } from "./dedupe-errors.js";
8
+ import { writeFile } from "fs/promises";
7
9
  export function parseArgs(argv) {
8
10
  const args = argv.slice(2);
9
11
  let vaultPath = null;
10
12
  let outputPath = null;
11
13
  let stdin = false;
12
14
  for (let i = 0; i < args.length; i++) {
13
- if (args[i] === '--output' || args[i] === '-o') {
15
+ if (args[i] === "--output" || args[i] === "-o") {
14
16
  outputPath = args[i + 1] || null;
15
17
  i++; // skip next arg
16
18
  }
17
- else if (args[i] === '--stdin') {
19
+ else if (args[i] === "--stdin") {
18
20
  stdin = true;
19
21
  }
20
- else if (!args[i].startsWith('-')) {
22
+ else if (!args[i].startsWith("-")) {
21
23
  vaultPath = args[i];
22
24
  }
23
25
  }
@@ -28,7 +30,7 @@ async function readStdin() {
28
30
  for await (const chunk of process.stdin) {
29
31
  chunks.push(chunk);
30
32
  }
31
- return Buffer.concat(chunks).toString('utf-8');
33
+ return Buffer.concat(chunks).toString("utf-8");
32
34
  }
33
35
  export async function run(options) {
34
36
  let files;
@@ -40,17 +42,30 @@ export async function run(options) {
40
42
  }
41
43
  else {
42
44
  if (!options.vaultPath) {
43
- throw new Error('Vault path is required (or use --stdin)');
45
+ throw new Error("Vault path is required (or use --stdin)");
44
46
  }
45
47
  files = await readVaultFiles(options.vaultPath);
46
48
  }
47
- return processContent(files);
49
+ const result = processContent(files);
50
+ // Course-reachability map for the validator page. Computed here, not in
51
+ // processContent, so it stays out of the Python-parity / fixture output.
52
+ // Degradable (per AGENTS.md): this feeds only the validator page's course
53
+ // filter, so a failure here must never break content processing for the
54
+ // whole platform — fall back to an empty map and carry on.
55
+ let fileToCourses = {};
56
+ try {
57
+ fileToCourses = buildFileToCourses(files);
58
+ }
59
+ catch (error) {
60
+ console.error("buildFileToCourses failed; serving validation without the course filter:", error instanceof Error ? error.message : error);
61
+ }
62
+ return { ...result, fileToCourses };
48
63
  }
49
64
  async function main() {
50
65
  const options = parseArgs(process.argv);
51
66
  if (!options.vaultPath && !options.stdin) {
52
- console.error('Usage: npx tsx src/cli.ts <vault-path> [--output <file>]');
53
- console.error(' npx tsx src/cli.ts --stdin [--output <file>]');
67
+ console.error("Usage: npx tsx src/cli.ts <vault-path> [--output <file>]");
68
+ console.error(" npx tsx src/cli.ts --stdin [--output <file>]");
54
69
  process.exit(1);
55
70
  }
56
71
  try {
@@ -58,11 +73,13 @@ async function main() {
58
73
  // Async URL reachability validation
59
74
  if (result.urlsToValidate.length > 0) {
60
75
  const urlWarnings = await validateUrls(result.urlsToValidate);
61
- result.errors.push(...urlWarnings);
76
+ // Re-dedupe: urlWarnings are appended after processContent already
77
+ // deduped, and can themselves repeat (same unreachable URL twice).
78
+ result.errors = dedupeErrors([...result.errors, ...urlWarnings]);
62
79
  }
63
80
  const json = JSON.stringify(result, null, 2);
64
81
  if (options.outputPath) {
65
- await writeFile(options.outputPath, json, 'utf-8');
82
+ await writeFile(options.outputPath, json, "utf-8");
66
83
  console.error(`Written to ${options.outputPath}`);
67
84
  }
68
85
  else {
@@ -70,16 +87,16 @@ async function main() {
70
87
  }
71
88
  }
72
89
  catch (error) {
73
- console.error('Error:', error instanceof Error ? error.message : error);
90
+ console.error("Error:", error instanceof Error ? error.message : error);
74
91
  process.exit(1);
75
92
  }
76
93
  }
77
94
  // Run if executed directly (not imported as a module)
78
95
  // Check for cli.ts, cli.js, or lens-content-processor (when run as npx binary)
79
- const scriptName = process.argv[1] || '';
80
- const isMainModule = scriptName.includes('cli.ts') ||
81
- scriptName.includes('cli.js') ||
82
- scriptName.endsWith('lens-content-processor');
96
+ const scriptName = process.argv[1] || "";
97
+ const isMainModule = scriptName.includes("cli.ts") ||
98
+ scriptName.includes("cli.js") ||
99
+ scriptName.endsWith("lens-content-processor");
83
100
  if (isMainModule) {
84
101
  main();
85
102
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1,36 +1,89 @@
1
1
  // content-schema.ts — Single source of truth for content type and segment type field definitions.
2
2
  function contentSchema(required, optional) {
3
- return { requiredFields: required, optionalFields: optional, allFields: [...required, ...optional] };
3
+ return {
4
+ requiredFields: required,
5
+ optionalFields: optional,
6
+ allFields: [...required, ...optional],
7
+ };
4
8
  }
5
9
  function segmentSchema(required, optional, booleanFields) {
6
- return { requiredFields: required, optionalFields: optional, allFields: [...required, ...optional], booleanFields };
10
+ return {
11
+ requiredFields: required,
12
+ optionalFields: optional,
13
+ allFields: [...required, ...optional],
14
+ booleanFields,
15
+ };
7
16
  }
8
17
  export const CONTENT_SCHEMAS = {
9
- 'module': contentSchema(['slug', 'title'], ['contentId', 'id', 'discussion', 'tags']),
10
- 'course': contentSchema(['slug', 'title'], ['id', 'tags']),
11
- 'lens': contentSchema(['id'], ['tldr', 'tags']),
12
- 'learning-outcome': contentSchema(['id'], ['discussion', 'learning-outcome', 'tags']),
13
- 'article': contentSchema(['title', 'author', 'source_url', 'published'], ['created', 'description', 'tags', 'url']),
14
- 'video-transcript': contentSchema(['title', 'channel', 'url'], ['tags']),
18
+ module: contentSchema(["slug", "title"], ["contentId", "id", "discussion", "tags"]),
19
+ course: contentSchema(["slug", "title"], [
20
+ "id",
21
+ "slug-aliases",
22
+ "tags",
23
+ "partner-name",
24
+ "partner-logo",
25
+ "partner-logo-small",
26
+ "partner-url",
27
+ ]),
28
+ lens: contentSchema(["id"], ["title", "tldr", "summary_for_tutor", "tags"]),
29
+ "learning-outcome": contentSchema(["id"], ["discussion", "learning-outcome", "tags"]),
30
+ article: contentSchema(["title", "author", "source_url", "published"], [
31
+ "created",
32
+ "description",
33
+ "tags",
34
+ "url",
35
+ "allowAuthorInTitle",
36
+ "allowUnreachableUrl",
37
+ ]),
38
+ "video-transcript": contentSchema(["title", "channel", "url"], ["tags", "allowUnreachableUrl"]),
15
39
  };
16
40
  export const SEGMENT_SCHEMAS = {
17
- 'text': segmentSchema(['content'], ['optional'], ['optional']),
18
- 'chat': segmentSchema(['instructions'], ['optional', 'hidePreviousContentFromUser', 'hidePreviousContentFromTutor'], ['optional', 'hidePreviousContentFromUser', 'hidePreviousContentFromTutor']),
19
- 'article-excerpt': segmentSchema([], ['from', 'to', 'optional'], ['optional']),
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']),
22
- 'roleplay': segmentSchema(['id', 'content', 'ai-instructions'], ['opening-message', 'assessment-instructions', 'optional', 'feedback'], ['optional', 'feedback']),
41
+ text: segmentSchema(["content"], ["optional"], ["optional"]),
42
+ // Note: chat and question take min-messages instead of optional
43
+ // min-messages:: 0 is the "not required" spelling at segment level
44
+ // (optional:: true remains a lens-level concept)
45
+ chat: segmentSchema(["instructions"], [
46
+ "min-messages",
47
+ "hidePreviousContentFromUser",
48
+ "hidePreviousContentFromTutor",
49
+ ], ["hidePreviousContentFromUser", "hidePreviousContentFromTutor"]),
50
+ article: segmentSchema([], ["source", "from", "to", "optional"], ["optional"]),
51
+ video: segmentSchema([], ["source", "from", "to", "optional"], ["optional"]),
52
+ question: segmentSchema(["content"], [
53
+ "assessment-instructions",
54
+ "max-time",
55
+ "max-chars",
56
+ "min-messages",
57
+ "enforce-voice",
58
+ "feedback",
59
+ ], ["enforce-voice", "feedback"]),
60
+ roleplay: segmentSchema(["id", "content", "ai-instructions"], [
61
+ "opening-message",
62
+ "assessment-instructions",
63
+ "user-customizable",
64
+ "optional",
65
+ "feedback",
66
+ ], ["user-customizable", "optional", "feedback"]),
23
67
  };
24
68
  /**
25
69
  * Valid fields per segment type, derived from SEGMENT_SCHEMAS.
26
70
  * Used by segment-fields.ts to check for misplaced fields.
27
71
  */
28
- export const VALID_FIELDS_BY_SEGMENT_TYPE = Object.fromEntries(Object.entries(SEGMENT_SCHEMAS).map(([type, schema]) => [type, new Set(schema.allFields)]));
72
+ export const VALID_FIELDS_BY_SEGMENT_TYPE = Object.fromEntries(Object.entries(SEGMENT_SCHEMAS).map(([type, schema]) => [
73
+ type,
74
+ new Set(schema.allFields),
75
+ ]));
29
76
  /**
30
77
  * Section-level fields used in body sections (not frontmatter, not segments).
31
78
  * These are fields like source:: and learningOutcomeId:: used in LO/lens sections.
32
79
  */
33
- const SECTION_LEVEL_FIELDS = ['source', 'learningOutcomeId', 'sourceUrl'];
80
+ const SECTION_LEVEL_FIELDS = [
81
+ "source",
82
+ "learningOutcomeId",
83
+ "sourceUrl",
84
+ "hide",
85
+ ];
86
+ const SECTION_LEVEL_BOOLEAN_FIELDS = ["hide"];
34
87
  /**
35
88
  * All known field names across all content types, derived from schemas.
36
89
  * Used by typo detection to suggest corrections for unrecognized fields.
@@ -58,6 +111,8 @@ export const ALL_BOOLEAN_FIELDS = (() => {
58
111
  for (const field of schema.booleanFields)
59
112
  fields.add(field);
60
113
  }
114
+ for (const field of SECTION_LEVEL_BOOLEAN_FIELDS)
115
+ fields.add(field);
61
116
  return [...fields];
62
117
  })();
63
118
  //# sourceMappingURL=content-schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"content-schema.js","sourceRoot":"","sources":["../src/content-schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"content-schema.js","sourceRoot":"","sources":["../src/content-schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { ContentError } from "./index.js";
2
+ /**
3
+ * Collapse duplicate errors that point at the same problem.
4
+ *
5
+ * A single content file is validated more than once per run: a learning
6
+ * outcome is parsed standalone AND again every time a module that references
7
+ * it is flattened (the flattener re-parses referenced LOs). Each pass runs the
8
+ * same parse code on the same content, so an identical error is emitted once
9
+ * per pass — e.g. one mistake in an LO referenced by one module surfaces twice.
10
+ *
11
+ * Authors should see each problem once. Dedupe on (file, line, severity,
12
+ * message), keeping the first occurrence so report order is stable.
13
+ */
14
+ export declare function dedupeErrors(errors: ContentError[]): ContentError[];
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Collapse duplicate errors that point at the same problem.
3
+ *
4
+ * A single content file is validated more than once per run: a learning
5
+ * outcome is parsed standalone AND again every time a module that references
6
+ * it is flattened (the flattener re-parses referenced LOs). Each pass runs the
7
+ * same parse code on the same content, so an identical error is emitted once
8
+ * per pass — e.g. one mistake in an LO referenced by one module surfaces twice.
9
+ *
10
+ * Authors should see each problem once. Dedupe on (file, line, severity,
11
+ * message), keeping the first occurrence so report order is stable.
12
+ */
13
+ export function dedupeErrors(errors) {
14
+ const seen = new Set();
15
+ const deduped = [];
16
+ for (const error of errors) {
17
+ // \x1f (unit separator) joins the fields: file paths and messages can
18
+ // themselves contain spaces, so a printable separator could let two
19
+ // distinct errors collapse to the same key.
20
+ const key = [
21
+ error.file,
22
+ error.line ?? "",
23
+ error.severity,
24
+ error.message,
25
+ ].join("\x1f");
26
+ if (seen.has(key))
27
+ continue;
28
+ seen.add(key);
29
+ deduped.push(error);
30
+ }
31
+ return deduped;
32
+ }
33
+ //# sourceMappingURL=dedupe-errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe-errors.js","sourceRoot":"","sources":["../src/dedupe-errors.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,MAAsB;IACjD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,OAAO,GAAmB,EAAE,CAAC;IAEnC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,sEAAsE;QACtE,oEAAoE;QACpE,4CAA4C;QAC5C,MAAM,GAAG,GAAG;YACV,KAAK,CAAC,IAAI;YACV,KAAK,CAAC,IAAI,IAAI,EAAE;YAChB,KAAK,CAAC,QAAQ;YACd,KAAK,CAAC,OAAO;SACd,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACf,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -1,6 +1,6 @@
1
- import type { FlattenedModule, Section, ContentError } from '../index.js';
2
- import type { ContentTier } from '../validator/tier.js';
3
- import { type ParsedLens } from '../parser/lens.js';
1
+ import type { FlattenedModule, Section, ContentError } from "../index.js";
2
+ import type { ContentTier } from "../validator/tier.js";
3
+ import { type ParsedLens } from "../parser/lens.js";
4
4
  export interface FlattenModuleResult {
5
5
  module: FlattenedModule | null;
6
6
  modules: FlattenedModule[];
@@ -25,35 +25,10 @@ interface VirtualModuleGroup {
25
25
  export declare function splitAtBoundaries(items: FlatItem[], parentSlug: string, parentTitle: string): VirtualModuleGroup[];
26
26
  /**
27
27
  * Flatten a module by resolving all references to Learning Outcomes, Lenses, and content.
28
- *
29
- * This function:
30
- * 1. Parses the module file
31
- * 2. For each Learning Outcome section, resolves the LO file
32
- * 3. For each Lens in the LO, resolves the lens file
33
- * 4. For each segment, extracts content from articles/videos as needed
34
- *
35
- * @param modulePath - Path to the module file within the files Map
36
- * @param files - Map of all file paths to their content
37
- * @param visitedPaths - Optional set of already-visited paths for cycle detection
38
- * @returns Flattened module with resolved sections and segments, plus any errors
39
28
  */
40
29
  export declare function flattenModule(modulePath: string, files: Map<string, string>, visitedPaths?: Set<string>, tierMap?: Map<string, ContentTier>): FlattenModuleResult;
41
30
  /**
42
31
  * Flatten a single Lens file into a FlattenedModule.
43
- *
44
- * This wraps a standalone Lens as a single-section module so it can be
45
- * rendered by the frontend using the existing Module.tsx component.
46
- *
47
- * The resulting module has:
48
- * - slug: 'lens/' + fileNameToSlug(lensPath)
49
- * - title: extracted from source metadata or lens header
50
- * - sections: exactly one section (lens-article, lens-video, or page)
51
- *
52
- * @param lensPath - Path to the lens file within the files Map
53
- * @param files - Map of all file paths to their content
54
- * @param tierMap - Optional tier map for filtering ignored content
55
- * @param preParsedLens - Optional pre-parsed lens to avoid re-parsing
56
- * @returns Flattened module with a single section, plus any errors
57
32
  */
58
33
  export declare function flattenLens(lensPath: string, files: Map<string, string>, tierMap?: Map<string, ContentTier>, preParsedLens?: ParsedLens): FlattenModuleResult;
59
34
  export {};