lens-content-processor 0.2.1 → 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.
Files changed (75) hide show
  1. package/dist/bundler/video.js +4 -4
  2. package/dist/bundler/video.js.map +1 -1
  3. package/dist/cli.d.ts +1 -2
  4. package/dist/cli.js +11 -9
  5. package/dist/cli.js.map +1 -1
  6. package/dist/content-schema.d.ts +34 -0
  7. package/dist/content-schema.js +62 -0
  8. package/dist/content-schema.js.map +1 -0
  9. package/dist/flattener/index.d.ts +21 -1
  10. package/dist/flattener/index.js +316 -33
  11. package/dist/flattener/index.js.map +1 -1
  12. package/dist/fs/read-vault.d.ts +1 -4
  13. package/dist/fs/read-vault.js +4 -8
  14. package/dist/fs/read-vault.js.map +1 -1
  15. package/dist/index.d.ts +25 -2
  16. package/dist/index.js +231 -6
  17. package/dist/index.js.map +1 -1
  18. package/dist/parser/article.d.ts +16 -0
  19. package/dist/parser/article.js +70 -0
  20. package/dist/parser/article.js.map +1 -0
  21. package/dist/parser/course.d.ts +2 -2
  22. package/dist/parser/course.js +18 -35
  23. package/dist/parser/course.js.map +1 -1
  24. package/dist/parser/frontmatter.js +2 -0
  25. package/dist/parser/frontmatter.js.map +1 -1
  26. package/dist/parser/learning-outcome.d.ts +4 -2
  27. package/dist/parser/learning-outcome.js +65 -23
  28. package/dist/parser/learning-outcome.js.map +1 -1
  29. package/dist/parser/lens.d.ts +38 -1
  30. package/dist/parser/lens.js +173 -18
  31. package/dist/parser/lens.js.map +1 -1
  32. package/dist/parser/module.d.ts +11 -5
  33. package/dist/parser/module.js +172 -76
  34. package/dist/parser/module.js.map +1 -1
  35. package/dist/parser/sections.js +116 -13
  36. package/dist/parser/sections.js.map +1 -1
  37. package/dist/parser/video-transcript.d.ts +11 -0
  38. package/dist/parser/video-transcript.js +39 -0
  39. package/dist/parser/video-transcript.js.map +1 -0
  40. package/dist/parser/wikilink.d.ts +2 -0
  41. package/dist/parser/wikilink.js +88 -24
  42. package/dist/parser/wikilink.js.map +1 -1
  43. package/dist/utils/slug.d.ts +5 -0
  44. package/dist/utils/slug.js +16 -0
  45. package/dist/utils/slug.js.map +1 -0
  46. package/dist/validator/chat-precedence.d.ts +9 -0
  47. package/dist/validator/chat-precedence.js +32 -0
  48. package/dist/validator/chat-precedence.js.map +1 -0
  49. package/dist/validator/duplicates.d.ts +13 -0
  50. package/dist/validator/duplicates.js +27 -0
  51. package/dist/validator/duplicates.js.map +1 -0
  52. package/dist/validator/field-typos.d.ts +11 -0
  53. package/dist/validator/field-typos.js +36 -28
  54. package/dist/validator/field-typos.js.map +1 -1
  55. package/dist/validator/field-values.d.ts +14 -0
  56. package/dist/validator/field-values.js +61 -7
  57. package/dist/validator/field-values.js.map +1 -1
  58. package/dist/validator/output-integrity.d.ts +7 -0
  59. package/dist/validator/output-integrity.js +57 -0
  60. package/dist/validator/output-integrity.js.map +1 -0
  61. package/dist/validator/segment-fields.js +1 -15
  62. package/dist/validator/segment-fields.js.map +1 -1
  63. package/dist/validator/tier.d.ts +18 -0
  64. package/dist/validator/tier.js +74 -0
  65. package/dist/validator/tier.js.map +1 -0
  66. package/dist/validator/timestamps.d.ts +2 -0
  67. package/dist/validator/timestamps.js +91 -0
  68. package/dist/validator/timestamps.js.map +1 -0
  69. package/dist/validator/url-reachability.d.ts +8 -0
  70. package/dist/validator/url-reachability.js +60 -0
  71. package/dist/validator/url-reachability.js.map +1 -0
  72. package/dist/validator/validate-frontmatter.d.ts +9 -0
  73. package/dist/validator/validate-frontmatter.js +53 -0
  74. package/dist/validator/validate-frontmatter.js.map +1 -0
  75. package/package.json +5 -5
@@ -1,77 +1,173 @@
1
1
  import { parseFrontmatter } from './frontmatter.js';
2
2
  import { parseSections, MODULE_SECTION_TYPES } from './sections.js';
3
+ import { validateSlugFormat } from '../validator/field-values.js';
4
+ import { validateFrontmatter } from '../validator/validate-frontmatter.js';
5
+ const VALID_PAGE_SUBSECTION_TYPES = new Set(['text', 'chat']);
3
6
  /**
4
- * Parse ## Text subsections from within a Page section body.
5
- * These subsections have a `content::` field that can span multiple lines.
6
- *
7
- * @param body - The body text of a # Page: section
8
- * @returns Array of TextSegment objects extracted from ## Text subsections
7
+ * Collect raw subsections from a Page section body.
8
+ * Each ## header starts a new subsection whose fields are collected generically.
9
9
  */
10
- export function parsePageTextSegments(body) {
11
- const segments = [];
10
+ function collectRawSubsections(body, baseLineNum) {
11
+ const subsections = [];
12
+ const unknownHeaders = [];
13
+ const warnings = [];
12
14
  const lines = body.split('\n');
13
- let inTextSection = false;
14
- let currentContent = '';
15
- let collectingContent = false;
16
- for (const line of lines) {
17
- // Check for ## Text header (case-insensitive)
18
- if (line.match(/^##\s+Text\s*$/i)) {
19
- // Save previous content if any
20
- if (collectingContent && currentContent.trim()) {
21
- segments.push({ type: 'text', content: currentContent.trim() });
15
+ let current = null;
16
+ let freeTextWarned = false;
17
+ let currentFieldName = null;
18
+ let currentFieldLines = [];
19
+ function finalizeField() {
20
+ if (current && currentFieldName) {
21
+ current.fields[currentFieldName] = currentFieldLines.join('\n').trim();
22
+ }
23
+ currentFieldName = null;
24
+ currentFieldLines = [];
25
+ }
26
+ function finalizeSubsection() {
27
+ finalizeField();
28
+ if (current) {
29
+ subsections.push(current);
30
+ }
31
+ current = null;
32
+ }
33
+ for (let i = 0; i < lines.length; i++) {
34
+ const line = lines[i];
35
+ const lineNum = baseLineNum + i + 1;
36
+ // Check for ## header
37
+ const headerMatch = line.match(/^##\s+(\S.*?)\s*$/);
38
+ if (headerMatch) {
39
+ finalizeSubsection();
40
+ freeTextWarned = false;
41
+ const rawType = headerMatch[1].trim();
42
+ const normalizedType = rawType.toLowerCase();
43
+ if (VALID_PAGE_SUBSECTION_TYPES.has(normalizedType)) {
44
+ current = { type: normalizedType, fields: {}, line: lineNum };
45
+ }
46
+ else {
47
+ unknownHeaders.push({ rawType, line: lineNum });
22
48
  }
23
- inTextSection = true;
24
- currentContent = '';
25
- collectingContent = false;
26
49
  continue;
27
50
  }
28
- // Check for any other ## header (end of Text section)
29
- if (line.match(/^##\s+\S/)) {
30
- // Save current content if any
31
- if (collectingContent && currentContent.trim()) {
32
- segments.push({ type: 'text', content: currentContent.trim() });
33
- }
34
- inTextSection = false;
35
- currentContent = '';
36
- collectingContent = false;
51
+ if (!current)
37
52
  continue;
53
+ // Check for field:: value
54
+ const fieldMatch = line.match(/^(\w+)::\s*(.*)$/);
55
+ if (fieldMatch) {
56
+ finalizeField();
57
+ currentFieldName = fieldMatch[1];
58
+ const inlineValue = fieldMatch[2].trim();
59
+ currentFieldLines = inlineValue ? [inlineValue] : [];
60
+ }
61
+ else if (currentFieldName) {
62
+ // Continue multiline field value
63
+ currentFieldLines.push(line);
64
+ }
65
+ else if (line.trim() && !freeTextWarned) {
66
+ freeTextWarned = true;
67
+ const preview = line.trim().length > 60 ? line.trim().slice(0, 60) + '...' : line.trim();
68
+ warnings.push({
69
+ file: '',
70
+ line: lineNum,
71
+ message: `Text outside of a field:: definition will be ignored: "${preview}"`,
72
+ suggestion: 'Place this text inside a field (e.g., content:: your text), or remove it',
73
+ severity: 'warning',
74
+ });
38
75
  }
39
- if (inTextSection) {
40
- // Check for content:: field
41
- const contentMatch = line.match(/^content::\s*(.*)$/);
42
- if (contentMatch) {
43
- // Start collecting content
44
- collectingContent = true;
45
- const inlineValue = contentMatch[1].trim();
46
- currentContent = inlineValue;
76
+ }
77
+ finalizeSubsection();
78
+ return { subsections, unknownHeaders, warnings };
79
+ }
80
+ /**
81
+ * Convert raw subsections into typed segments, validating required fields.
82
+ */
83
+ function convertSubsections(subsections, file) {
84
+ const segments = [];
85
+ const errors = [];
86
+ for (const sub of subsections) {
87
+ switch (sub.type) {
88
+ case 'text': {
89
+ const hasContentField = 'content' in sub.fields;
90
+ const content = sub.fields.content;
91
+ if (!hasContentField) {
92
+ errors.push({
93
+ file,
94
+ line: sub.line,
95
+ message: 'Text section missing content:: field',
96
+ suggestion: "Add 'content::' followed by your text content",
97
+ severity: 'error',
98
+ });
99
+ break;
100
+ }
101
+ if (content.trim()) {
102
+ segments.push({ type: 'text', content: content.trim() });
103
+ }
104
+ break;
47
105
  }
48
- else if (collectingContent) {
49
- // Check if this line starts another field (ends content collection)
50
- if (line.match(/^\w+::\s*/)) {
51
- // Save current content and stop collecting
52
- if (currentContent.trim()) {
53
- segments.push({ type: 'text', content: currentContent.trim() });
54
- }
55
- collectingContent = false;
56
- currentContent = '';
106
+ case 'chat': {
107
+ const hasInstructionsField = 'instructions' in sub.fields;
108
+ const instructions = sub.fields.instructions;
109
+ if (!hasInstructionsField) {
110
+ errors.push({
111
+ file,
112
+ line: sub.line,
113
+ message: 'Chat segment missing instructions:: field',
114
+ suggestion: "Add 'instructions:: Your instructions here' to the chat segment",
115
+ severity: 'error',
116
+ });
117
+ break;
57
118
  }
58
- else {
59
- // Continue multiline content
60
- if (currentContent) {
61
- currentContent += '\n' + line;
62
- }
63
- else {
64
- currentContent = line;
65
- }
119
+ if (!instructions || instructions.trim() === '') {
120
+ errors.push({
121
+ file,
122
+ line: sub.line,
123
+ message: 'Chat segment has empty instructions:: field',
124
+ suggestion: 'Add instructions text after instructions::',
125
+ severity: 'warning',
126
+ });
66
127
  }
128
+ const segment = {
129
+ type: 'chat',
130
+ instructions: instructions || '',
131
+ hidePreviousContentFromUser: sub.fields.hidePreviousContentFromUser?.toLowerCase() === 'true' ? true : undefined,
132
+ hidePreviousContentFromTutor: sub.fields.hidePreviousContentFromTutor?.toLowerCase() === 'true' ? true : undefined,
133
+ };
134
+ segments.push(segment);
135
+ break;
67
136
  }
68
137
  }
69
138
  }
70
- // Don't forget the last segment
71
- if (collectingContent && currentContent.trim()) {
72
- segments.push({ type: 'text', content: currentContent.trim() });
139
+ return { segments, errors };
140
+ }
141
+ /**
142
+ * Parse ## Text and ## Chat subsections from within a Page section body.
143
+ * Reports errors for unknown ## headers and missing required fields.
144
+ *
145
+ * @param body - The body text of a # Page: section
146
+ * @param file - File path for error reporting
147
+ * @param baseLineNum - Line number offset (body's position within the file)
148
+ * @returns Segment objects (TextSegment | ChatSegment) and any errors
149
+ */
150
+ export function parsePageSegments(body, file = '', baseLineNum = 0) {
151
+ const errors = [];
152
+ const { subsections, unknownHeaders, warnings } = collectRawSubsections(body, baseLineNum);
153
+ // Forward free-text warnings with file path
154
+ for (const w of warnings) {
155
+ errors.push({ ...w, file });
156
+ }
157
+ // Report unknown headers
158
+ for (const unk of unknownHeaders) {
159
+ const capitalized = [...VALID_PAGE_SUBSECTION_TYPES].map(t => t[0].toUpperCase() + t.slice(1));
160
+ errors.push({
161
+ file,
162
+ line: unk.line,
163
+ message: `Unknown section type: ${unk.rawType}`,
164
+ suggestion: `Valid types: ${capitalized.join(', ')}`,
165
+ severity: 'error',
166
+ });
73
167
  }
74
- return segments;
168
+ const result = convertSubsections(subsections, file);
169
+ errors.push(...result.errors);
170
+ return { segments: result.segments, errors };
75
171
  }
76
172
  export function parseModule(content, file) {
77
173
  const errors = [];
@@ -82,26 +178,17 @@ export function parseModule(content, file) {
82
178
  return { module: null, errors };
83
179
  }
84
180
  const { frontmatter, body, bodyStartLine } = frontmatterResult;
85
- // Validate required frontmatter fields
86
- if (!frontmatter.slug) {
87
- errors.push({
88
- file,
89
- line: 2,
90
- message: 'Missing required field: slug',
91
- suggestion: "Add 'slug: your-module-slug' to frontmatter",
92
- severity: 'error',
93
- });
94
- }
95
- if (!frontmatter.title) {
96
- errors.push({
97
- file,
98
- line: 2,
99
- message: 'Missing required field: title',
100
- suggestion: "Add 'title: Your Module Title' to frontmatter",
101
- severity: 'error',
102
- });
181
+ const frontmatterErrors = validateFrontmatter(frontmatter, 'module', file);
182
+ errors.push(...frontmatterErrors);
183
+ // Module-specific: validate slug format (only if slug is present and non-empty)
184
+ const slug = frontmatter.slug;
185
+ if (typeof slug === 'string' && slug.trim() !== '') {
186
+ const slugFormatError = validateSlugFormat(slug, file, 2);
187
+ if (slugFormatError) {
188
+ errors.push(slugFormatError);
189
+ }
103
190
  }
104
- if (errors.length > 0) {
191
+ if (errors.some(e => e.severity === 'error')) {
105
192
  return { module: null, errors };
106
193
  }
107
194
  // Parse sections (H1 headers for module files)
@@ -116,6 +203,15 @@ export function parseModule(content, file) {
116
203
  for (const section of sectionsResult.sections) {
117
204
  section.line += bodyStartLine - 1;
118
205
  }
206
+ if (sectionsResult.sections.length === 0) {
207
+ errors.push({
208
+ file,
209
+ line: bodyStartLine,
210
+ message: 'Module has no sections',
211
+ suggestion: "Add sections like '# Page:', '# Learning Outcome:', or '# Uncategorized:'",
212
+ severity: 'warning',
213
+ });
214
+ }
119
215
  const module = {
120
216
  slug: frontmatter.slug,
121
217
  title: frontmatter.title,
@@ -1 +1 @@
1
- {"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/parser/module.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/parser/module.ts"],"names":[],"mappings":"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"}
@@ -1,29 +1,50 @@
1
+ import { ALL_KNOWN_FIELDS } from '../content-schema.js';
2
+ import { levenshtein } from '../validator/field-typos.js';
1
3
  // Valid section types per file type (exported for use by other parsers)
2
4
  export const MODULE_SECTION_TYPES = new Set(['learning outcome', 'page', 'uncategorized']);
3
5
  export const LO_SECTION_TYPES = new Set(['lens', 'test']);
4
- // Lens sections: input headers are `### Article:`, `### Video:`, `### Text:`
5
- // Output types are `lens-article`, `lens-video`, `text` (v2 format)
6
- export const LENS_SECTION_TYPES = new Set(['text', 'article', 'video']);
6
+ // Lens sections: input headers are `### Article:`, `### Video:`, `### Page:`
7
+ // Output types are `lens-article`, `lens-video`, `page` (v2 format)
8
+ export const LENS_SECTION_TYPES = new Set(['page', 'article', 'video']);
9
+ // All known structural header types (sections + segments) for markdown heading detection
10
+ const ALL_STRUCTURAL_TYPES = new Set([
11
+ // Section types
12
+ 'learning outcome', 'page', 'uncategorized', 'lens', 'test', 'module', 'meeting', 'article', 'video',
13
+ // Segment types
14
+ 'text', 'chat', 'article-excerpt', 'video-excerpt', 'question',
15
+ ]);
16
+ // Fields that commonly contain markdown with headings
17
+ const MARKDOWN_CONTENT_FIELDS = new Set(['content', 'instructions']);
7
18
  // Map input section names to output types for Lens files
8
19
  export const LENS_OUTPUT_TYPE = {
9
- 'text': 'text',
20
+ 'page': 'page',
10
21
  'article': 'lens-article',
11
22
  'video': 'lens-video',
12
23
  };
13
24
  // Header pattern is parameterized by level (1-4)
14
25
  function makeSectionPattern(level) {
15
26
  const hashes = '#'.repeat(level);
16
- // Match: ^#{level} <type>: <title>$
17
- // Captures: group 1 = type, group 2 = title
18
- return new RegExp(`^${hashes}\\s+([^:]+):\\s*(.*)$`, 'i');
27
+ // Match: ^#{level} <type> OR ^#{level} <type>: <optional title>
28
+ // Captures: group 1 = type, group 2 = title (may be undefined)
29
+ return new RegExp(`^${hashes}\\s+([^:]+?)(?::\\s*(.*))?$`, 'i');
19
30
  }
31
+ // Note: unrecognized headers are now caught by makeSectionPattern matching all
32
+ // ### headers, with unknown types reported as "Unknown section type" errors.
20
33
  export function parseSections(content, headerLevel, validTypes, file = '') {
21
34
  const SECTION_HEADER_PATTERN = makeSectionPattern(headerLevel);
35
+ // Build patterns for adjacent levels to detect wrong heading level
36
+ const wrongLevelPatterns = [];
37
+ for (const adjLevel of [headerLevel - 1, headerLevel + 1]) {
38
+ if (adjLevel >= 1 && adjLevel <= 4) {
39
+ wrongLevelPatterns.push({ pattern: makeSectionPattern(adjLevel), level: adjLevel });
40
+ }
41
+ }
22
42
  const lines = content.split('\n');
23
43
  const sections = [];
24
44
  const errors = [];
25
45
  let currentSection = null;
26
46
  let currentBody = [];
47
+ let preHeaderWarned = false;
27
48
  for (let i = 0; i < lines.length; i++) {
28
49
  const line = lines[i];
29
50
  const lineNum = i + 1;
@@ -38,13 +59,14 @@ export function parseSections(content, headerLevel, validTypes, file = '') {
38
59
  }
39
60
  const rawType = headerMatch[1].trim();
40
61
  const normalizedType = rawType.toLowerCase();
41
- const title = headerMatch[2].trim();
62
+ const title = (headerMatch[2] ?? '').trim();
42
63
  if (!validTypes.has(normalizedType)) {
64
+ const capitalized = [...validTypes].map(t => t.split(' ').map(w => w[0].toUpperCase() + w.slice(1)).join(' '));
43
65
  errors.push({
44
66
  file,
45
67
  line: lineNum,
46
68
  message: `Unknown section type: ${rawType}`,
47
- suggestion: `Valid types: ${[...validTypes].join(', ')}`,
69
+ suggestion: `Valid types: ${capitalized.join(', ')}`,
48
70
  severity: 'error',
49
71
  });
50
72
  }
@@ -58,8 +80,42 @@ export function parseSections(content, headerLevel, validTypes, file = '') {
58
80
  };
59
81
  currentBody = [];
60
82
  }
61
- else if (currentSection) {
62
- currentBody.push(line);
83
+ else {
84
+ if (currentSection) {
85
+ currentBody.push(line);
86
+ }
87
+ else if (!currentSection) {
88
+ // Check for headers at wrong level that match known section types
89
+ for (const { pattern, level } of wrongLevelPatterns) {
90
+ const wrongMatch = line.match(pattern);
91
+ if (wrongMatch) {
92
+ const rawType = wrongMatch[1].trim();
93
+ if (validTypes.has(rawType.toLowerCase())) {
94
+ const expected = '#'.repeat(headerLevel);
95
+ const actual = '#'.repeat(level);
96
+ errors.push({
97
+ file,
98
+ line: lineNum,
99
+ message: `Found '${actual} ${rawType}:' (heading level ${level}) but expected heading level ${headerLevel} (${expected} ${rawType}:)`,
100
+ suggestion: `Change '${actual}' to '${expected}'`,
101
+ severity: 'warning',
102
+ });
103
+ }
104
+ break;
105
+ }
106
+ }
107
+ }
108
+ if (!currentSection && line.trim() && !preHeaderWarned) {
109
+ // Non-blank content before any section header — will be silently lost
110
+ preHeaderWarned = true;
111
+ errors.push({
112
+ file,
113
+ line: lineNum,
114
+ message: 'Content found before first section header — this text will be ignored',
115
+ suggestion: 'Move this text into a section, or remove it',
116
+ severity: 'warning',
117
+ });
118
+ }
63
119
  }
64
120
  }
65
121
  // Don't forget last section
@@ -71,18 +127,39 @@ export function parseSections(content, headerLevel, validTypes, file = '') {
71
127
  }
72
128
  return { sections, errors };
73
129
  }
74
- const FIELD_PATTERN = /^(\w+)::\s*(.*)$/;
130
+ const FIELD_PATTERN = /^([\w-]+)::\s*(.*)$/;
75
131
  function parseFields(section, file) {
76
132
  const lines = section.body.split('\n');
77
133
  const warnings = [];
78
134
  const seenFields = new Set();
79
135
  let currentField = null;
80
136
  let currentValue = [];
137
+ let freeTextWarned = false;
81
138
  for (let i = 0; i < lines.length; i++) {
82
139
  const line = lines[i];
83
140
  const lineNum = section.line + i + 1; // +1 because body starts after header
84
141
  // Check for sub-header first - starts a new scope for field tracking
85
- if (line.match(/^#{1,6}\s/)) {
142
+ const headerMatch = line.match(/^(#{1,6})\s+(.*)$/);
143
+ if (headerMatch) {
144
+ // Before resetting, check if this looks like a markdown heading
145
+ // inside a content/instructions field (not a structural header)
146
+ if (currentField && MARKDOWN_CONTENT_FIELDS.has(currentField)) {
147
+ const headingText = headerMatch[2].trim();
148
+ // Extract type word: "Text", "Chat: title", "Article-excerpt" etc.
149
+ const typeWord = headingText.replace(/:.*$/, '').trim().toLowerCase();
150
+ // Also check if it's a typo of a structural type (levenshtein ≤ 2)
151
+ const isTypoOfStructural = [...ALL_STRUCTURAL_TYPES].some(st => levenshtein(typeWord, st) <= 2);
152
+ if (!ALL_STRUCTURAL_TYPES.has(typeWord) && !isTypoOfStructural) {
153
+ const hashes = headerMatch[1];
154
+ warnings.push({
155
+ file,
156
+ line: lineNum,
157
+ message: `'${hashes} ${headingText}' looks like a Markdown heading inside ${currentField}:: field`,
158
+ suggestion: `Escape it as '!${hashes} ${headingText}' so it's treated as content, not a section boundary`,
159
+ severity: 'warning',
160
+ });
161
+ }
162
+ }
86
163
  // Save current field if any
87
164
  if (currentField) {
88
165
  section.fields[currentField] = currentValue.join('\n').trim();
@@ -91,6 +168,7 @@ function parseFields(section, file) {
91
168
  }
92
169
  // Reset seenFields for the new sub-section scope
93
170
  seenFields.clear();
171
+ freeTextWarned = false;
94
172
  continue;
95
173
  }
96
174
  const match = line.match(FIELD_PATTERN);
@@ -118,6 +196,31 @@ function parseFields(section, file) {
118
196
  // Continue multiline value
119
197
  currentValue.push(line);
120
198
  }
199
+ else {
200
+ // Not inside a field — check for single-colon that should be double-colon
201
+ // Only suggest field:: if the word is a known field name
202
+ const singleColonMatch = line.match(/^(\w+):\s+(.*)$/);
203
+ if (singleColonMatch && !line.match(/^https?:/) && ALL_KNOWN_FIELDS.includes(singleColonMatch[1])) {
204
+ warnings.push({
205
+ file,
206
+ line: lineNum,
207
+ message: `Found '${singleColonMatch[1]}:' with single colon — did you mean '${singleColonMatch[1]}::'?`,
208
+ suggestion: `Change '${singleColonMatch[1]}:' to '${singleColonMatch[1]}::' (double colon)`,
209
+ severity: 'warning',
210
+ });
211
+ }
212
+ else if (line.trim() && !freeTextWarned) {
213
+ freeTextWarned = true;
214
+ const preview = line.trim().length > 60 ? line.trim().slice(0, 60) + '...' : line.trim();
215
+ warnings.push({
216
+ file,
217
+ line: lineNum,
218
+ message: `Text outside of a field:: definition will be ignored: "${preview}"`,
219
+ suggestion: 'Place this text inside a field (e.g., content:: your text), or remove it',
220
+ severity: 'warning',
221
+ });
222
+ }
223
+ }
121
224
  }
122
225
  // Save final field
123
226
  if (currentField) {
@@ -1 +1 @@
1
- {"version":3,"file":"sections.js","sourceRoot":"","sources":["../../src/parser/sections.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sections.js","sourceRoot":"","sources":["../../src/parser/sections.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { ContentError } from '../index.js';
2
+ export interface ParsedVideoTranscript {
3
+ title: string;
4
+ channel: string;
5
+ url: string;
6
+ }
7
+ export interface VideoTranscriptParseResult {
8
+ transcript: ParsedVideoTranscript | null;
9
+ errors: ContentError[];
10
+ }
11
+ export declare function parseVideoTranscript(content: string, file: string): VideoTranscriptParseResult;
@@ -0,0 +1,39 @@
1
+ import { parseFrontmatter } from './frontmatter.js';
2
+ import { validateFrontmatter } from '../validator/validate-frontmatter.js';
3
+ export function parseVideoTranscript(content, file) {
4
+ const errors = [];
5
+ const frontmatterResult = parseFrontmatter(content, file);
6
+ if (frontmatterResult.error) {
7
+ errors.push(frontmatterResult.error);
8
+ return { transcript: null, errors };
9
+ }
10
+ const { frontmatter } = frontmatterResult;
11
+ // Validate frontmatter against schema (typo detection + required fields)
12
+ const frontmatterErrors = validateFrontmatter(frontmatter, 'video-transcript', file);
13
+ errors.push(...frontmatterErrors);
14
+ const hasRequiredError = frontmatterErrors.some(e => e.severity === 'error');
15
+ if (hasRequiredError) {
16
+ return { transcript: null, errors };
17
+ }
18
+ // Check text fields for wikilinks (not url — that's a URL)
19
+ const textFields = ['title', 'channel'];
20
+ for (const field of textFields) {
21
+ const value = String(frontmatter[field]);
22
+ if (/\[\[.+?\]\]/.test(value)) {
23
+ errors.push({
24
+ file,
25
+ line: 2,
26
+ message: `Field '${field}' contains a wikilink — use plain text`,
27
+ suggestion: `Remove [[...]] from '${field}'`,
28
+ severity: 'error',
29
+ });
30
+ }
31
+ }
32
+ const transcript = {
33
+ title: String(frontmatter.title),
34
+ channel: String(frontmatter.channel),
35
+ url: String(frontmatter.url),
36
+ };
37
+ return { transcript, errors };
38
+ }
39
+ //# sourceMappingURL=video-transcript.js.map
@@ -0,0 +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;AAa3E,MAAM,UAAU,oBAAoB,CAAC,OAAe,EAAE,IAAY;IAChE,MAAM,MAAM,GAAmB,EAAE,CAAC;IAElC,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,CAAC,WAAW,EAAE,kBAAkB,EAAE,IAAI,CAAC,CAAC;IACrF,MAAM,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,CAAC;IAElC,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;IAC7E,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;KAC7B,CAAC;IAEF,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AAChC,CAAC"}
@@ -2,6 +2,8 @@ export interface WikilinkParts {
2
2
  path: string;
3
3
  display?: string;
4
4
  isEmbed?: boolean;
5
+ error?: string;
6
+ correctedPath?: string;
5
7
  }
6
8
  export declare function parseWikilink(text: string): WikilinkParts | null;
7
9
  export declare function resolveWikilinkPath(linkPath: string, sourceFile: string): string;